Docs Menu

가이드: Atlas Data API 에 대한 Express .js 대안 구현

2024 9월부터 Atlas Data API 더 이상 사용되지 않습니다. 이 서비스를 사용하는 경우 2025 9월 이전에 다른 솔루션으로 마이그레이션 해야 합니다.

Atlas Data API 통해 개발자는 HTTP 엔드포인트를 사용하여 MongoDB Atlas 클러스터와 직접 상호 작용 수 있습니다. 이 가이드 드라이버 기반 사용자 지정 API 사용하여 데이터 API 의 기능을 복제하는 방법을 보여 줍니다.

API 템플릿은 Node.js 및 Express 로 구축되었으며,Vercel에서 배포서버 지원합니다. 백엔드 서비스는 API 키 기반 권한 부여 로 보호되며 Mongoose 와 함께 MongoDB Node.js 운전자 사용하여 데이터에 대한 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 database 연결을 관리하고 캐시합니다.

  • models/dynamicModel.js: 스키마리스 Atlas 데이터를 지원 동적으로 생성된 Mongoose 모델입니다.

  • controllers/dbcontroller.js: 정의된 CRUD 및 집계 작업에 대한 비즈니스 로직입니다.

이러한 파일에 대해서는 아래에서 자세히 설명합니다.

리포지토리 복제하고 종속성을 설치합니다.

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 요청은 해당 컨트롤러 기능에 매핑됩니다.

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 database 연결을 처리합니다. 연결 세부 정보는 .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 파일 CRUD 작업 및 애그리게이션을 처리하기 위한 컨트롤러 로직을 구현합니다. 각 메서드는 동적으로 생성된 Mongoose 모델을 사용하여 MongoDB 와 상호 작용합니다.

API 현재 다음 작업을 지원합니다.

  • insertOne

  • insertmany

  • 하나의 결과 찾기

  • find many

  • 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();

다음 예시 요청은 sample_mflix.users 샘플 데이터 세트와 함께 API 사용하는 방법을 보여줍니다. Atlas cluster 에서 아직 사용할 수 있는 sample_mflix 데이터 세트가 없는 경우 Atlas 에 데이터 로드를 참조하세요.

참고

우편 배달부 컬렉션 사용 가능

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의 이름과 이메일 만 반환합니다.

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

이 가이드 더 이상 사용되지 않는 Atlas Data API 의 대안으로 사용자 지정 드라이버 기반 API 구현 방법을 설명합니다. 그러나 템플릿 앱 범위가 제한되어 있으며 기본 기능만 보여 줍니다. 특정 요구 사항을 충족하기 위해 추가 개선 사항을 구현하는 것이 좋습니다.

다음은 템플릿 앱 개선하기 위해 권장되는 기능입니다.

  • 향상된 로깅: 더 나은 관찰 가능성과 디버깅을 위해 구조화된 로깅을 구현합니다.

  • 오류 추적: 오류 추적 도구와 통합하여 API 상태를 모니터 합니다.

  • 향상된 속도 제한: 보다 강력한 요청 제한을 통해 API 남용으로부터 보호합니다.

  • 보안: 일반적인 취약점에 대해 API 강화합니다. 배포하기 전에, 특히 오프프레미스에서 호스팅하는 경우 민감한 데이터와 비밀이 제대로 보호되는지 확인하세요.