ガイド: Atlas データAPIのExpress .js の代替手段
項目一覧
9 月 2024 日現在、Atlas データAPI は非推奨になりました。このサービスを使用する場合は、9 月 2025 より前に別のソリューションに移行する必要があります。
Atlas データAPI を使用すると、開発者はHTTPエンドポイントを使用してMongoDB Atlasクラスターを直接操作できます。このガイドでは、ドライバーベースのカスタムAPIを使用して、 データAPIの機能を複製する方法を説明します。
APIテンプレートはNode.jsとExpressで構築され、Vercelへの配置をサポートします。バックエンドサービスはAPIキーベースの認可で保護されており、MongoDB Node.jsドライバーとMongooseを使用して、データに対してCRUD (作成、読み取り、更新、削除)と集計操作を実行します。
プロジェクトのソースコードは、次の Githubリポジトリで入手できます。 https://github.com/abhishekmongoDB/data-api-alternative
重要
本番環境に対応していない
このテンプレートは、本番環境またはサードパーティのホスティング環境での使用を意図したものではありません。範囲が制限され、基本機能のみが表示されます。特定の要件を満たし、セキュリティのベストプラクティスに従うために、追加の機能強化を実装することを強くお勧めします。
プロジェクト構造
data-api-alternative/ . ├── index.js ├── connection/ │ └── databaseManager.js ├── controllers/ │ └── dbController.js ├── models/ │ └── dynamicModel.js ├── routes/ │ └── api.js ├── utils/ │ └── logging.js ├── .env ├── package.json └── vercel.json
主なファイルは次のとおりです。
.env: 認証情報、接続の詳細、プロジェクト設定を保持する構成ファイル。
インデックス .js: Expressサーバーのメインエントリ点。
route/api.js: 対応するビジネス ロジックにマップされたAPIエンドポイントを公開します。
connection/databaseManager.js: MongoDBデータベース接続を管理し、キャッシュします。
models/dyntaxModel.js: スキーマレス Atlas データをサポートするMongooseモデルを動的に生成しました。
Drivers/dbCluster.js: 定義されたCRUDおよび集計操作のビジネス ロジック。
これらのファイルについては、以下で詳しく説明しています。
はじめる
前提条件
有効な接続文字列を持つ配置されたMongoDBクラスターまたはローカルインスタンス。
有効なAtlas APIキーリクエストを認証するための。組織レベルまたはプロジェクトレベルで、プログラムによるアクセスを付与できます。
Node.jsとnpm (Node Package Manager)の最新の安定バージョンがインストールされました。
プロジェクトを設定する
リポジトリをクローンし、依存関係をインストールします。
git clone https://github.com/abhishekmongoDB/data-api-alternative.git cd data-api-alternative npm install
環境変数を定義する
プロジェクトのルートディレクトリに .env
ファイルを作成し、次のコードを貼り付けます。
Replace with your deployment credentials MONGO_URI="<MONGO_URI>" MONGO_OPTIONS="<MONGO_OPTIONS>" # Optional API_KEY="<API_KEY>" API_SECRET="<API_SECRET>" Project variables PORT=7438 RATE_LIMIT_WINDOW_MS=900000 # 15 minutes in milliseconds RATE_LIMIT_MAX=100 # Maximum requests per window RATE_LIMIT_MESSAGE=Too many requests, please try again later.
次のプレースホルダーを自分の認証情報に置き換えます。
MONGO_URI
を、配置用の接続文字列に置き換えます。詳細については、「 接続文字列の形式 」を参照してください。ローカルインスタンス:
"mongodb://[<user>:<pw>@]localhost"
Atlas cluster:
"mongodb+srv://[<user>:<pw>@]<cluster>.<projectId>.mongodb.net"
MONGO_OPTIONS
接続固有のオプションを指定する任意のクエリ文字列に置き換えます。詳細については、「 接続文字列オプション 」を参照してください。API_KEY
を有効なAPIキーに置き換えます。API_SECRET
を、対応するAPIシークレットに置き換えます。
サーバーを初期化
Expressサーバーはindex.js
ファイルで初期化されます。サーバーは.env
ファイルで指定されたポートでリッスンします。
サーバーには次のミドルウェアが含まれています。
x-api-key
とx-api-secret
ヘッダーを使用したAPIキーとシークレットの検証JSONリクエストボディを解析します。
APIエンドポイントに基づいてコントローラー メソッドへのリクエストをルーティングします。
レート制限。レート制限オプションは
.env
ファイルで指定されます。winston
ライブラリを使用したログ記録。
/** * This file initializes the server, validates API keys for added security, * and routes requests to the appropriate controller methods. */ const rateLimit = require("express-rate-limit"); const express = require("express"); const apiRoutes = require("./routes/api"); const logger = require("./utils/logging"); require("dotenv").config(); const API_KEY = process.env.API_KEY; // Load API key from .env const API_SECRET = process.env.API_SECRET; // Load API secret from .env const app = express(); // Middleware for rate limiting const limiter = rateLimit({ windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10), // 15 minutes max: parseInt(process.env.RATE_LIMIT_MAX, 10), // Limit each IP to 100 requests per windowMs message: { message: process.env.RATE_LIMIT_MESSAGE }, }); // Apply the rate limiter to all requests app.use(limiter); // Middleware for parsing requests app.use(express.json()); // Middleware for API key authentication and logging app.use((req, res, next) => { logger.info({ method: req.method, url: req.originalUrl, body: req.body, headers: req.headers, }); const apiKey = req.headers["x-api-key"]; const apiSecret = req.headers["x-api-secret"]; if (apiKey === API_KEY && apiSecret === API_SECRET) { next(); // Proceed to the next middleware or route } else { res.status(403).json({ message: "Forbidden: Invalid API Key or Secret" }); } }); // Middleware for API routing app.use("/api", apiRoutes); // Start the server const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });
ルートを定義する
api.js
ファイルは、基本的なCRUDおよび集計操作のルートを定義します。 HTTP POST リクエストは、対応するコントローラー関数にマッピングされます。
/** * Defines the routes for all API endpoints and maps them to the corresponding functions in the dbController class. */ const express = require('express'); const dbController = require('../controllers/dbController'); const router = express.Router(); router.post('/insertOne', dbController.insertOne); router.post('/insertMany', dbController.insertMany); router.post('/findOne', dbController.findOne); router.post('/find', dbController.find); router.post('/updateOne', dbController.updateOne); router.post('/deleteOne', dbController.deleteOne); router.post('/deleteMany', dbController.deleteMany); router.post('/aggregate', dbController.aggregate); module.exports = router;
MongoDB に接続する
databaseManager.js
ファイルは、mongoose
ライブラリを使用してMongoDBデータベース接続を処理します。接続の詳細は .env
ファイルに保存されます。
使用可能なオプションの完全なリストについては、 MongoDB Node.jsドライバーのドキュメントの接続オプションを参照してください。
const mongoose = require("mongoose"); require("dotenv").config(); const connections = {}; // Cache for database connections /** * Manages MongoDB database connections. * @param {string} database - The database name. * @returns {mongoose.Connection} - Mongoose connection instance. */ const getDatabaseConnection = (database) => { const baseURI = process.env.MONGO_URI; const options = process.env.MONGO_OPTIONS || ""; if (!baseURI) { throw new Error("MONGO_URI is not defined in .env file"); } // If connection does not exist, create it if (!connections[database]) { connections[database] = mongoose.createConnection( `${baseURI}/${database}${options}` ); // Handle connection errors connections[database].on("error", (err) => { console.error(`MongoDB connection error for ${database}:`, err); }); connections[database].once("open", () => { console.log(`Connected to MongoDB database: ${database}`); }); } return connections[database]; }; module.exports = getDatabaseConnection;
モデルを動的に生成する
dynamicModel.js
ユーティリティファイルは、指定されたデータベースとコレクションのMongooseモデルを動的に生成します。
また、次の処理も行います。
あらゆるフィールドを受け入れることができる柔軟なスキーマを使用してモデルを定義します。
冗長なモデル定義を回避するためにモデルをキャッシュします。
// Import the mongoose library for MongoDB object modeling const mongoose = require("mongoose"); // Import the function to get a database connection const getDatabaseConnection = require("../connection/databaseManager"); // Initialize an empty object to cache models // This helps in reusing models and avoiding redundant model creation const modelsCache = {}; // Cache for models (database.collection -> Model) /** * Creates and retrieves a dynamic model for a given database and collection. * This function ensures that the same model is reused if it has already been created. * * @param {string} database - The name of the database. * @param {string} collection - The name of the collection. * @returns {mongoose.Model} - The Mongoose model instance for the specified collection. */ const getModel = (database, collection) => { // Create a unique key for the model based on the database and collection names const modelKey = `${database}.${collection}`; // Check if the model already exists in the cache // If it does, return the cached model if (modelsCache[modelKey]) { return modelsCache[modelKey]; } // Get the database connection for the specified database const dbConnection = getDatabaseConnection(database); // Define a flexible schema with no predefined structure // This allows the schema to accept any fields const schema = new mongoose.Schema({}, { strict: false }); // Create the model using the database connection, collection name, and schema // Cache the model for future use const model = dbConnection.model(collection, schema, collection); modelsCache[modelKey] = model; // Return the newly created model return model; }; // Export the getModel function as a module module.exports = getModel;
ドライバーを実装する
dbController.js
ファイルは、 CRUD操作と集計を処理するためのコントローラー ロジックを実装しています。各メソッドは、動的に生成されたMongooseモデルを使用してMongoDBとやりとりします。
API は現在、次の操作をサポートしています。
1 つを挿入
多数挿入
1件を特定
findMany
更新 1
多数更新
deleteOne
deleteMany
集計
/** * Contains the logic for all CRUD and aggregation operations. * Each method interacts with the database and handles errors gracefully. */ const getModel = require("../models/dynamicModel"); class DbController { async insertOne(req, res) { const { database, collection, document } = req.body; try { const Model = getModel(database, collection); const result = await Model.insertOne(document); if (!result) { return res .status(400) .json({ success: false, message: "Insertion failed" }); } res.status(201).json({ success: true, result }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } } async insertMany(req, res) { const { database, collection, documents } = req.body; try { const Model = getModel(database, collection); const result = await Model.insertMany(documents); if (!result || result.length === 0) { return res .status(400) .json({ success: false, message: "Insertion failed" }); } res.status(201).json({ success: true, result }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } } async findOne(req, res) { const { database, collection, filter, projection } = req.body; try { const Model = getModel(database, collection); const result = await Model.findOne(filter, projection); if (!result) { return res .status(404) .json({ success: false, message: "No record found" }); } res.status(200).json({ success: true, result }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } } async find(req, res) { const { database, collection, filter, projection, sort, limit } = req.body; try { const Model = getModel(database, collection); const result = await Model.find(filter, projection).sort(sort).limit(limit); if (!result || result.length === 0) { return res .status(404) .json({ success: false, message: "No records found" }); } res.status(200).json({ success: true, result }); } catch (error) { r es.status(500).json({ success: false, error: error.message }); } } async updateOne(req, res) { const { database, collection, filter, update, upsert } = req.body; try { const Model = getModel(database, collection); const result = await Model.updateOne(filter, update, { upsert }); if (result.matchedCount === 0) { return res .status(404) .json({ success: false, message: "No records updated" }); } res.status(200).json({ success: true, result }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } } async updateMany(req, res) { const { database, collection, filter, update } = req.body; try { const Model = getModel(database, collection); const result = await Model.updateMany(filter, update); if (result.matchedCount === 0) { return res .status(404) .json({ success: false, message: "No records updated" }); } res.status(200).json({ success: true, result }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } } async deleteOne(req, res) { const { database, collection, filter } = req.body; try { const Model = getModel(database, collection); const result = await Model.deleteOne(filter); if (result.deletedCount === 0) { return res .status(404) .json({ success: false, message: "No records deleted" }); } res.status(200).json({ success: true, result }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } } async deleteMany(req, res) { const { database, collection, filter } = req.body; try { const Model = getModel(database, collection); const result = await Model.deleteMany(filter); if (result.deletedCount === 0) { return res .status(404).json({ success: false, message: "No records deleted" }); } res.status(200).json({ success: true, result }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } } async aggregate(req, res) { const { database, collection, pipeline } = req.body; try { const Model = getModel(database, collection); const result = await Model.aggregate(pipeline); if (!result || result.length === 0) { return res .status(404) .json({ success: false, message: "No aggregation results found" }); } res.status(200).json({ success: true, result }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } } } module.exports = new DbController();
APIリクエストの例
次の例は、sample_mflix.usersサンプルデータセットでAPIを使用する方法を示しています。 Atlas クラスターで使用可能な sample_mflix
データセットがまだない場合は、「 Atlas へのデータのロード 」を参照してください。
サーバーを起動する
次のコマンドを実行して、 Expressサーバーを起動します。プロジェクトファイルへの変更を保存するたびに、サーバーは自動的に再読み込みされます。
npm run dev
リクエストの送信
を実行中サーバーでは、次の cURL コマンドを使用してリクエストを送信できます。
これらの例コマンドを実行中前に、<YOUR_API_KEY>
と <YOUR_API_SECRET>
のプレースホルダーを認証情報で更新していることを確認してください。
ドキュメントの挿入
次の例は、1 つまたは複数のドキュメントを挿入する方法を示しています。
新しいユーザードキュメントを users
コレクションに挿入します 。
curl -X POST http://localhost:7438/api/insertOne \ -H "Content-Type: application/json" \ -H "x-api-key: <YOUR_API_KEY>" \ -H "x-api-secret: <YOUR_API_SECRET>" \ -d '{ "database": "sample_mflix", "collection": "users", "document": { "name": "Marcus Bell", "email": "marcus.bell@example.com", "password": "lucky13" } }'
複数のユーザー ドキュメントを users
コレクションに挿入します 。
curl -X POST http://localhost:7438/api/insertMany \ -H "Content-Type: application/json" \ -H "x-api-key: <YOUR_API_KEY>" \ -H "x-api-secret: <YOUR_API_SECRET>" \ -d '{ "database": "sample_mflix", "collection": "users", "documents": [ { "name": "Marvin Diaz", "email": "marvin.diaz@example.com", "password": "123unicorn" }, { "name": "Delores Lambert", "email": "delores.lambert@example.com", "password": "cats&dogs" }, { "name": "Gregor Ulrich", "email": "gregor.ulrich@example.com", "password": "securePass123" } ] }'
ドキュメントの検索
次の例は、1 つまたは複数のドキュメントを検索する方法を示しています。
名前でユーザーを検索し、メールアドレスのみを返します。
curl -X POST http://localhost:7438/api/findOne \ -H "Content-Type: application/json" \ -H "x-api-key: <YOUR_API_KEY>" \ -H "x-api-secret: <YOUR_API_SECRET>" \ -d '{ "database": "sample_mflix", "collection": "users", "filter": { "name": "Marvin Diaz" }, "projection": { "email": 1, "_id": 0 } }'
指定されたドメインで終わるメールアドレスを持つすべてのユーザーを検索します。次に、結果を名前で並べ替え、最初の 10 の名前とメールのみを返します。
curl -X POST http://localhost:7438/api/find \ -H "Content-Type: application/json" \ -H "x-api-key: <YOUR_API_KEY>" \ -H "x-api-secret: <YOUR_API_SECRET>" \ -d '{ "database": "sample_mflix", "collection": "users", "filter": { "email": { "$regex": "example\\.com$" } }, "projection": { "name": 1, "email": 1, "_id": 0 }, "sort": { "name": 1 }, "limit": 10 }'
Update Documents
次の例は、1 つまたは複数のドキュメントの更新を示しています。
指定されたメールメールを持つユーザーのメールアドレスを更新します。
curl -X POST http://localhost:7438/api/updateOne \ -H "Content-Type: application/json" \ -H "x-api-key: <YOUR_API_KEY>" \ -H "x-api-secret: <YOUR_API_SECRET>" \ -d '{ "database": "sample_mflix", "collection": "users", "filter": { "email": "marvin.diaz@example.com" }, "update": { "$set": { "password": "456pegasus" } }, "upsert": false }'
指定されたドメインを持つすべてのユーザーのメールアドレスを更新します。
curl -X POST http://localhost:7438/api/updateMany \ -H "Content-Type: application/json" \ -H "x-api-key: <YOUR_API_KEY>" \ -H "x-api-secret: <YOUR_API_SECRET>" \ -d '{ "database": "sample_mflix", "collection": "users", "filter": { "email": { "$regex": "@example\\.com$" } }, "update": { "$set": { "email": { "$replaceAll": { "input": "$email", "find": "@example.com", "replacement": "@example.org" } } } }, "upsert": false }'
削除
次の例は、1 つまたは複数のドキュメントの削除を示しています。
指定された名前のドキュメントを削除します。
curl -X POST http://localhost:7438/api/deleteOne \ -H "Content-Type: application/json" \ -H "x-api-key: <YOUR_API_KEY>" \ -H "x-api-secret: <YOUR_API_SECRET>" \ -d '{ "database": "sample_mflix", "collection": "users", "filter": { "name": "Delores Lambert" } }'
名前が「M」で始まるすべてのドキュメントを削除します。
curl -X POST http://localhost:7438/api/deleteMany \ -H "Content-Type: application/json" \ -H "x-api-key: <YOUR_API_KEY>" \ -H "x-api-secret: <YOUR_API_SECRET>" \ -d '{ "database": "sample_mflix", "collection": "users", "filter": { "name": { "$regex": "^M" } } }'
ドキュメントの集計
次の例では、ユーザーをメールごとにグループ化し、発生回数をカウントする集計操作を示しています。
curl -X POST http://localhost:7438/api/aggregate \ -H "Content-Type: application/json" \ -H "x-api-key: <YOUR_API_KEY>" \ -H "x-api-secret: <YOUR_API_SECRET>" \ -d '{ "database": "sample_mflix", "collection": "users", "pipeline": [ { "$group": { "_id": "$email", "count": { "$sum": 1 } } } ] }'
次のステップ
このガイドでは、廃止された Atlas データAPIの代替として、カスタム ドライバーベースのAPIを実装する方法について説明しました。ただし、テンプレートアプリの範囲は限定されており、基本的な機能のみを表示します。特定の要件を満たすために、追加の機能強化を実装することを強くお勧めします。
追加機能
テンプレートアプリを拡張するために、次の推奨機能を使用します。
ログの記録: 可視性とデバッグ性を向上させるために構造化ログを実装します。
エラー追跡: エラー追跡ツールと統合して、 API の健全性を監視します。
レート制限の強化: より強力なリクエスト制限でAPI の不正使用を防ぎます。
セキュリティ: 一般的な脆弱性に対してAPIを強化します。特にオンプレミスでホストされている場合は、機密データと秘密が適切に保護されていることを確認してください。