ANNOUNCEMENT: Voyage AI joins MongoDB to power more accurate and trustworthy AI applications on Atlas.
Learn more
Docs 菜单

指南:实施Atlas Data API的Express .js 替代方案

自 20249 月起, Atlas Data API已被弃用。如果您使用此服务,则必须在 9 月 2025之前迁移到其他解决方案。

Atlas Data API允许开发者使用HTTP端点直接与其MongoDB Atlas集群交互。本指南演示如何使用基于驱动程序的自定义API复制数据API的功能。

API模板使用 Node.js 和Express构建,支持在Vercel上部署。后端服务通过基于API密钥的授权进行保护,并使用MongoDB Node.js驾驶员Mongoose对数据执行增删改查 (创建、读取、更新和删除)和聚合操作。

以下 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 服务器的主入口点。

  • routes/api.js:公开映射到相应业务逻辑的API端点。

  • connection/databaseManager.js:管理和缓存MongoDB 数据库连接。

  • models/dynamicModel.js:动态生成的支持无模式Atlas数据的Mongoose模型。

  • Controllers/dbController.js:已定义的增删改查和聚合操作的业务逻辑。

下面将更详细地描述这些文件。

克隆存储库并安装依赖项:

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文件定义基本增删改查和聚合操作的路由。 HTTP POST 请求映射到相应的控制器功能。

routes/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/dynamicModel.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文件实现用于处理增删改查操作和聚合的控制器逻辑。每种方法都使用动态生成的Mongoose模型与MongoDB进行交互。

该API目前支持以下操作:

  • insertOne

  • insertmany

  • 查找一个

  • 找到很多

  • updateOne

  • UpdateMany

  • deleteOne

  • deleteMany

  • 聚合

Controllers/dbController.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();

以下示例请求演示了如何使用API与sample_mflix.users示例数据集。如果您的Atlas 集群中还没有可用的 sample_mflix 数据集,请参阅将数据加载到Atlas。

注意

可用的 Postman 集合

这些 API 还作为项目存储库中的Postman集合提供。

运行以下命令以启动Express服务器。每次将更改保存到项目文件时,服务器都会自动重新加载。

npm run dev

在服务器运行,您可以使用以下 cURL 命令发送请求。

在运行这些示例命令之前,请确保使用您的凭证更新<YOUR_API_KEY><YOUR_API_SECRET> 占位符。

以下示例演示了如何插入一个或多个文档。

将新的用户文档插入 users集合:

insertOne
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集合:

insertmany
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" }
]
}'

以下示例演示如何查找一个或多个文档。

按名称查找用户并仅返回电子邮件解决:

查找一个
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
}'

以下示例演示了更新一个或多个文档。

使用指定的电子邮件更新用户的电子邮件解决:

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

更新具有指定域的所有用户的电子邮件解决:

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

以下示例演示了删除一个或多个文档。

删除指定名称的文档:

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

本指南说明了如何实现基于驱动程序的自定义API作为已弃用的Atlas Data API的替代方案。但是,模板应用的范围有限,仅演示基本功能。我们强烈建议实施其他增强功能,以满足您的特定要求。

以下是增强模板应用的推荐功能:

  • 增强的日志记录:实施结构化日志记录以提高可观察性和调试性。

  • 错误跟踪:与错误跟踪工具集成以监控API运行状况。

  • 增强的速率限制:通过更强大的请求限制保护您的API免遭滥用。

  • 安全性:针对常见漏洞强化API。在部署之前,确保敏感数据和密钥得到适当保护,尤其是在异地托管时。