TypeORM 이해하기

shooting star·2024년 5월 8일
0
post-thumbnail

들어가며

웹 애플리케이션을 개발할 때 데이터베이스는 굉장히 중요합니다. 특히 관계형 데이터베이스를 효율적으로 다루기 위해 ORM(Object-Relational Mapping)이 자주 사용됩니다. 이번 글에서는 Node.js에서 TypeORM을 사용하여 데이터베이스를 효율적으로 다루는 방법을 소개하겠습니다.

ORM vs 순수 JavaScript

ORM 사용 예시

TypeORM을 사용하여 Board 테이블에서 데이터를 가져오는 방법은 다음과 같습니다.

// ORM 사용
const boards = await Board.find({ title: 'Hello', status: 'PUBLIC' });

순수 JavaScript 사용 예시

ORM을 사용하지 않고 SQL 쿼리를 직접 작성하여 데이터를 가져오는 방법은 다음과 같습니다.

// 순수 JavaScript 사용
db.query('SELECT * FROM boards WHERE title = "Hello" AND status = "PUBLIC"', (err, result) => {
    if (err) {
        throw new Error('Error');
    }
    const boards = result.rows;
});

ORM(Object Relational Mapping)이란?

ORM은 객체와 관계형 데이터베이스 간의 데이터를 자동으로 변형 및 연결하는 작업입니다. 이를 통해 개발자는 객체 지향 프로그래밍 방식으로 데이터베이스를 조작할 수 있게 됩니다.

ORM과 Node.js 추상화 계층

데이터베이스 추상화 계층은 SQL을 직접 사용하지 않고도 데이터베이스와 통신할 수 있도록 도와주는 레이어입니다. 이를 위해 개발자는 다양한 라이브러리를 활용할 수 있으며, 추상화 정도에 따라 세 가지 수준으로 나뉩니다.

저수준: 데이터베이스 드라이버

데이터베이스 드라이버는 데이터베이스 연결을 처리하고 원시 SQL 문자열을 작성하여 데이터베이스에 전달합니다. 대표적인 예는 다음과 같습니다.

  • mysql: MySQL용 드라이버
  • pg: PostgreSQL용 드라이버
  • sqlite3: SQLite용 드라이버

다음은 pg 모듈을 사용하여 PostgreSQL 데이터베이스에 접근하는 예시입니다.

// 데이터베이스 드라이버 사용
const { Client } = require('pg');
const client = new Client({
    user: 'postgres',
    host: 'localhost',
    database: 'test',
    password: 'password',
    port: 5432
});
client.connect();

client.query('SELECT * FROM ingredients WHERE recipe = $1', ['Chicken Tikka Masala'], (err, res) => {
    if (err) {
        console.error(err);
    } else {
        console.log(res.rows);
    }
    client.end();
});

중간수준: 쿼리 빌더

쿼리 빌더는 SQL 쿼리를 좀 더 구조화된 방식으로 작성할 수 있도록 도와줍니다. 대표적인 쿼리 빌더 라이브러리는 다음과 같습니다.

  • knex: 다수의 SQL 언어를 지원하는 쿼리 빌더
// knex 사용
const knex = require('knex')({
    client: 'pg',
    connection: {
        host: 'localhost',
        user: 'postgres',
        password: 'password',
        database: 'test'
    }
});

knex('ingredients')
    .select('*')
    .where({ recipe: 'Chicken Tikka Masala' })
    .then(data => console.log(data))
    .catch(err => console.error(err));

고수준: ORM

ORM은 데이터베이스의 데이터를 객체로 매핑하여 애플리케이션에서 객체 지향적으로 다룰 수 있도록 합니다. 주요 ORM 라이브러리는 다음과 같습니다.

  • typeorm
  • sequelize
  • prisma

장점

  • 데이터베이스 추상화: 다양한 데이터베이스 지원
  • 중복 코드 방지
  • SQL 인젝션 방지
  • 모델 유효성 검사 지원
  • TypeScript 지원

단점

  • 러닝 커브: 각 ORM마다 고유한 문법이 있음
  • 성능 이슈: 복잡한 쿼리의 경우 성능 저하 가능

TypeORM이란?

TypeORM은 Node.js에서 실행되고 TypeScript로 작성된 객체 관계형 매퍼 라이브러리입니다. 다양한 관계형 데이터베이스를 지원하며, 간단한 CLI 명령어로 생산성을 높일 수 있습니다.

TypeORM 특징과 이점

  1. 모델 기반 데이터베이스 생성: 데이터베이스 테이블 자동 생성
  2. 간단한 데이터 조작: 삽입, 업데이트, 삭제 등
  3. 관계 매핑: 일대일, 일대다, 다대다 매핑 지원
  4. 데이터베이스 추상화: 여러 데이터베이스 지원

