Node.js MongoDB 데이터 삽입 시 흔히 발생하는 문제점들과 해결방법

안녕하세요! 오늘은 Node.js와 MongoDB를 사용할 때 데이터 삽입 과정에서 자주 발생하는 문제점들과 그 해결방법에 대해 이야기해보겠습니다.

문제 상황: "db is not defined" 오류

db is not defined 에러 해결법

많은 개발자들이 처음 MongoDB를 사용할 때 겪는 가장 흔한 문제 중 하나가 바로 이 오류입니다.

해당 오류는 db라는 변수를 참조하지 못해서 발생하는오류입니다.

 

잘못된 코드 예시

const express = require('express')
const app = express()

const { MongoClient } = require('mongodb');
const url = 'mongodb://localhost:27017';
const client = new MongoClient(url);

async function main() {
  await client.connect();
  const db = client.db('forum'); // 함수 스코프 내에서만 유효
  const collection = db.collection('documents');
  return '성공';
}

main()
  .then(console.log)
  .catch(console.error)
  .finally(() => client.close()); // 연결이 즉시 끊어짐

app.get('/news', (요청, 응답) => {
  db.collection('post').insertOne({ // ❌ db is not defined!
    title: '오늘의 날씨',
    content: '맑음'
  })
})

18번 라인까지는 mongodb npm에서 제공되는 예제코드입니다.(링크)

단순 디비를 조회하고 응답을 반환할때는 문제가 없지만 새로운 라우터를 생성하고 요청을 해야하는 상황에서 다음과 같은 문제가 발생합니다.

문제점 분석

  1. 스코프 문제: db 변수가 main() 함수 내부에서만 정의되어 라우트 핸들러에서 접근할 수 없음
  2. 연결 해제: client.close()가 즉시 실행되어 데이터베이스 연결이 끊어짐
  3. 비동기 처리 부재: 데이터 삽입이 비동기 작업인데 await를 사용하지 않음
  4. 응답 누락: 클라이언트에게 응답을 보내지 않아 요청이 무한 대기

 

올바른 해결방법

전역 변수로 데이터베이스 연결 관리

const express = require('express')
const app = express()

const { MongoClient } = require('mongodb');
const url = 'mongodb://localhost:27017';
const client = new MongoClient(url);

// 전역 변수로 db 선언
let db;

async function main() {
  await client.connect();
  console.log('서버에 연결하였습니다.');
  db = client.db('forum'); // 전역 변수에 할당
  return '성공';
}

main()
  .then(console.log)
  .catch(console.error)
  // 연결 유지를 위해 client.close() 제거

 

데이터 삽입비동기 처리와 에러 핸들링

app.get('/news', async (요청, 응답) => {
  try {
    const result = await db.collection('post').insertOne({
      title: '오늘의 날씨',
      content: '맑음'
    });
    응답.send('데이터가 성공적으로 추가되었습니다!');
  } catch (error) {
    console.error('오류:', error);
    응답.status(500).send('서버 오류가 발생했습니다.');
  }
})

코드 분석

1. async 키워드

app.get('/news', async (요청, 응답) => {
  • 라우트 핸들러 함수 앞에 async를 붙이면 이 함수가 비동기 함수가 됩니다
  • 비동기 함수 내에서만 await 키워드를 사용할 수 있습니다

 

2. await 키워드

const result = await db.collection('post').insertOne({...});
  • await는 "기다려"라는 뜻입니다
  • 데이터베이스 작업이 완료될 때까지 기다린 후 다음 코드를 실행합니다
  • await 없이 db.collection('post').insertOne({...})만 쓰면 Promise 객체가 반환되어 즉시 다음 코드로 넘어갑니다

잠깐 그러면 'await'을 사용하면 중간에 대기해야하니까 동기 처리가 아닐까? 하는 생각이 들수 있습니다.

await해당 함수에서만 응답이올때까지 기다리고, 다른 함수들의 요청들은 계속 처리됩니다. 즉 위 코드에서 DB 작업은 백그라운드에서 실행되면서 Node.js는 다른 라우터 핸들러의 작업을 계속 받을 수 있습니다.

 

3. result 변수에 담는 이유

const result = await db.collection('post').insertOne({...});
  • insertOne() 메서드는 삽입 결과 정보를 반환합니다
  • result에는 다음과 같은 정보가 담깁니다.
{
  acknowledged: true,
  insertedId: ObjectId("..."), // 새로 생성된 문서의 ID
  insertedCount: 1
}

 

4. 응답.send()로 응답 보내기

응답.send('데이터가 성공적으로 추가되었습니다!');
  • Express.js에서 모든 HTTP 요청에는 반드시 응답을 보내야 합니다
  • 응답을 보내지 않으면 브라우저가 무한 대기 상태가 됩니다
  • 응답.send()는 클라이언트에게 텍스트, JSON, HTML 등을 전송합니다

 

5. try-catch로 에러 처리

try {
  // 데이터베이스 작업
} catch (error) {
  console.error('오류:', error);
  응답.status(500).send('서버 오류가 발생했습니다.');
}
  • try 블록에서 오류가 발생하면 catch 블록이 실행됩니다
  • 데이터베이스 연결 실패, 권한 문제, 네트워크 오류 등을 처리합니다
  • 응답.status(500)은 HTTP 500 에러(서버 내부 오류)를 의미합니다
Top