Docs Menu

ガイド: 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-keyx-api-secret ヘッダーを使用したAPIキーとシークレットの検証

  • JSONリクエストボディを解析します。

  • APIエンドポイントに基づいてコントローラー メソッドへのリクエストをルーティングします。

  • レート制限。レート制限オプションは .envファイルで指定されます。

  • winston ライブラリを使用したログ記録。

index.js
/**
* 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 リクエストは、対応するコントローラー関数にマッピングされます。

route/api.js
/**
* 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;

databaseManager.jsファイルは、mongoose ライブラリを使用してMongoDBデータベース接続を処理します。接続の詳細は .envファイルに保存されます。

使用可能なオプションの完全なリストについては、 MongoDB Node.jsドライバーのドキュメントの接続オプションを参照してください。

connection/databaseManager.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モデルを動的に生成します。

また、次の処理も行います。

  • あらゆるフィールドを受け入れることができる柔軟なスキーマを使用してモデルを定義します。

  • 冗長なモデル定義を回避するためにモデルをキャッシュします。

models/dicalModel.js
// 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

  • 集計

operations/dbCluster.js
/**
* 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();

次の例は、sample_mflix.usersサンプルデータセットでAPIを使用する方法を示しています。 Atlas クラスターで使用可能な sample_mflix データセットがまだない場合は、「 Atlas へのデータのロード 」を参照してください。

注意

Postman コレクションが利用可能

プロジェクトリポジトリでは、Postmanコレクションとしても利用できます。

次のコマンドを実行して、 Expressサーバーを起動します。プロジェクトファイルへの変更を保存するたびに、サーバーは自動的に再読み込みされます。

npm run dev

を実行中サーバーでは、次の cURL コマンドを使用してリクエストを送信できます。

これらの例コマンドを実行中前に、<YOUR_API_KEY><YOUR_API_SECRET> のプレースホルダーを認証情報で更新していることを確認してください。

次の例は、1 つまたは複数のドキュメントを挿入する方法を示しています。

新しいユーザードキュメントを usersコレクションに挿入します 。

1 つを挿入
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 つまたは複数のドキュメントを検索する方法を示しています。

名前でユーザーを検索し、メールアドレスのみを返します。

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 の名前とメールのみを返します。

find
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
}'

次の例は、1 つまたは複数のドキュメントの更新を示しています。

指定されたメールメールを持つユーザーのメールアドレスを更新します。

更新 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 つまたは複数のドキュメントの削除を示しています。

指定された名前のドキュメントを削除します。

deleteOne
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」で始まるすべてのドキュメントを削除します。

deleteMany
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を強化します。特にオンプレミスでホストされている場合は、機密データと秘密が適切に保護されていることを確認してください。