프로그래밍/NODEJS 강좌 BY GEMINI

6️⃣ 데이터베이스 연동: 정보의 저장과 관리 💾

lazy_web_devloper 2025. 7. 3. 09:32
728x90
반응형

6️⃣ 데이터베이스 연동: 정보의 저장과 관리 💾

이전 챕터에서 만든 API는 서버가 재시작되면 모든 데이터가 사라지는 치명적인 단점이 있었습니다. 실제 서비스에서는 데이터를 영구적으로 보존해야 하며, 이를 위해 데이터베이스를 사용합니다.

이번 챕터에서는 Node.js 애플리케이션에서 가장 널리 사용되는 두 종류의 데이터베이스, 즉 **관계형 데이터베이스(RDBMS)**와 NoSQL 데이터베이스에 어떻게 연결하고 데이터를 조작(CRUD)하는지 배워보겠습니다. 또한, 데이터베이스 작업을 훨씬 쉽게 만들어주는 ORM(Object-Relational Mapper)과 ODM(Object-Data Mapper)의 개념도 함께 다룰 것입니다.


관계형 데이터베이스 (RDBMS) 연동 🗄️

Java 개발자에게는 매우 친숙한 PostgreSQL, MySQL, Oracle 등 관계형 데이터베이스는 정해진 스키마(테이블 구조)에 따라 데이터를 체계적으로 저장하는 데 사용됩니다. Node.js에서도 이러한 RDBMS를 당연히 사용할 수 있습니다.

1. 데이터베이스 드라이버 사용

데이터베이스와 통신하기 위해서는 해당 데이터베이스 전용 드라이버(driver) 패키지가 필요합니다. Java에서 JDBC 드라이버(.jar 파일)를 사용하는 것과 동일한 원리입니다.

  • PostgreSQL용: pg
  • MySQL용: mysql2 (기존 mysql 패키지보다 성능 및 기능이 개선된 버전)

예시: pg 드라이버로 PostgreSQL에 직접 연결하고 쿼리 실행하기

먼저, 드라이버를 설치합니다.

Bash
 
npm install pg

async/await를 사용하여 쿼리를 실행하는 기본 코드는 다음과 같습니다.

JavaScript
 
const { Pool } = require('pg');

// 데이터베이스 연결 정보를 담은 Pool 객체 생성
// Pool은 내부적으로 커넥션 풀을 관리하여 효율성을 높여줍니다.
// Java의 DataSource와 유사한 개념입니다.
const pool = new Pool({
  user: 'db_user',
  host: 'localhost',
  database: 'my_database',
  password: 'secret_password',
  port: 5432,
});

async function getUsers() {
  try {
    // pool에서 커넥션을 빌려와 쿼리를 실행합니다.
    const res = await pool.query('SELECT * FROM users');
    console.log('사용자 목록:', res.rows); // res.rows에 쿼리 결과 배열이 담깁니다.
  } catch (err) {
    console.error('쿼리 실행 중 오류 발생', err.stack);
  } finally {
    // 실제 애플리케이션에서는 앱이 종료될 때 한 번만 호출합니다.
    // await pool.end();
  }
}

getUsers();

이처럼 드라이버를 직접 사용하면 SQL 쿼리를 그대로 작성하여 데이터베이스를 제어할 수 있습니다.

2. ORM (Object-Relational Mapper) 사용

애플리케이션 코드에 SQL 쿼리를 직접 작성하는 것은 반복적이고, SQL 인젝션 같은 보안 취약점을 유발할 수 있으며, 객체지향적인 코드 흐름을 방해합니다. 이러한 문제점을 해결하기 위해 ORM을 사용합니다.

ORM은 데이터베이스의 테이블(Table)을 JavaScript 객체/클래스(Object)와 매핑(mapping)하여, 개발자가 SQL을 직접 작성하는 대신 익숙한 객체지향적인 코드로 데이터베이스를 다룰 수 있게 해주는 도구입니다.

