NestJS 데이터베이스 연동 NestJS는 다양한 데이터베이스와 ORM을 지원합니다. 가장 일반적으로 TypeORM, Prisma, Mongoose를 사용합니다.
TypeORM 설정 TypeORM은 TypeScript와 JavaScript를 위한 ORM으로, NestJS와 완벽하게 통합됩니다.
패키지 설치 npm install @nestjs/typeorm typeorm mysql2 npm install @nestjs/typeorm typeorm pg npm install @nestjs/typeorm typeorm sqlite3 npm install @nestjs/typeorm typeorm mariadb npm install @nestjs/typeorm typeorm mssql
기본 설정 app.module.ts import { Module } from '@nestjs/common' ;import { TypeOrmModule } from '@nestjs/typeorm' ;@Module ({ imports : [ TypeOrmModule .forRoot ({ type : 'mysql' , host : 'localhost' , port : 3306 , username : 'root' , password : 'password' , database : 'test' , entities : [__dirname + '/**/*.entity{.ts,.js}' ], synchronize : true , }), ], }) export class AppModule {}
환경 변수 사용 .env DB_TYPE=mysql DB_HOST=localhost DB_PORT=3306 DB_USERNAME=root DB_PASSWORD=password DB_DATABASE=test
app.module.ts (ConfigModule 사용) import { Module } from '@nestjs/common' ;import { TypeOrmModule } from '@nestjs/typeorm' ;import { ConfigModule , ConfigService } from '@nestjs/config' ;@Module ({ imports : [ ConfigModule .forRoot ({ isGlobal : true , }), TypeOrmModule .forRootAsync ({ imports : [ConfigModule ], useFactory : (configService: ConfigService ) => ({ type : configService.get ('DB_TYPE' ) as any , host : configService.get ('DB_HOST' ), port : configService.get <number >('DB_PORT' ), username : configService.get ('DB_USERNAME' ), password : configService.get ('DB_PASSWORD' ), database : configService.get ('DB_DATABASE' ), entities : [__dirname + '/**/*.entity{.ts,.js}' ], synchronize : configService.get ('NODE_ENV' ) !== 'production' , }), inject : [ConfigService ], }), ], }) export class AppModule {}
Entity 정의 user.entity.ts import { Entity , Column , PrimaryGeneratedColumn , CreateDateColumn , UpdateDateColumn , } from 'typeorm' ; @Entity ('users' )export class User { @PrimaryGeneratedColumn () id : number ; @Column ({ length : 100 }) name : string ; @Column ({ unique : true }) email : string ; @Column () password : string ; @Column ({ nullable : true }) age : number ; @Column ({ default : true }) isActive : boolean ; @CreateDateColumn () createdAt : Date ; @UpdateDateColumn () updatedAt : Date ; }
컬럼 옵션 @Entity ()export class Product { @PrimaryGeneratedColumn ('uuid' ) id : string ; @Column ({ type : 'varchar' , length : 200 }) name : string ; @Column ({ type : 'decimal' , precision : 10 , scale : 2 }) price : number ; @Column ({ type : 'text' , nullable : true }) description : string ; @Column ({ type : 'int' , default : 0 }) stock : number ; @Column ({ type : 'simple-array' }) tags : string []; @Column ({ type : 'json' , nullable : true }) metadata : object ; @Column ({ type : 'enum' , enum : ['draft' , 'published' , 'archived' ], default : 'draft' }) status : string ; }
관계(Relations) 설정 One-to-Many / Many-to-One import { Entity , PrimaryGeneratedColumn , Column , OneToMany } from 'typeorm' ;import { Post } from '../posts/post.entity' ;@Entity ()export class User { @PrimaryGeneratedColumn () id : number ; @Column () name : string ; @OneToMany (() => Post , post => post.user ) posts : Post []; } import { Entity , PrimaryGeneratedColumn , Column , ManyToOne } from 'typeorm' ;import { User } from '../users/user.entity' ;@Entity ()export class Post { @PrimaryGeneratedColumn () id : number ; @Column () title : string ; @Column ('text' ) content : string ; @ManyToOne (() => User , user => user.posts ) user : User ; }
Many-to-Many import { Entity , PrimaryGeneratedColumn , Column , ManyToMany , JoinTable } from 'typeorm' ;import { Tag } from '../tags/tag.entity' ;@Entity ()export class Post { @PrimaryGeneratedColumn () id : number ; @Column () title : string ; @ManyToMany (() => Tag , tag => tag.posts ) @JoinTable () tags : Tag []; } import { Entity , PrimaryGeneratedColumn , Column , ManyToMany } from 'typeorm' ;import { Post } from '../posts/post.entity' ;@Entity ()export class Tag { @PrimaryGeneratedColumn () id : number ; @Column () name : string ; @ManyToMany (() => Post , post => post.tags ) posts : Post []; }
One-to-One import { Entity , PrimaryGeneratedColumn , Column , OneToOne , JoinColumn } from 'typeorm' ;import { Profile } from '../profiles/profile.entity' ;@Entity ()export class User { @PrimaryGeneratedColumn () id : number ; @Column () email : string ; @OneToOne (() => Profile , profile => profile.user ) @JoinColumn () profile : Profile ; } import { Entity , PrimaryGeneratedColumn , Column , OneToOne } from 'typeorm' ;import { User } from '../users/user.entity' ;@Entity ()export class Profile { @PrimaryGeneratedColumn () id : number ; @Column () bio : string ; @Column ({ nullable : true }) avatar : string ; @OneToOne (() => User , user => user.profile ) user : User ; }
Repository 사용 모듈에 Entity 등록 import { Module } from '@nestjs/common' ;import { TypeOrmModule } from '@nestjs/typeorm' ;import { UsersController } from './users.controller' ;import { UsersService } from './users.service' ;import { User } from './user.entity' ;@Module ({ imports : [TypeOrmModule .forFeature ([User ])], controllers : [UsersController ], providers : [UsersService ], exports : [UsersService ], }) export class UsersModule {}
Service에서 Repository 사용 import { Injectable , NotFoundException } from '@nestjs/common' ;import { InjectRepository } from '@nestjs/typeorm' ;import { Repository } from 'typeorm' ;import { User } from './user.entity' ;import { CreateUserDto } from './dto/create-user.dto' ;import { UpdateUserDto } from './dto/update-user.dto' ;@Injectable ()export class UsersService { constructor ( @InjectRepository (User) private readonly usersRepository: Repository<User>, ) {} async findAll (): Promise <User []> { return this .usersRepository .find (); } async findOne (id : number ): Promise <User > { const user = await this .usersRepository .findOne ({ where : { id } }); if (!user) { throw new NotFoundException (`User with ID ${id} not found` ); } return user; } async create (createUserDto : CreateUserDto ): Promise <User > { const user = this .usersRepository .create (createUserDto); return this .usersRepository .save (user); } async update (id : number , updateUserDto : UpdateUserDto ): Promise <User > { await this .usersRepository .update (id, updateUserDto); return this .findOne (id); } async remove (id : number ): Promise <void > { const result = await this .usersRepository .delete (id); if (result.affected === 0 ) { throw new NotFoundException (`User with ID ${id} not found` ); } } }
고급 쿼리 조건 검색 const users = await this .usersRepository .find ({ where : { isActive : true }, }); const users = await this .usersRepository .find ({ where : { isActive : true , age : 25 }, }); const users = await this .usersRepository .find ({ where : [{ age : 25 }, { age : 30 }], }); const users = await this .usersRepository .find ({ select : ['id' , 'name' , 'email' ], }); const users = await this .usersRepository .find ({ order : { createdAt : 'DESC' }, }); const users = await this .usersRepository .find ({ skip : 0 , take : 10 , });
관계 로딩 const users = await this .usersRepository .find ({ relations : ['posts' , 'profile' ], }); const users = await this .usersRepository .find ({ relations : ['posts' , 'posts.tags' ], }); const user = await this .usersRepository .findOne ({ where : { id : 1 }, relations : ['posts' ], });
QueryBuilder 사용 const users = await this .usersRepository .createQueryBuilder ('user' ) .where ('user.age > :age' , { age : 18 }) .getMany (); const users = await this .usersRepository .createQueryBuilder ('user' ) .where ('user.age BETWEEN :min AND :max' , { min : 18 , max : 65 }) .andWhere ('user.isActive = :isActive' , { isActive : true }) .orderBy ('user.createdAt' , 'DESC' ) .take (10 ) .getMany (); const users = await this .usersRepository .createQueryBuilder ('user' ) .leftJoinAndSelect ('user.posts' , 'post' ) .where ('post.status = :status' , { status : 'published' }) .getMany (); const count = await this .usersRepository .createQueryBuilder ('user' ) .where ('user.age > :age' , { age : 18 }) .getCount (); const users = await this .usersRepository .query ( 'SELECT * FROM users WHERE age > ?' , [18 ], );
트랜잭션 QueryRunner 사용 import { DataSource } from 'typeorm' ;@Injectable ()export class UsersService { constructor ( @InjectRepository (User) private usersRepository: Repository<User>, private dataSource: DataSource, ) {} async createUserWithProfile (userData: any , profileData: any ) { const queryRunner = this .dataSource .createQueryRunner (); await queryRunner.connect (); await queryRunner.startTransaction (); try { const user = queryRunner.manager .create (User , userData); await queryRunner.manager .save (user); const profile = queryRunner.manager .create (Profile , { ...profileData, user, }); await queryRunner.manager .save (profile); await queryRunner.commitTransaction (); return user; } catch (err) { await queryRunner.rollbackTransaction (); throw err; } finally { await queryRunner.release (); } } }
Transaction 데코레이터 사용 import { Transaction , TransactionRepository } from 'typeorm' ;@Injectable ()export class UsersService { @Transaction () async createUserWithProfile ( @TransactionRepository (User) userRepository: Repository<User>, @TransactionRepository (Profile) profileRepository: Repository<Profile>, userData: any , profileData: any , ) { const user = await userRepository.save (userData); const profile = await profileRepository.save ({ ...profileData, user }); return { user, profile }; } }
MongoDB (Mongoose) 설정 패키지 설치 npm install @nestjs/mongoose mongoose
모듈 설정 import { Module } from '@nestjs/common' ;import { MongooseModule } from '@nestjs/mongoose' ;@Module ({ imports : [ MongooseModule .forRoot ('mongodb://localhost/nest' ), ], }) export class AppModule {}
Schema 정의 import { Prop , Schema , SchemaFactory } from '@nestjs/mongoose' ;import { Document } from 'mongoose' ;export type UserDocument = User & Document ;@Schema ()export class User { @Prop ({ required : true }) name : string ; @Prop ({ required : true , unique : true }) email : string ; @Prop () age : number ; @Prop ({ default : Date .now }) createdAt : Date ; } export const UserSchema = SchemaFactory .createForClass (User );
Service에서 사용 import { Injectable } from '@nestjs/common' ;import { InjectModel } from '@nestjs/mongoose' ;import { Model } from 'mongoose' ;import { User , UserDocument } from './user.schema' ;@Injectable ()export class UsersService { constructor ( @InjectModel (User.name) private userModel: Model<UserDocument>, ) {} async create (createUserDto : any ): Promise <User > { const createdUser = new this .userModel (createUserDto); return createdUser.save (); } async findAll (): Promise <User []> { return this .userModel .find ().exec (); } async findOne (id : string ): Promise <User > { return this .userModel .findById (id).exec (); } async update (id : string , updateUserDto : any ): Promise <User > { return this .userModel .findByIdAndUpdate (id, updateUserDto, { new : true }).exec (); } async remove (id : string ): Promise <User > { return this .userModel .findByIdAndRemove (id).exec (); } }
Prisma 설정 패키지 설치 npm install @prisma/client npm install -D prisma
Prisma 초기화
Schema 정의 (prisma/schema.prisma) datasource db { provider = "postgresql" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } model User { id Int @id @default(autoincrement()) email String @unique name String? posts Post[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Post { id Int @id @default(autoincrement()) title String content String? published Boolean @default(false) author User @relation(fields: [authorId], references: [id]) authorId Int createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }
Prisma Service import { Injectable , OnModuleInit } from '@nestjs/common' ;import { PrismaClient } from '@prisma/client' ;@Injectable ()export class PrismaService extends PrismaClient implements OnModuleInit { async onModuleInit ( ) { await this .$connect(); } }
사용 예제 import { Injectable } from '@nestjs/common' ;import { PrismaService } from '../prisma/prisma.service' ;@Injectable ()export class UsersService { constructor (private prisma: PrismaService ) {} async findAll ( ) { return this .prisma .user .findMany (); } async findOne (id: number ) { return this .prisma .user .findUnique ({ where : { id } }); } async create (data: any ) { return this .prisma .user .create ({ data }); } async update (id: number , data: any ) { return this .prisma .user .update ({ where : { id }, data }); } async remove (id: number ) { return this .prisma .user .delete ({ where : { id } }); } }
Migration TypeORM Migration npm run typeorm migration:create -- -n CreateUsersTable npm run typeorm migration:run npm run typeorm migration:revert
Prisma Migration npx prisma migrate dev --name init npx prisma migrate deploy npx prisma generate
정리
TypeORM: SQL 데이터베이스를 위한 강력한 ORM
Mongoose: MongoDB를 위한 ODM
Prisma: 현대적인 타입 안전 ORM
Entity/Schema로 데이터 모델 정의
Repository 패턴으로 데이터 액세스
관계(Relations)로 테이블 간 연결
QueryBuilder로 복잡한 쿼리 작성
Transaction으로 데이터 일관성 보장
다음 단계 데이터베이스 연결을 마스터했다면:
검증 파이프로 데이터 유효성 검사하기
미들웨어, 가드, 인터셉터 활용하기
JWT 인증 구현하기
API 문서화 (Swagger) 설정하기