지원하는 데이터베이스

  • MySQL
  • PostgreSQL
  • SQLite
  • SQL Server
  • Oracle 등

TypeORM을 이용한 앱 개발

프로젝트 설정

  1. 프로젝트 폴더를 생성하고 package.json을 생성합니다.
npm init -y
  1. 필요한 모듈을 설치합니다.
npm install express morgan pg typeorm reflect-metadata --save
npm install typescript ts-node @types/node @types/express @types/morgan --save-dev

tsconfig.json 생성

TypeScript 설정 파일을 생성합니다.

npx tsc --init

ormconfig.json 생성

TypeORM 설정 파일을 생성하고 데이터베이스 연결 정보를 추가합니다.

{
    "type": "postgres",
    "host": "localhost",
    "port": 5432,
    "username": "postgres",
    "password": "password",
    "database": "typeorm_db",
    "synchronize": true,
    "logging": false,
    "entities": ["src/entity/**/*.ts"],
    "migrations": ["src/migration/**/*.ts"],
    "subscribers": ["src/subscriber/**/*.ts"],
    "cli": {
        "entitiesDir": "src/entity",
        "migrationsDir": "src/migration",
        "subscribersDir": "src/subscriber"
    }
}

index.ts 생성

src/index.ts 파일을 생성하고 Express 앱을 초기화합니다.

import 'reflect-metadata';
import express from 'express';
import morgan from 'morgan';
import { createConnection } from 'typeorm';

const app = express();
app.use(express.json());
app.use(morgan('dev'));

createConnection().then(async connection => {
    app.get('/', (req, res) => {
        res.send('Hello, TypeORM!');
    });

    const PORT = 3000;
    app.listen(PORT, () => {
        console.log(`Server running on http://localhost:${PORT}`);
    });
}).catch(error => console.log(error));

Entity 생성

src/entity/User.ts 파일을 생성하고 User 엔터티를 정의합니다.

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    firstName: string;

    @Column()
    lastName: string;

    @Column()
    isActive: boolean;
}

데이터베이스 연결 및 CRUD 구현

  1. src/data-source.ts에서 데이터베이스 연결을 초기화합니다.
import 'reflect-metadata';
import { DataSource } from 'typeorm';
import { User } from './entity/User';

export const AppDataSource = new DataSource({
    type: 'postgres',
    host: 'localhost',
    port: 5432,
    username: 'postgres',
    password: 'password',
    database: 'typeorm_db',
    synchronize: true,
    logging: false,
    entities: [User],
    migrations: [],
    subscribers: []
});
  1. src/index.ts에서 데이터베이스 연결을 초기화하고 CRUD API를 작성합니다.
import 'reflect-metadata';
import express from 'express';
import morgan from 'morgan';
import { AppDataSource } from './data-source';
import { User } from './entity/User';

const app = express();
app.use(express.json());
app.use(morgan('dev'));

AppDataSource.initialize().then(() => {
    const userRepository = AppDataSource.getRepository(User);

    // Create User
    app.post('/users', async (req, res) => {
        const { firstName, lastName, isActive } = req.body;
        const user = new User();
        user.firstName = firstName;
        user.lastName = lastName;
        user.isActive = isActive;

        await userRepository.save(user);
        res.status(201).json(user);
    });

    // Read Users
    app.get('/users', async (req, res) => {
        const users = await userRepository.find();
        res.json(users);
    });

    // Update User
    app.put('/users/:id', async (req, res) => {
        const { id } = req.params;
        const { firstName, lastName, isActive } = req.body;
        let user = await userRepository.findOneBy

({ id: parseInt(id) });

        if (user) {
            user.firstName = firstName;
            user.lastName = lastName;
            user.isActive = isActive;

            await userRepository.save(user);
            res.json(user);
        } else {
            res.status(404).json({ message: 'User not found' });
        }
    });

    // Delete User
    app.delete('/users/:id', async (req, res) => {
        const { id } = req.params;
        const user = await userRepository.findOneBy({ id: parseInt(id) });

        if (user) {
            await userRepository.remove(user);
            res.json({ message: 'User deleted' });
        } else {
            res.status(404).json({ message: 'User not found' });
        }
    });

    const PORT = 3000;
    app.listen(PORT, () => {
        console.log(`Server running on http://localhost:${PORT}`);
    });
}).catch(error => console.log(error));

마무리

이 글에서는 TypeORM을 사용하여 Node.js에서 데이터베이스와 객체 지향 프로그래밍을 효율적으로 연결하는 방법을 살펴보았습니다.

0개의 댓글