728x90
반응형
Node.js 애플리케이션에서 MongoDB를 연동하는 방법을 알아봅니다. MongoDB 네이티브 드라이버를 사용하여 데이터베이스에 연결하고 CRUD 작업을 수행하는 전체 과정을 다룹니다.
1. MongoDB 소개와 특징
MongoDB는 문서 지향 NoSQL 데이터베이스입니다. 데이터를 JSON과 유사한 BSON(Binary JSON) 형식으로 저장하며, 스키마가 유연하여 다양한 형태의 데이터를 저장할 수 있습니다.
주요 특징:
- 문서 기반 저장: 데이터를 컬렉션 내의 문서로 저장하며, 각 문서는 고유한
_id필드를 가집니다 - 유연한 스키마: 같은 컬렉션 내에서도 문서마다 다른 필드를 가질 수 있습니다
- 수평적 확장: 샤딩을 통해 여러 서버에 데이터를 분산 저장할 수 있습니다
- 풍부한 쿼리 언어: 필터링, 정렬, 집계 등 다양한 쿼리 기능을 제공합니다
- 인덱싱 지원: 단일 필드, 복합 필드, 텍스트 인덱스 등을 지원합니다
2. mongodb 네이티브 드라이버 설치 및 설정
Node.js에서 MongoDB를 사용하려면 공식 MongoDB Node.js 드라이버를 설치해야 합니다.
2.1 패키지 설치
npm install mongodb
2.2 기본 설정
const { MongoClient } = require('mongodb');
// MongoDB 연결 URI
const uri = 'mongodb://localhost:27017';
// 클라이언트 인스턴스 생성
const client = new MongoClient(uri);
2.3 프로젝트 구조 예시
project/
├── package.json
├── src/
│ ├── db/
│ │ └── connection.js
│ ├── models/
│ │ └── user.js
│ └── index.js
└── .env
3. 연결 문자열(Connection String) 구성
MongoDB 연결 문자열은 데이터베이스 연결에 필요한 모든 정보를 포함합니다.
3.1 로컬 MongoDB 연결
// 기본 로컬 연결
const uri = 'mongodb://localhost:27017';
// 특정 데이터베이스 지정
const uri = 'mongodb://localhost:27017/myDatabase';
3.2 인증이 필요한 연결
// 사용자명과 비밀번호 포함
const uri = 'mongodb://username:password@localhost:27017/myDatabase';
// 인증 데이터베이스 지정
const uri = 'mongodb://username:password@localhost:27017/myDatabase?authSource=admin';
3.3 MongoDB Atlas 클라우드 연결
// MongoDB Atlas 연결 문자열
const uri = 'mongodb+srv://username:password@cluster0.xxxxx.mongodb.net/myDatabase?retryWrites=true&w=majority';
3.4 연결 옵션
const { MongoClient } = require('mongodb');
const uri = 'mongodb://localhost:27017';
const client = new MongoClient(uri, {
maxPoolSize: 10, // 연결 풀 최대 크기
minPoolSize: 5, // 연결 풀 최소 크기
serverSelectionTimeoutMS: 5000, // 서버 선택 타임아웃
socketTimeoutMS: 45000, // 소켓 타임아웃
connectTimeoutMS: 10000, // 연결 타임아웃
retryWrites: true, // 쓰기 작업 재시도
w: 'majority' // 쓰기 확인 수준
});
반응형
4. 데이터베이스 연결 및 연결 관리
4.1 기본 연결
const { MongoClient } = require('mongodb');
const uri = 'mongodb://localhost:27017';
const client = new MongoClient(uri);
async function connect() {
try {
await client.connect();
console.log('MongoDB에 연결되었습니다.');
// 데이터베이스 선택
const db = client.db('myDatabase');
// 컬렉션 선택
const collection = db.collection('users');
return { db, collection };
} catch (error) {
console.error('연결 오류:', error);
throw error;
}
}
connect();
4.2 연결 상태 확인
async function checkConnection() {
try {
await client.connect();
// ping 명령으로 연결 확인
await client.db('admin').command({ ping: 1 });
console.log('MongoDB 연결이 정상입니다.');
return true;
} catch (error) {
console.error('연결 확인 실패:', error);
return false;
}
}
4.3 싱글톤 패턴으로 연결 관리
// db/connection.js
const { MongoClient } = require('mongodb');
const uri = process.env.MONGODB_URI || 'mongodb://localhost:27017';
let client = null;
let db = null;
async function connectDB(dbName = 'myDatabase') {
if (db) {
return db;
}
try {
client = new MongoClient(uri);
await client.connect();
db = client.db(dbName);
console.log(`${dbName} 데이터베이스에 연결되었습니다.`);
return db;
} catch (error) {
console.error('데이터베이스 연결 실패:', error);
throw error;
}
}
function getDB() {
if (!db) {
throw new Error('데이터베이스가 연결되지 않았습니다. connectDB()를 먼저 호출하세요.');
}
return db;
}
async function closeDB() {
if (client) {
await client.close();
client = null;
db = null;
console.log('데이터베이스 연결이 종료되었습니다.');
}
}
module.exports = { connectDB, getDB, closeDB };
4.4 연결 관리 모듈 사용
// index.js
const { connectDB, getDB, closeDB } = require('./db/connection');
async function main() {
// 데이터베이스 연결
await connectDB('myDatabase');
// 연결된 DB 인스턴스 사용
const db = getDB();
const users = db.collection('users');
// 작업 수행
const result = await users.find({}).toArray();
console.log(result);
// 연결 종료
await closeDB();
}
main().catch(console.error);
5. CRUD 작업
5.1 문서 삽입 (Create)
단일 문서 삽입 - insertOne
async function insertOneDocument(collection) {
const document = {
name: '홍길동',
email: 'hong@example.com',
age: 30,
createdAt: new Date()
};
const result = await collection.insertOne(document);
console.log('삽입된 문서 ID:', result.insertedId);
return result;
}
여러 문서 삽입 - insertMany
async function insertManyDocuments(collection) {
const documents = [
{ name: '김철수', email: 'kim@example.com', age: 25 },
{ name: '이영희', email: 'lee@example.com', age: 28 },
{ name: '박민수', email: 'park@example.com', age: 35 }
];
const result = await collection.insertMany(documents);
console.log('삽입된 문서 수:', result.insertedCount);
console.log('삽입된 문서 IDs:', result.insertedIds);
return result;
}
5.2 문서 조회 (Read)
단일 문서 조회 - findOne
async function findOneDocument(collection) {
// 조건에 맞는 첫 번째 문서 조회
const document = await collection.findOne({ name: '홍길동' });
console.log('조회된 문서:', document);
return document;
}
여러 문서 조회 - find
async function findDocuments(collection) {
// 모든 문서 조회
const allDocs = await collection.find({}).toArray();
// 조건에 맞는 문서 조회
const filteredDocs = await collection.find({ age: { $gte: 30 } }).toArray();
// 정렬과 제한
const sortedDocs = await collection
.find({})
.sort({ age: -1 }) // 나이 내림차순
.limit(5) // 5개만 조회
.toArray();
// 특정 필드만 조회 (프로젝션)
const projectedDocs = await collection
.find({}, { projection: { name: 1, email: 1, _id: 0 } })
.toArray();
return { allDocs, filteredDocs, sortedDocs, projectedDocs };
}
쿼리 연산자 사용
async function queryWithOperators(collection) {
// 비교 연산자
const gteDocs = await collection.find({ age: { $gte: 25 } }).toArray(); // 25 이상
const ltDocs = await collection.find({ age: { $lt: 30 } }).toArray(); // 30 미만
const neDocs = await collection.find({ status: { $ne: 'inactive' } }).toArray(); // 같지 않음
// 논리 연산자
const andDocs = await collection.find({
$and: [
{ age: { $gte: 25 } },
{ status: 'active' }
]
}).toArray();
const orDocs = await collection.find({
$or: [
{ age: { $lt: 25 } },
{ age: { $gt: 35 } }
]
}).toArray();
// 배열 연산자
const inDocs = await collection.find({
status: { $in: ['active', 'pending'] }
}).toArray();
return { gteDocs, ltDocs, andDocs, orDocs, inDocs };
}
5.3 문서 수정 (Update)
단일 문서 수정 - updateOne
async function updateOneDocument(collection) {
const filter = { name: '홍길동' };
const update = {
$set: { age: 31, updatedAt: new Date() },
$inc: { loginCount: 1 }
};
const result = await collection.updateOne(filter, update);
console.log('매칭된 문서 수:', result.matchedCount);
console.log('수정된 문서 수:', result.modifiedCount);
return result;
}
여러 문서 수정 - updateMany
async function updateManyDocuments(collection) {
const filter = { status: 'inactive' };
const update = {
$set: { status: 'active', activatedAt: new Date() }
};
const result = await collection.updateMany(filter, update);
console.log('매칭된 문서 수:', result.matchedCount);
console.log('수정된 문서 수:', result.modifiedCount);
return result;
}
문서 교체 - replaceOne
async function replaceDocument(collection) {
const filter = { name: '홍길동' };
const replacement = {
name: '홍길동',
email: 'newhong@example.com',
age: 32,
profile: {
bio: '개발자',
website: 'https://example.com'
},
updatedAt: new Date()
};
const result = await collection.replaceOne(filter, replacement);
console.log('교체된 문서 수:', result.modifiedCount);
return result;
}
upsert 옵션 사용
async function upsertDocument(collection) {
const filter = { email: 'new@example.com' };
const update = {
$set: { name: '신규사용자', age: 20 },
$setOnInsert: { createdAt: new Date() }
};
const options = { upsert: true };
const result = await collection.updateOne(filter, update, options);
console.log('upserted ID:', result.upsertedId);
return result;
}
5.4 문서 삭제 (Delete)
단일 문서 삭제 - deleteOne
async function deleteOneDocument(collection) {
const filter = { name: '홍길동' };
const result = await collection.deleteOne(filter);
console.log('삭제된 문서 수:', result.deletedCount);
return result;
}
여러 문서 삭제 - deleteMany
async function deleteManyDocuments(collection) {
// 조건에 맞는 여러 문서 삭제
const filter = { status: 'inactive' };
const result = await collection.deleteMany(filter);
console.log('삭제된 문서 수:', result.deletedCount);
return result;
}
async function deleteAllDocuments(collection) {
// 모든 문서 삭제 (주의: 컬렉션의 모든 데이터 삭제)
const result = await collection.deleteMany({});
console.log('삭제된 문서 수:', result.deletedCount);
return result;
}
6. 인덱스 생성 및 관리
인덱스는 쿼리 성능을 크게 향상시킵니다. 자주 검색하는 필드에 인덱스를 생성하면 조회 속도가 빨라집니다.
6.1 단일 필드 인덱스
async function createSingleIndex(collection) {
// 오름차순 인덱스 생성
const result = await collection.createIndex({ email: 1 });
console.log('생성된 인덱스:', result);
return result;
}
6.2 복합 인덱스
async function createCompoundIndex(collection) {
// 여러 필드를 조합한 인덱스
const result = await collection.createIndex(
{ status: 1, createdAt: -1 },
{ name: 'status_createdAt_idx' }
);
console.log('생성된 복합 인덱스:', result);
return result;
}
6.3 고유 인덱스
async function createUniqueIndex(collection) {
// 중복을 허용하지 않는 인덱스
const result = await collection.createIndex(
{ email: 1 },
{ unique: true }
);
console.log('생성된 고유 인덱스:', result);
return result;
}
6.4 텍스트 인덱스
async function createTextIndex(collection) {
// 텍스트 검색을 위한 인덱스
const result = await collection.createIndex(
{ title: 'text', content: 'text' },
{ default_language: 'korean' }
);
console.log('생성된 텍스트 인덱스:', result);
return result;
}
// 텍스트 검색 사용
async function textSearch(collection) {
const results = await collection.find({
$text: { $search: '검색어' }
}).toArray();
return results;
}
6.5 인덱스 조회 및 삭제
async function manageIndexes(collection) {
// 모든 인덱스 조회
const indexes = await collection.indexes();
console.log('현재 인덱스 목록:', indexes);
// 특정 인덱스 삭제
await collection.dropIndex('email_1');
// 모든 인덱스 삭제 (_id 인덱스 제외)
await collection.dropIndexes();
return indexes;
}
7. 에러 처리 및 연결 종료
7.1 에러 처리
const { MongoClient, MongoServerError } = require('mongodb');
async function handleErrors(collection) {
try {
// 중복 키 삽입 시도
await collection.insertOne({ _id: 'duplicate', name: '테스트' });
await collection.insertOne({ _id: 'duplicate', name: '테스트2' });
} catch (error) {
if (error instanceof MongoServerError) {
if (error.code === 11000) {
console.error('중복 키 오류:', error.message);
} else {
console.error('MongoDB 서버 오류:', error.message);
}
} else {
console.error('예상치 못한 오류:', error);
}
}
}
7.2 연결 오류 처리
async function connectWithRetry(uri, maxRetries = 3) {
const client = new MongoClient(uri);
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
await client.connect();
console.log('MongoDB 연결 성공');
return client;
} catch (error) {
console.error(`연결 시도 ${attempt}/${maxRetries} 실패:`, error.message);
if (attempt === maxRetries) {
throw new Error('최대 재시도 횟수 초과');
}
// 재시도 전 대기
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
}
7.3 연결 종료 처리
async function gracefulShutdown(client) {
process.on('SIGINT', async () => {
console.log('애플리케이션 종료 중...');
try {
await client.close();
console.log('MongoDB 연결이 정상적으로 종료되었습니다.');
process.exit(0);
} catch (error) {
console.error('연결 종료 중 오류:', error);
process.exit(1);
}
});
}
7.4 전체 예제
const { MongoClient } = require('mongodb');
const uri = 'mongodb://localhost:27017';
async function main() {
const client = new MongoClient(uri);
try {
// 연결
await client.connect();
console.log('MongoDB에 연결되었습니다.');
const db = client.db('testDB');
const users = db.collection('users');
// 문서 삽입
const insertResult = await users.insertOne({
name: '테스트 사용자',
email: 'test@example.com',
createdAt: new Date()
});
console.log('삽입된 문서 ID:', insertResult.insertedId);
// 문서 조회
const user = await users.findOne({ email: 'test@example.com' });
console.log('조회된 사용자:', user);
// 문서 수정
const updateResult = await users.updateOne(
{ email: 'test@example.com' },
{ $set: { name: '수정된 이름' } }
);
console.log('수정된 문서 수:', updateResult.modifiedCount);
// 문서 삭제
const deleteResult = await users.deleteOne({ email: 'test@example.com' });
console.log('삭제된 문서 수:', deleteResult.deletedCount);
} catch (error) {
console.error('오류 발생:', error);
} finally {
// 연결 종료
await client.close();
console.log('MongoDB 연결이 종료되었습니다.');
}
}
main();
결론
Node.js에서 MongoDB 네이티브 드라이버를 사용하면 문서 기반 데이터베이스의 유연성을 그대로 활용할 수 있습니다. 연결 풀 관리, 적절한 인덱스 설정, 에러 처리를 통해 안정적인 데이터베이스 연동을 구현할 수 있으며, 비동기 처리 패턴과 함께 사용하면 효율적인 애플리케이션을 개발할 수 있습니다.
728x90
반응형
'Node.js' 카테고리의 다른 글
| Node.js 데이터베이스 연동(Database Integration) 완벽 가이드 (1) | 2026.03.16 |
|---|---|
| Node.js의 쿠키 관리(Cookie Management) (0) | 2026.03.16 |
| Node.js의 세션 관리(Session Management) (0) | 2026.03.15 |
| Node.js의 OAuth2.0 구현 (1) | 2026.03.15 |
| Node.js의 JSON 웹 토큰(JWT) 사용법 (0) | 2026.03.14 |