블로그 본문
[Express-MongoDB 연동] Node.js에서 mongodb 클라이언트 드라이버 연동, db.js 모듈화 및 RESTful API 구현
니꼴라 클라우드 테크 10월 06, 2026📌 목차 바로가기
지난 포스팅에서는 MongoDB의 환경 구성, BSON 포맷의 특징, 원자적 수정자($set, \(inc, \)push) 기반의 기본 CRUD 문법과 Aggregation Framework(집계 파이프라인)의 \(match, \)group Stage 연산 원리를 터미널 CLI 환경에서 실습했습니다.
데이터베이스 본체의 CRUD와 집계 쿼리 연산력을 갖추었으니, 이제 이 MongoDB 인프라를 웹 애플리케이션의 핵심 서버 프레임워크인 Node.js Express 환경과 유기적으로 결합할 차례입니다. 오늘 포스팅에서는 보안 환경변수 관리, 싱글톤 패턴 기반의 db.js 연결 모듈화, 비즈니스 로직을 추상화하는 userService.js 계층 분리, 그리고 Express 기반 RESTful API(app.js) 구현 기술을 설명해 드리겠습니다.
1. 패키지 구성 및 보안 환경변수 관리 (.env)
Node.js 백엔드 서버에서 MongoDB와 연동하기 위해서는 공식 드라이버 패키지인 mongodb와 보안 환경변수 관리 모듈인 dotenv를 설치해야 합니다.
■ 패키지 설치 명령어
# Node.js 프로젝트 초기화 및 필수 패키지 설치
npm init -y
npm install mongodb dotenv express
■ 하드코딩 방지 및 .env 파일 보안 수칙
실무 프로젝트에서 가장 치명적인 보안 사고 중 하나는 데이터베이스 접속 URI와 인증 비밀번호를 소스 코드 내부에 날것(Hard-Coding)으로 적재하는 것입니다. 코드가 GitHub 같은 외부 공개 저장소에 푸시되는 순간, 전 세계 해커들의 타깃이 되어 데이터베이스가 장악당하는 대참사가 유발됩니다.
따라서 데이터베이스 접속 문자열(MONGODB_URI)과 대상 데이터베이스 이름(MONGODB_DATABASE)은 소스 코드와 완전히 분리하여 Root 디렉토리의 .env 파일에 격리 보관합니다.
# .env 환경변수 설정 파일
MONGODB_URI=mongodb://localhost:27017
MONGODB_DATABASE=shopping_db
PORT=3000
이 설정은 추후 AWS DocumentDB와 같은 클라우드 매니지드 환경으로 전환할 때도 코드 수정 없이 .env 파일 내의 URI 문자열에 tls=true&tlsCAFile=global-bundle.pem 옵션만 추가하여 즉시 유연하게 확장할 수 있는 아키텍처적 기반이 됩니다.
2. 싱글톤 패턴 기반 db.js 데이터베이스 연결 모듈화
기존 RDBMS(MySQL) 환경에서는 mysql2/promise 패키지의 createPool()을 사용하여 커넥션 풀을 생성했습니다. 반면 MongoDB 환경에서는 공식 드라이버가 제공하는 MongoClient 클래스를 가동하여 클라이언트 커넥션 객체를 생성합니다.
/**
* db.js - MongoDB 데이터베이스 연결 관리 중앙 모듈
*/
const { MongoClient } = require('mongodb');
require('dotenv').config(); // .env 환경변수 로드
const uri = process.env.MONGODB_URI;
const client = new MongoClient(uri, {
maxPoolSize: 10, // 커넥션 풀 최대 유지 개수 (기본 100개, 적정 10개)
serverSelectionTimeoutMS: 5000, // 서버 선택 타임아웃 (5초)
socketTimeoutMS: 45000 // 소켓 타임아웃 (45초)
});
let dbInstance = null;
/**
* 데이터베이스 연결 및 인스턴스 반환 함수
*/
async function connectToDatabase() {
try {
if (!dbInstance) {
await client.connect();
console.log('MongoDB에 성공적으로 연결되었습니다.');
dbInstance = client.db(process.env.MONGODB_DATABASE);
}
return dbInstance;
} catch (err) {
console.error('MongoDB 연결 에러 발생:', err);
throw err;
}
}
module.exports = { connectToDatabase, client };
■ RDBMS vs NoSQL 연결 커넥션 관리 차이
- MySQL (
mysql2): 개별 비즈니스 쿼리를 실행할 때마다 풀에서 커넥션을 수동으로 빌려온 뒤, 작업이 마무리면connection.release()를 통해 풀로 반환해야 하는 엄격한 수동 관리가 필요합니다. - MongoDB (
mongodb):MongoClient객체가 내부적으로 고성능 커넥션 풀을 자체 관리합니다. 애플리케이션 가동 시client.connect()를 실행해두면, 이후 각 라우터에서 개별적으로 커넥션을 반납하는 코드 없이 자율적이고 우아하게 비동기 I/O를 처리합니다.
3. 계층형 아키텍처(Layered Architecture) 기반 userService.js 모듈화
단일 엔트리 포인트 파일에 DB 쿼리문과 HTTP 요청/응답 로직을 뒤섞어 작성하는 것은 유지보수성을 파괴하는 안티패턴입니다. **단일 책임 원칙(SRP)**에 입각해 DB 접근 및 CRUD 비즈니스 연산만을 전담하는 서비스 모듈인 userService.js를 수립합니다.
/**
* userService.js - 사용자 데이터 CRUD 비즈니스 로직 전담 모듈
*/
const { connectToDatabase } = require('./db');
module.exports = {
// 1. 사용자 신규 생성 (Create)
createUser: async function (userData) {
try {
const db = await connectToDatabase();
const result = await db.collection('users').insertOne(userData);
return result;
} catch (err) {
console.error('createUser 에러:', err);
throw err;
}
},
// 2. 사용자 목록 조회 (Read)
getUsers: async function (query = {}) {
try {
const db = await connectToDatabase();
return await db.collection('users').find(query).toArray();
} catch (err) {
console.error('getUsers 에러:', err);
throw err;
}
},
// 3. 사용자 정보 수정 (Update)
updateUser: async function (userId, updateData) {
try {
const db = await connectToDatabase();
const result = await db.collection('users').updateOne(
{ _id: userId },
{ $set: updateData }
);
return result;
} catch (err) {
console.error('updateUser 에러:', err);
throw err;
}
},
// 4. 사용자 삭제 (Delete)
deleteUser: async function (userId) {
try {
const db = await connectToDatabase();
const result = await db.collection('users').deleteOne({ _id: userId });
return result;
} catch (err) {
console.error('deleteUser 에러:', err);
throw err;
}
}
};
4. 계층형 아키텍처 및 Express-MongoDB 수송 메시지 흐름도
클라이언트의 비동기 HTTP 요청이 Express 애플리케이션(app.js)의 RESTful API 엔드포인트로 유입되면, 서비스 계층(userService.js)을 거쳐 db.js 중앙 모듈의 MongoClient 커넥션 풀을 이용해 MongoDB로 전송되는 4계층 수송 아키텍처 흐름도입니다.
5. Express 기반 RESTful API 구동 서버 (app.js) 구현
이제 userService.js 서비스 계층을 메인 Express 웹 서버인 app.js에 라우팅 연결하여 클라이언트에 완결된 RESTful API 서비스를 제공합니다.
/**
* app.js - Express 웹 서버 및 RESTful API 엔드포인트 설정
*/
const express = require('express');
const userService = require('./userService');
const { client } = require('./db');
const app = express();
app.use(express.json()); // JSON 요청 본문(Body) 파싱 미들웨어
// 1. 사용자 생성 API (POST /users)
app.post('/users', async (req, res) => {
try {
const result = await userService.createUser(req.body);
res.status(201).json(result);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 2. 사용자 목록 조회 API (GET /users)
app.get('/users', async (req, res) => {
try {
const users = await userService.getUsers();
res.json(users);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 3. 사용자 수정 API (PUT /users/:id)
app.put('/users/:id', async (req, res) => {
try {
const result = await userService.updateUser(req.params.id, req.body);
res.json(result);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 4. 사용자 삭제 API (DELETE /users/:id)
app.delete('/users/:id', async (req, res) => {
try {
const result = await userService.deleteUser(req.params.id);
res.json(result);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 서버 구동
const PORT = process.env.PORT || 3000;
const server = app.listen(PORT, () => {
console.log(`Express 서버가 포트 ${PORT}에서 실행 중입니다.`);
});
// 프로세스 종료 시 커넥션 정리 (Graceful Shutdown)
process.on('SIGINT', async () => {
console.log('\n서버 종료 신호 수신. DB 커넥션을 안전하게 닫습니다.');
if (client) await client.close();
server.close(() => {
console.log('서버가 안전하게 종료되었습니다.');
process.exit(0);
});
});
마치며: 모듈화와 계층형 아키텍처로 백엔드 결속력을 완성했습니다!
이번 포스팅에서 MongoDB 공식 드라이버와 dotenv 기반의 보안 환경변수 관리, 싱글톤 패턴 기반의 db.js 연결 모듈화, userService.js 계층 분리, 그리고 Express 기반 RESTful API 서버(app.js) 및 Graceful Shutdown 자원 반납 프로세스를 구축했습니다.
"DB 접속 정보의 보안 격리부터 MongoClient 커넥션 풀 중앙 관리, 비즈니스 로직의 계층적 추상화, 그리고 프로세스 종료 시 자원 해제에 이르기까지 enterprise 수준의 단단한 NoSQL 웹 백엔드 아키텍처를 수립한 것입니다."
Node.js 환경에서의 기본 Express-MongoDB 연동 및 RESTful API 구현이 완비되었으니, 이제 한 단계 더 나아가 클라우드 환경에서 완전 관리형으로 제공되는 AWS DynamoDB와 DocumentDB의 클라우드 매니지드 NoSQL 인프라 구축으로 나아갈 차례입니다.
다음 포스팅 [서버리스 NoSQL] Amazon DynamoDB의 HTTP 기반 무상태(Stateless) 아키텍처, 파티션 키(PK) 설계 및 초고속 Key-Value 다루기] 편에서는 TCP 커넥션 풀을 사용하지 않는 DynamoDB의 HTTP Stateless 통신 메커니즘, Primary/Sort Key 인덱싱 전략 및 AWS 콘솔/CLI 실습을 진행하겠습니다.
오늘 실습한 db.js 모듈화나 Express 라우팅 및 Graceful Shutdown 연산에 대해 궁금한 점이 있으시다면 주저하지 마시고 운영자 메일로 질문을 남겨주세요. 오늘도 안전하고 똑똑한 클라우딩 라이프 하세요. 감사합니다! 😉
출처
- 이현호. 실무 프로젝트로 완성하는 클라우드 환경에서 DB 구축과 웹 개발. 길벗캠퍼스. 2026.04. (10.3장 'MongoDB 개요 및 환경구축', 302-311페이지 및 13.1장 'DB 연결 및 관리 모듈(db.js)', 390-392페이지 참조)
- 이현호. "10장. NoSQL 데이터베이스_설명오디오.m4a" 및 "11장. 클라우드 NoSQL 데이터베이스_설명오디오.m4a" 설명 오디오 가이드 스크립트 기반 mongodb 공식 드라이버 패키지 구성, dotenv 보안 URI 분리, db.js 싱글톤 모듈화, userService.js 비즈니스 추상화 및 Graceful Shutdown 수칙 비하인드 대화록 완벽 반영.
- MongoDB Node.js Driver Documentation (https://www.mongodb.com/docs/drivers/node/current/) 및 Express.js Routing Guide.
자주 묻는 질문 (FAQ)
Q. Node.js 애플리케이션에서 MongoDB 연동 시 데이터베이스 접속 URI 정보를 .env 환경변수로 분리 관리하는 이유는 무엇인가요?
A. 보안성 확보 및 환경별 확장성 보장입니다. DB 접속 URI와 인증 비밀번호가 코드에 하드코딩되면 GitHub 등을 통한 소스 유출 시 치명적인 해킹 사고가 유발되므로, .env 파일에 격리하여 로컬 테스트 및 AWS DocumentDB 클라우드 전환 시 설정만 유연하게 교체하도록 설계합니다.
Q. MySQL의 connection.release() 수동 반환 방식과 비교할 때, MongoDB MongoClient의 커넥션 풀 관리 방식의 차이점은 무엇인가요?
A. 자율적 커넥션 풀링 관리 방식입니다. MySQL은 쿼리 후 개발자가 명시적으로 connection.release()를 호출해 풀에 반환해야 하지만, MongoDB는 MongoClient가 내부적으로 커넥션 풀(maxPoolSize)을 보유하고 비동기 I/O 요청 시 자율적으로 풀을 유지하고 재사용합니다.
Q. 백엔드 아키텍처 설계 시 Express 라우터(app.js)와 DB 조작 로직을 userService.js 모듈로 계층 분리하는 이점은 무엇인가요?
A. 단일 책임 원칙(SRP) 준수 및 유지보수성 향상입니다. HTTP 요청/응답 처리는 app.js가 담당하고, 실제 데이터 CRUD 조작은 userService.js가 담당하도록 추상화함으로써, 코드 중복을 제거하고 기능 수정이나 단위 테스트 실행 시 독립성을 높여줍니다.
