指南:实施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:已定义的增删改查和聚合操作的业务逻辑。
下面将更详细地描述这些文件。
开始体验
先决条件
已部署的 MongoDB 集群或本地实例,具有有效连接字符串。
用于验证请求的有效Atlas API密钥。您可以在组织或项目级别授予编程访问权限权限。
设置项目
克隆存储库并安装依赖项:
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.
将以下占位符替换为您的凭证:
初始化服务器
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
文件定义基本增删改查和聚合操作的路由。 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
文件实现用于处理增删改查操作和聚合的控制器逻辑。每种方法都使用动态生成的Mongoose模型与MongoDB进行交互。
该API目前支持以下操作:
insertOne
insertmany
查找一个
找到很多
updateOne
UpdateMany
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请求示例
以下示例请求演示了如何使用API与sample_mflix.users示例数据集。如果您的Atlas 集群中还没有可用的 sample_mflix
数据集,请参阅将数据加载到Atlas。
启动服务器
运行以下命令以启动Express服务器。每次将更改保存到项目文件时,服务器都会自动重新加载。
npm run dev
发送请求
在服务器运行,您可以使用以下 cURL 命令发送请求。
在运行这些示例命令之前,请确保使用您的凭证更新<YOUR_API_KEY>
和 <YOUR_API_SECRET>
占位符。
插入文档
以下示例演示了如何插入一个或多个文档。
将新的用户文档插入 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" } ] }'
查找文档
以下示例演示如何查找一个或多个文档。
按名称查找用户并仅返回电子邮件解决:
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
以下示例演示了更新一个或多个文档。
使用指定的电子邮件更新用户的电子邮件解决:
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 }'
删除
以下示例演示了删除一个或多个文档。
删除指定名称的文档:
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 } } } ] }'
后续步骤
本指南说明了如何实现基于驱动程序的自定义API作为已弃用的Atlas Data API的替代方案。但是,模板应用的范围有限,仅演示基本功能。我们强烈建议实施其他增强功能,以满足您的特定要求。
其他功能
以下是增强模板应用的推荐功能:
增强的日志记录:实施结构化日志记录以提高可观察性和调试性。
错误跟踪:与错误跟踪工具集成以监控API运行状况。
增强的速率限制:通过更强大的请求限制保护您的API免遭滥用。
安全性:针对常见漏洞强化API。在部署之前,确保敏感数据和密钥得到适当保护,尤其是在异地托管时。