Java 개발자를 위한 비유: Node.js의 ORM은 Java 생태계의 JPA/Hibernate와 완벽하게 동일한 개념입니다. @Entity 어노테이션으로 클래스를 테이블과 매핑하고 UserRepository.save(user)와 같은 메소드로 데이터를 조작하는 것처럼, Node.js ORM도 모델(Model) 클래스를 정의하고 User.create(user) 같은 메소드를 사용합니다.

Node.js에서 인기 있는 ORM은 다음과 같습니다.

  • Sequelize: 가장 오래되고 널리 사용되는 ORM 중 하나입니다. 다양한 RDBMS를 지원하며 기능이 풍부합니다.
  • TypeORM: TypeScript를 기반으로 하며, 데코레이터(@) 문법을 사용하는 등 Java의 Hibernate/JPA와 매우 유사한 경험을 제공하여 Java 개발자가 특히 선호할 수 있습니다.

여기서는 Sequelize를 사용하여 이전 챕터의 API를 개선하는 예시를 보여드리겠습니다.

예시: Sequelize로 사용자 관리 API 구현하기

1단계: 필요 패키지 설치

Bash
 
# Sequelize ORM, pg 드라이버, pg-hstore(Postgres의 JSON 타입 지원) 설치
npm install sequelize pg pg-hstore

2단계: 연결 설정 및 모델 정의

JavaScript
 
const { Sequelize, DataTypes } = require('sequelize');

// 1. 데이터베이스 연결 설정
const sequelize = new Sequelize('my_database', 'db_user', 'secret_password', {
  host: 'localhost',
  dialect: 'postgres' // 어떤 RDBMS를 사용하는지 명시
});

// 2. 'User' 모델(테이블) 정의 (JPA의 @Entity 클래스와 유사)
const User = sequelize.define('User', {
  // 모델 이름은 단수형('User')을 사용하면,
  // Sequelize가 자동으로 복수형('Users')으로 테이블 이름을 만듭니다.
  id: {
    type: DataTypes.INTEGER,
    autoIncrement: true,
    primaryKey: true
  },
  name: {
    type: DataTypes.STRING,
    allowNull: false // NOT NULL 제약 조건
  }
  // createdAt과 updatedAt 컬럼은 Sequelize가 자동으로 추가하고 관리합니다.
}, {
  // 다른 모델 옵션들
});

// 3. 모델과 데이터베이스 테이블 동기화 (개발 시 유용)
// 테이블이 없으면 생성해줍니다.
async function syncDatabase() {
  try {
    await sequelize.sync({ force: false }); // force: true면 기존 테이블을 지우고 새로 만듦
    console.log('Database synced successfully.');
  } catch (error) {
    console.error('Unable to sync database:', error);
  }
}

syncDatabase();

// 이제 'User' 모델을 사용하여 CRUD 작업을 수행할 수 있습니다.
// 예: User.create({ name: 'Bob' });

3단계: Express API에 Sequelize 적용하기

이전 챕터의 메모리 배열 대신, User 모델을 사용하도록 API 코드를 수정합니다.

JavaScript
 
// 모든 사용자 조회 (Read)
app.get('/api/users', async (req, res) => {
  try {
    const users = await User.findAll(); // SELECT * FROM "Users";
    res.json(users);
  } catch (err) {
    res.status(500).json({ message: 'Error fetching users', error: err });
  }
});

// 새로운 사용자 생성 (Create)
app.post('/api/users', async (req, res) => {
  try {
    const newUser = await User.create({ name: req.body.name }); // INSERT INTO "Users" ...
    res.status(201).json(newUser);
  } catch (err) {
    res.status(500).json({ message: 'Error creating user', error: err });
  }
});

// ... 다른 라우트 핸들러들도 findByPk, update, destroy 등의 메소드를 사용하여 수정 ...

ORM을 사용함으로써 코드에서 SQL 쿼리가 사라지고, 비즈니스 로직에 더 집중할 수 있는 명확하고 유지보수하기 좋은 코드가 되었습니다.


RDBMS와의 연동은 많은 엔터프라이즈급 애플리케이션의 기본입니다. 드라이버를 직접 사용하거나, ORM을 통해 생산성을 높이는 방법을 이해하는 것은 매우 중요합니다.

728x90
반응형