가이드: 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 및 집계 작업에 대한 비즈니스 로직입니다.
이러한 파일에 대해서는 아래에서 자세히 설명합니다.
시작하기
전제 조건
유효한 연결 문자열 있는배포된 MongoDB cluster 또는 로컬 인스턴스 입니다.
유효한 Atlas API 키 입니다. 요청을 인증하기 위한 것입니다. 조직 또는 프로젝트 수준에서 프로그래밍 방식의 액세스를 부여할 수 있습니다.
안정적인 최신 버전의 Node.js 및 npm (Node Package 관리자) 이 설치되었습니다.
프로젝트 설정
리포지토리 복제하고 종속성을 설치합니다.
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 database 연결을 처리합니다. 연결 세부 정보는 .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 현재 다음 작업을 지원합니다.
insertOne
insertmany
하나의 결과 찾기
find many
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 요청 예시
다음 예시 요청은 sample_mflix.users 샘플 데이터 세트와 함께 API 사용하는 방법을 보여줍니다. Atlas cluster 에서 아직 사용할 수 있는 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 }'
문서 업데이트
다음 예에서는 하나 이상의 문서를 업데이트하는 방법을 보여 줍니다.
지정된 이메일 로 사용자의 이메일 주소 업데이트합니다.
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 } } } ] }'
다음 단계
이 가이드 더 이상 사용되지 않는 Atlas Data API 의 대안으로 사용자 지정 드라이버 기반 API 구현 방법을 설명합니다. 그러나 템플릿 앱 범위가 제한되어 있으며 기본 기능만 보여 줍니다. 특정 요구 사항을 충족하기 위해 추가 개선 사항을 구현하는 것이 좋습니다.
추가 기능
다음은 템플릿 앱 개선하기 위해 권장되는 기능입니다.
향상된 로깅: 더 나은 관찰 가능성과 디버깅을 위해 구조화된 로깅을 구현합니다.
오류 추적: 오류 추적 도구와 통합하여 API 상태를 모니터 합니다.
향상된 속도 제한: 보다 강력한 요청 제한을 통해 API 남용으로부터 보호합니다.
보안: 일반적인 취약점에 대해 API 강화합니다. 배포하기 전에, 특히 오프프레미스에서 호스팅하는 경우 민감한 데이터와 비밀이 제대로 보호되는지 확인하세요.