NestJS์™€ TypeORM 2026: ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜, ๊ด€๊ณ„ ์„ค์ •, ๊ธฐ์ˆ  ๋ฉด์ ‘ ํ•ต์‹ฌ ์ •๋ฆฌ

NestJS์™€ TypeORM์„ ํ™œ์šฉํ•œ ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ ์‹ค์ „ ๊ฐ€์ด๋“œ. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ด€๋ฆฌ, ๊ด€๊ณ„ ๋ชจ๋ธ๋ง, ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•๊ณผ ๊ธฐ์ˆ  ๋ฉด์ ‘์—์„œ ์ž์ฃผ ์ถœ์ œ๋˜๋Š” ์งˆ๋ฌธ์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

NestJS์™€ TypeORM ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋ฐ ๊ด€๊ณ„ ์•„ํ‚คํ…์ฒ˜ ๋‹ค์ด์–ด๊ทธ๋žจ

2026๋…„ ํ˜„์žฌ, NestJS์™€ TypeORM์€ Node.js ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์—์„œ ๊ฐ€์žฅ ์•ˆ์ •์ ์ด๊ณ  ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ์กฐํ•ฉ ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. NestJS๋Š” ๋ชจ๋“ˆ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜์™€ ์˜์กด์„ฑ ์ฃผ์ž…์œผ๋กœ ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ๊ตฌ์กฐ๋ฅผ ์ œ๊ณตํ•˜๊ณ , TypeORM์€ TypeScript์˜ ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ณ„์ธต๊นŒ์ง€ ํ™•์žฅํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์šด์˜, ๋ณต์žกํ•œ ๊ด€๊ณ„ ์„ค๊ณ„, ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ ๋“ฑ ์‹ค๋ฌด์—์„œ ๋งˆ์ฃผํ•˜๋Š” ๊ณผ์ œ๋Š” ์ ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ด ๊ธ€์—์„œ๋Š” TypeORM ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ „๋žต, ์ฃผ์š” ๊ด€๊ณ„ ํŒจํ„ด, ํŠธ๋žœ์žญ์…˜ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•์„ ๊ตฌ์ฒด์ ์ธ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ๊ธฐ์ˆ  ๋ฉด์ ‘์—์„œ NestJS์™€ TypeORM์— ๋Œ€ํ•ด ์ž์ฃผ ์ถœ์ œ๋˜๋Š” ์งˆ๋ฌธ๊ณผ ๋ชจ๋ฒ” ๋‹ต๋ณ€๋„ ํ•จ๊ป˜ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

TypeORM DataSource API ์•ˆ๋‚ด

TypeORM์€ ๊ธฐ์กด์˜ ormconfig.json์ด๋‚˜ createConnection ๋ฐฉ์‹์„ ๋Œ€์ฒดํ•˜๋Š” DataSource API๋ฅผ ํ‘œ์ค€์œผ๋กœ ์ฑ„ํƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ธ€์˜ ๋ชจ๋“  ์ฝ”๋“œ ์˜ˆ์ œ๋Š” ํ˜„์žฌ DataSource API๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค. ๋ ˆ๊ฑฐ์‹œ ์„ค์ • ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋Š” ํ”„๋กœ์ ํŠธ๋Š” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

NestJS์—์„œ TypeORM ์„ค์ •ํ•˜๊ธฐ

์ฒซ ๋ฒˆ์งธ ๋‹จ๊ณ„๋Š” TypeORM CLI๊ฐ€ ์‚ฌ์šฉํ•  ๋…๋ฆฝ์ ์ธ DataSource ์„ค์ • ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด ํŒŒ์ผ์„ NestJS ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ์™€ ๋ถ„๋ฆฌํ•˜๋ฉด CLI ๋„๊ตฌ๊ฐ€ ๋…๋ฆฝ์ ์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

src/config/typeorm.config.tstypescript
import { DataSource } from 'typeorm';
import { config } from 'dotenv';

config(); // Load .env variables

export default new DataSource({
  type: 'postgres',
  host: process.env.DB_HOST,
  port: parseInt(process.env.DB_PORT || '5432'),
  username: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  entities: ['src/**/*.entity.ts'],
  migrations: ['src/migrations/*.ts'],
  synchronize: false,
});

synchronize: false ์„ค์ •์€ ๋งค์šฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋Š” synchronize: true๋กœ ์Šคํ‚ค๋งˆ๋ฅผ ์ž๋™ ๋™๊ธฐํ™”ํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์ง€๋งŒ, ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์ด๋ฅผ ํ™œ์„ฑํ™”ํ•˜๋ฉด ์ปฌ๋Ÿผ ์‚ญ์ œ, ๋ฐ์ดํ„ฐ ํƒ€์ž… ๋ณ€๊ฒฝ ๋“ฑ์ด ์˜ˆ๊ณ  ์—†์ด ์‹คํ–‰๋˜์–ด ๋ฐ์ดํ„ฐ ์†์‹ค๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ”„๋กœ๋•์…˜์—์„œ๋Š” ๋ฐ˜๋“œ์‹œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

AppModule์—์„œ๋Š” ConfigService๋ฅผ ํ†ตํ•ด ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์ฃผ์ž…ํ•˜๊ณ  TypeORM์„ ๋น„๋™๊ธฐ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

src/app.module.tstypescript
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    TypeOrmModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        type: 'postgres',
        host: config.get('DB_HOST'),
        port: config.getOrThrow<number>('DB_PORT'),
        username: config.get('DB_USER'),
        password: config.get('DB_PASSWORD'),
        database: config.get('DB_NAME'),
        autoLoadEntities: true, // Auto-register entities from feature modules
        synchronize: false,     // Always false in production
      }),
    }),
  ],
})
export class AppModule {}

config.get()๊ณผ config.getOrThrow()์˜ ์ฐจ์ด์— ์ฃผ๋ชฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. getOrThrow()๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ์ •์˜๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํฌํŠธ์™€ ๊ฐ™์€ ํ•„์ˆ˜ ์„ค์ • ๊ฐ’์— ์ด ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์„ค์ • ์˜ค๋ฅ˜๋ฅผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹œ์ ์— ์ฆ‰์‹œ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜: ์ƒ์„ฑ, ์‹คํ–‰, ๋กค๋ฐฑ

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์€ ์ „๋ฌธ์ ์ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ด€๋ฆฌ์˜ ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค. ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ ์ด๋ ฅ์„ ๋ฒ„์ „ ๊ด€๋ฆฌํ•˜๊ณ  ์žฌํ˜„ ๊ฐ€๋Šฅํ•œ ๋ฐฉ์‹์œผ๋กœ ์ ์šฉํ•˜๊ฑฐ๋‚˜ ๋˜๋Œ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ npm ์Šคํฌ๋ฆฝํŠธ๋ฅผ package.json์— ์ •์˜ํ•˜๋ฉด TypeORM CLI ์ž‘์—…์ด ๊ฐ„์†Œํ™”๋ฉ๋‹ˆ๋‹ค.

package.json (scripts section)json
{
  "migration:generate": "typeorm-ts-node-commonjs migration:generate src/migrations/$npm_config_name -d src/config/typeorm.config.ts",
  "migration:run": "typeorm-ts-node-commonjs migration:run -d src/config/typeorm.config.ts",
  "migration:revert": "typeorm-ts-node-commonjs migration:revert -d src/config/typeorm.config.ts"
}

์‹ค์ œ ์›Œํฌํ”Œ๋กœ์šฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๋จผ์ € Entity๋ฅผ ์ˆ˜์ •ํ•œ ํ›„ npm run migration:generate --name=AddUserTable์„ ์‹คํ–‰ํ•˜๋ฉด ํ˜„์žฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ์™€ Entity ์ •์˜ ๊ฐ„์˜ ์ฐจ์ด๋ฅผ ๊ฐ์ง€ํ•˜์—ฌ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์ด ์ž๋™ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. npm run migration:run์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ ์šฉํ•˜๊ณ , npm run migration:revert๋กœ ๊ฐ€์žฅ ์ตœ๊ทผ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ๋˜๋Œ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ์—์„œ ํ”ํžˆ ๋ฐœ์ƒํ•˜๋Š” ์‹ค์ˆ˜๋Š” migration:generate๊ฐ€ ์ž๋™์œผ๋กœ ์ฐจ์ด๋ฅผ ๊ฐ์ง€ํ•ด์ฃผ๋Š”๋ฐ๋„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ˆ˜๋™์œผ๋กœ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ˆ˜๋™ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ๋Š” ์ปฌ๋Ÿผ๋ช… ๋ณ€๊ฒฝ์— ๋”ฐ๋ฅธ ๋ฐ์ดํ„ฐ ์ด์ „์ฒ˜๋Ÿผ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ๋กœ์ง์ด ํฌํ•จ๋  ๋•Œ์— ํ•œํ•ฉ๋‹ˆ๋‹ค.

์ƒ์„ฑ๋œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋ฐ˜๋“œ์‹œ ํ™•์ธ

์ปฌ๋Ÿผ๋ช… ๋ณ€๊ฒฝ์€ ALTER RENAME ๋Œ€์‹  DROP + ADD๋กœ ์ƒ์„ฑ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ๊ธฐ์กด ๋ฐ์ดํ„ฐ๊ฐ€ ์†Œ์‹ค๋ฉ๋‹ˆ๋‹ค. up() ๋ฉ”์„œ๋“œ์˜ SQL ๋ฌธ์„ ๋ฐ˜๋“œ์‹œ ํ™•์ธํ•˜๊ณ , ํ•„์š”ํ•˜๋ฉด ์ˆ˜๋™์œผ๋กœ ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Entity์™€ ๊ด€๊ณ„ ๋ชจ๋ธ๋ง

TypeORM์€ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ TypeScript ํด๋ž˜์Šค์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ตฌ์กฐ๋ฅผ ๋งคํ•‘ํ•ฉ๋‹ˆ๋‹ค. ์ฃผ์š” ๊ด€๊ณ„ ์œ ํ˜•์€ OneToMany/ManyToOne, ManyToMany, OneToOne์˜ ์„ธ ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค.

OneToMany์™€ ManyToOne

์‚ฌ์šฉ์ž์™€ ์ฃผ๋ฌธ์˜ ๊ด€๊ณ„๋Š” @OneToMany์™€ @ManyToOne์˜ ๋Œ€ํ‘œ์ ์ธ ์‚ฌ์šฉ ์‚ฌ๋ก€์ž…๋‹ˆ๋‹ค.

src/entities/user.entity.tstypescript
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, CreateDateColumn } from 'typeorm';
import { Order } from './order.entity';

@Entity('users')
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ unique: true })
  email: string;

  @Column()
  name: string;

  @OneToMany(() => Order, (order) => order.user)
  orders: Order[];  // No DB column created here; relation lives on the Order side

  @CreateDateColumn()
  createdAt: Date;
}
src/entities/order.entity.tstypescript
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
import { User } from './user.entity';

@Entity('orders')
export class Order {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column('decimal', { precision: 10, scale: 2 })
  total: number;

  @Column({ default: 'pending' })
  status: string;

  @ManyToOne(() => User, (user) => user.orders, { onDelete: 'CASCADE' })
  @JoinColumn({ name: 'user_id' })  // Explicit FK column name
  user: User;
}

์—ฌ๊ธฐ์„œ ํ•ต์‹ฌ์€ @OneToMany ์ชฝ์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ปฌ๋Ÿผ์„ ์ƒ์„ฑํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ์™ธ๋ž˜ ํ‚ค๋Š” ํ•ญ์ƒ @ManyToOne ์ชฝ์— ์œ„์น˜ํ•ฉ๋‹ˆ๋‹ค. @JoinColumn ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋กœ ์™ธ๋ž˜ ํ‚ค ์ปฌ๋Ÿผ๋ช…์„ ๋ช…์‹œ์ ์œผ๋กœ ์ง€์ •ํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๊ฐ€๋…์„ฑ์ด ํ–ฅ์ƒ๋˜๊ณ  ๋„ค์ด๋ฐ ์ถฉ๋Œ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. onDelete: 'CASCADE' ์˜ต์…˜์€ ์‚ฌ์šฉ์ž๊ฐ€ ์‚ญ์ œ๋  ๋•Œ ๊ด€๋ จ ์ฃผ๋ฌธ๋„ ์ž๋™์œผ๋กœ ์‚ญ์ œ๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

ManyToMany

์ƒํ’ˆ๊ณผ ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๊ด€๊ณ„์—๋Š” ManyToMany ๊ด€๊ณ„๊ฐ€ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. TypeORM์€ ์ค‘๊ฐ„ ํ…Œ์ด๋ธ”์„ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

src/entities/product.entity.tstypescript
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable } from 'typeorm';
import { Category } from './category.entity';

@Entity('products')
export class Product {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  name: string;

  @Column('decimal', { precision: 10, scale: 2 })
  price: number;

  @ManyToMany(() => Category, (category) => category.products)
  @JoinTable({ name: 'product_categories' })  // Owns the junction table
  categories: Category[];
}

@JoinTable ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” ๊ด€๊ณ„์˜ ์†Œ์œ  ์ธก์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ํ•œ์ชฝ์—๋งŒ ์ด ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ๋ฐฐ์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ค‘๊ฐ„ ํ…Œ์ด๋ธ”์— ์ •๋ ฌ ์ˆœ์„œ ๋“ฑ ์ถ”๊ฐ€ ์ปฌ๋Ÿผ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ, ๋ณ„๋„์˜ Entity๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋‘ ๊ฐœ์˜ @ManyToOne ๊ด€๊ณ„๋กœ ๊ตฌ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Node.js / NestJS ๋ฉด์ ‘ ์ค€๋น„๊ฐ€ ๋˜์…จ๋‚˜์š”?

์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ, flashcards, ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์—ฐ์Šตํ•˜์„ธ์š”.

Repository ํŒจํ„ด๊ณผ Feature Module

NestJS๋Š” Feature Module์„ ํ†ตํ•œ ๋ชจ๋“ˆํ™”๋ฅผ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ๋ชจ๋“ˆ์€ TypeOrmModule.forFeature()๋กœ Entity๋ฅผ ๋“ฑ๋กํ•˜๊ณ , ํ•ด๋‹น Repository์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

src/orders/orders.module.tstypescript
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Order } from '../entities/order.entity';
import { OrdersService } from './orders.service';
import { OrdersController } from './orders.controller';

@Module({
  imports: [TypeOrmModule.forFeature([Order])],
  providers: [OrdersService],
  controllers: [OrdersController],
})
export class OrdersModule {}

Service์—์„œ๋Š” @InjectRepository๋ฅผ ํ†ตํ•ด Repository๊ฐ€ ์ฃผ์ž…๋ฉ๋‹ˆ๋‹ค. ๋‹จ์ˆœํ•œ ์ฟผ๋ฆฌ์—๋Š” Repository API๊ฐ€ ์ ํ•ฉํ•˜๊ณ , ๋ณต์žกํ•œ ์ฟผ๋ฆฌ์—๋Š” QueryBuilder๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

src/orders/orders.service.tstypescript
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Order } from '../entities/order.entity';

@Injectable()
export class OrdersService {
  constructor(
    @InjectRepository(Order)
    private readonly orderRepo: Repository<Order>,
  ) {}

  async findByUser(userId: string): Promise<Order[]> {
    return this.orderRepo.find({
      where: { user: { id: userId } },
      relations: ['user'],          // Eager-load the user relation
      order: { createdAt: 'DESC' }, // Most recent first
    });
  }

  async findWithFilters(status: string, minTotal: number): Promise<Order[]> {
    // QueryBuilder for complex queries
    return this.orderRepo
      .createQueryBuilder('order')
      .leftJoinAndSelect('order.user', 'user')
      .where('order.status = :status', { status })
      .andWhere('order.total >= :minTotal', { minTotal })
      .orderBy('order.total', 'DESC')
      .getMany();
  }
}

findByUser ๋ฉ”์„œ๋“œ๋Š” ์ค‘์ฒฉ๋œ Where ์กฐ๊ฑด์œผ๋กœ ๊ด€๊ณ„๋ฅผ ํ•„ํ„ฐ๋งํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. relations ๋ฐฐ์—ด์€ ์–ด๋–ค ๊ด€๊ณ„๋ฅผ ๋กœ๋“œํ• ์ง€ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. ์ด ์„ค์ •์ด ์—†์œผ๋ฉด TypeORM์€ ์ฃผ๋ฌธ ๋ฐ์ดํ„ฐ๋งŒ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์‚ฌ์šฉ์ž ์ •๋ณด๋Š” ํฌํ•จํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

findWithFilters์˜ QueryBuilder๋Š” ๋” ์„ธ๋ฐ€ํ•œ ์ œ์–ด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. leftJoinAndSelect๋Š” ๊ด€๊ณ„๋ฅผ ๋กœ๋“œํ•˜์—ฌ ๊ฒฐ๊ณผ์— ํฌํ•จ์‹œํ‚ค๊ณ , ๋งค๊ฐœ๋ณ€์ˆ˜ํ™”๋œ ์ฟผ๋ฆฌ๋ฅผ ํ†ตํ•ด SQL ์ธ์ ์…˜์„ ํ™•์‹คํ•˜๊ฒŒ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

ํŠธ๋žœ์žญ์…˜์˜ ์˜ฌ๋ฐ”๋ฅธ ๊ตฌํ˜„

์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž‘์—…์„ ์›์ž์ ์œผ๋กœ ์‹คํ–‰ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ, ํŠธ๋žœ์žญ์…˜์€ ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค. TypeORM์€ DataSource.transaction() ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

src/orders/orders.service.ts (additional method)typescript
async createOrderWithItems(
  userId: string,
  items: { productId: string; quantity: number }[],
  dataSource: DataSource,
): Promise<Order> {
  return dataSource.transaction(async (manager) => {
    // All operations use the transactional manager
    const order = manager.create(Order, {
      user: { id: userId },
      total: 0,
      status: 'pending',
    });
    const savedOrder = await manager.save(order);

    let total = 0;
    for (const item of items) {
      const product = await manager.findOneByOrFail(Product, { id: item.productId });
      total += product.price * item.quantity;

      await manager.save(OrderItem, {
        order: savedOrder,
        product,
        quantity: item.quantity,
        unitPrice: product.price,
      });
    }

    savedOrder.total = total;
    return manager.save(savedOrder);
  });
}

ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ๋Š” ๋ชจ๋“  ์ž‘์—…์— ๋ฐ˜๋“œ์‹œ manager๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜ Repository๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•ด๋‹น ์ž‘์—…์€ ํŠธ๋žœ์žญ์…˜ ์™ธ๋ถ€์—์„œ ์‹คํ–‰๋˜๋ฏ€๋กœ, ์ดํ›„ ๋‹จ๊ณ„์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ด๋„ ๋กค๋ฐฑ๋˜์ง€ ์•Š์•„ ๋ฐ์ดํ„ฐ ๋ถˆ์ผ์น˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ฝœ๋ฐฑ ๋‚ด์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ „์ฒด ํŠธ๋žœ์žญ์…˜์ด ์ž๋™์œผ๋กœ ๋กค๋ฐฑ๋ฉ๋‹ˆ๋‹ค.

QueryRunner์™€ transaction()์˜ ์ฐจ์ด

DataSource.transaction()์€ ๋‹จ์ˆœํ•œ ๋‹ค๋‹จ๊ณ„ ์ž‘์—…์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. QueryRunner๋Š” ์ปค๋ฐ‹/๋กค๋ฐฑ ์‹œ์ ๊ณผ ์„ธ์ด๋ธŒํฌ์ธํŠธ๋ฅผ ์ˆ˜๋™์œผ๋กœ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์–ด, ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ์—์„œ์˜ ๋ถ€๋ถ„ ๋กค๋ฐฑ ๋“ฑ ๋” ์ •๋ฐ€ํ•œ ์ œ์–ด๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

NestJS์™€ TypeORM ๊ธฐ์ˆ  ๋ฉด์ ‘ ํ•ต์‹ฌ ์งˆ๋ฌธ

๊ธฐ์ˆ  ๋ฉด์ ‘์—์„œ NestJS์™€ TypeORM์— ๊ด€ํ•œ ์งˆ๋ฌธ์€ ์•„ํ‚คํ…์ฒ˜ ์ดํ•ด, ์‹ค๋ฌด ๋ฌธ์ œ ํ•ด๊ฒฐ ๋Šฅ๋ ฅ, ๊ทธ๋ฆฌ๊ณ  ํ”ํ•œ ์‹ค์ˆ˜์— ๋Œ€ํ•œ ์ธ์‹์ด๋ผ๋Š” ์„ธ ๊ฐ€์ง€ ์˜์—ญ์œผ๋กœ ๋‚˜๋‰ฉ๋‹ˆ๋‹ค.

ํ”„๋กœ๋•์…˜์—์„œ synchronize๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•˜๋Š” ์ด์œ ๋Š” ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

synchronize: true๋กœ ์„ค์ •ํ•˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹œ Entity ์ •์˜์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ๊ฐ€ ์ž๋™์œผ๋กœ ๋™๊ธฐํ™”๋ฉ๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์—์„œ ์ปฌ๋Ÿผ ์‚ญ์ œ, ๋ฐ์ดํ„ฐ ํƒ€์ž… ๋ณ€๊ฒฝ, ํ…Œ์ด๋ธ” ์‚ญ์ œ๊ฐ€ ๊ธฐ๋ก ์—†์ด ์ˆ˜ํ–‰๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ฐ์ดํ„ฐ ์†์‹ค ์œ„ํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์ด ๋ฒ„์ „ ๊ด€๋ฆฌ๋˜๊ณ , ์žฌํ˜„ ๊ฐ€๋Šฅํ•˜๋ฉฐ, ๋˜๋Œ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

migration:generate์™€ migration:create์˜ ์ฐจ์ด์ ์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

migration:generate๋Š” ํ˜„์žฌ Entity ์ •์˜์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ๋ฅผ ๋น„๊ตํ•˜์—ฌ ์ฐจ์ด๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ํ•„์š”ํ•œ SQL ๋ฌธ์„ ์ž๋™ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด migration:create๋Š” ๋นˆ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๋ฉฐ, SQL ๋ฌธ์€ ์ˆ˜๋™์œผ๋กœ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. ํ›„์ž๋Š” ๋ฐ์ดํ„ฐ ์ด์ „์ด๋‚˜ ์‹œ๋“œ ๋ฐ์ดํ„ฐ ์‚ฝ์ž… ๋“ฑ ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ ์ด์™ธ์˜ ์ž‘์—…์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

OneToMany/ManyToOne ๊ด€๊ณ„์—์„œ ์™ธ๋ž˜ ํ‚ค๋Š” ์–ด๋А ์ชฝ์— ์œ„์น˜ํ•ฉ๋‹ˆ๊นŒ?

์™ธ๋ž˜ ํ‚ค๋Š” ํ•ญ์ƒ @ManyToOne ์ชฝ์— ์œ„์น˜ํ•ฉ๋‹ˆ๋‹ค. @OneToMany ์ชฝ์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ปฌ๋Ÿผ์„ ์ƒ์„ฑํ•˜์ง€ ์•Š์œผ๋ฉฐ, TypeScript์—์„œ ์—ญ๋ฐฉํ–ฅ ์ฐธ์กฐ๋ฅผ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•œ ์šฉ๋„๋กœ๋งŒ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

Repository API์™€ QueryBuilder๋Š” ์–ด๋–ป๊ฒŒ ๊ตฌ๋ถ„ํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๊นŒ?

Repository API(find, findOne, save)๋Š” ๋‹จ์ˆœํ•œ CRUD ์ž‘์—…๊ณผ ์ง์ ‘์ ์ธ ํ•„ํ„ฐ ์กฐ๊ฑด์˜ ์ฟผ๋ฆฌ์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. QueryBuilder๋Š” ๋ณต์žกํ•œ JOIN, ์„œ๋ธŒ์ฟผ๋ฆฌ, ์ง‘๊ณ„ ํ•จ์ˆ˜, ๋™์ ์œผ๋กœ ๊ตฌ์„ฑ๋˜๋Š” ์ฟผ๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๋ชจ๋“ˆ ๊ฐ„ ์ˆœํ™˜ ์˜์กด์„ฑ์€ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๊นŒ?

NestJS๋Š” ์ˆœํ™˜ ์˜์กด์„ฑ ํ•ด๊ฒฐ์„ ์œ„ํ•ด forwardRef()๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด ํ•จ์ˆ˜์˜ ์‚ฌ์šฉ์€ ์•„ํ‚คํ…์ฒ˜ ๋ฌธ์ œ๋ฅผ ์‹œ์‚ฌํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. ๊ณตํ†ต ๋กœ์ง์„ ๋…๋ฆฝ์ ์ธ ๋ชจ๋“ˆ๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๋” ๋ฐ”๋žŒ์งํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ์ผ๋ฐ˜ Repository๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ฉ๋‹ˆ๊นŒ?

์ผ๋ฐ˜ Repository๋ฅผ ํ†ตํ•œ ์ž‘์—…์€ ํŠธ๋žœ์žญ์…˜ ์™ธ๋ถ€์—์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ๋ณ€๊ฒฝ ์‚ฌํ•ญ์€ ์ฆ‰์‹œ ์ปค๋ฐ‹๋˜๋ฉฐ, ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ์—๋„ ๋กค๋ฐฑ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋ฐ์ดํ„ฐ ๋ถˆ์ผ์น˜์˜ ์›์ธ์ด ๋ฉ๋‹ˆ๋‹ค.

Node.js / NestJS ๋ฉด์ ‘ ์ค€๋น„๊ฐ€ ๋˜์…จ๋‚˜์š”?

์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ, flashcards, ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์—ฐ์Šตํ•˜์„ธ์š”.

๊ฒฐ๋ก 

NestJS์™€ TypeORM์˜ ์กฐํ•ฉ์€ ํƒ€์ž… ์•ˆ์ „ํ•œ Node.js ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์„ ์œ„ํ•œ ์„ฑ์ˆ™ํ•œ ์ƒํƒœ๊ณ„๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ธ€์˜ ํ•ต์‹ฌ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

  • ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์€ ๋กœ์ปฌ ๊ฐœ๋ฐœ์„ ์ œ์™ธํ•œ ๋ชจ๋“  ํ™˜๊ฒฝ์—์„œ synchronize๋ฅผ ๋Œ€์ฒดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • ๊ด€๊ณ„ ์„ค๊ณ„์—์„œ๋Š” ์–ด๋А ์ชฝ์ด ์™ธ๋ž˜ ํ‚ค๋ฅผ ๋ณด์œ ํ•˜๋Š”์ง€ ์ •ํ™•ํžˆ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์ด ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค
  • ํŠธ๋žœ์žญ์…˜์—์„œ๋Š” ์ผ๋ฐ˜ Repository๊ฐ€ ์•„๋‹Œ ๋ฐ˜๋“œ์‹œ Manager๋ฅผ ํ†ตํ•ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • QueryBuilder๋Š” Repository API๋กœ ๋Œ€์‘ํ•  ์ˆ˜ ์—†๋Š” ๋ณต์žกํ•œ ์ฟผ๋ฆฌ๋ฅผ ์œ„ํ•œ ๋ณด์™„ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค
  • ๊ธฐ์ˆ  ๋ฉด์ ‘์—์„œ๋Š” ๋ฌธ๋ฒ• ์ง€์‹๋ฟ ์•„๋‹ˆ๋ผ, ์•„ํ‚คํ…์ฒ˜ ๊ฒฐ์ •์˜ ๊ทผ๊ฑฐ๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋Š” ๋Šฅ๋ ฅ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค

์—ฐ์Šต์„ ์‹œ์ž‘ํ•˜์„ธ์š”!

๋ฉด์ ‘ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์™€ ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์ง€์‹์„ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

๊ณต์œ 

๊ด€๋ จ ๊ธฐ์‚ฌ

2026๋…„ NestJS์™€ GraphQL ์™„๋ฒฝ ๊ฐ€์ด๋“œ: ์Šคํ‚ค๋งˆ ์„ค๊ณ„, ๋ฆฌ์กธ๋ฒ„ ๊ตฌํ˜„, ๋ฉด์ ‘ ๋Œ€๋น„

2026๋…„ NestJS์™€ GraphQL ์™„๋ฒฝ ๊ฐ€์ด๋“œ: ์Šคํ‚ค๋งˆ ์„ค๊ณ„, ๋ฆฌ์กธ๋ฒ„ ๊ตฌํ˜„, ๋ฉด์ ‘ ๋Œ€๋น„

NestJS 11์—์„œ GraphQL ํ†ตํ•ฉ์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค. ์ฝ”๋“œ ํผ์ŠคํŠธ์™€ ์Šคํ‚ค๋งˆ ํผ์ŠคํŠธ ์„ค๊ณ„ ๋ฐฉ์‹, ๋ฆฌ์กธ๋ฒ„ ํŒจํ„ด, DataLoader๋ฅผ ํ™œ์šฉํ•œ N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ, ์ธ์ฆ ๋ฐ ์ธ๊ฐ€ ๊ตฌํ˜„์„ ํ•™์Šตํ•ฉ๋‹ˆ๋‹ค.

Node.js 24 URLPattern ํผ๋ฏธ์…˜ ๋ชจ๋ธ ๋ฉด์ ‘ ๋Œ€๋น„ 2026

Node.js 24 ํ•ต์‹ฌ ๊ธฐ๋Šฅ ์™„์ „ ๋ถ„์„: URLPattern, ํผ๋ฏธ์…˜ ๋ชจ๋ธ, ๋ฉด์ ‘ ๋Œ€๋น„ ๊ฐ€์ด๋“œ (2026๋…„ํŒ)

Node.js 24 LTS(Krypton)์˜ ์ฃผ์š” ์‹ ๊ธฐ๋Šฅ์ธ URLPattern, ํผ๋ฏธ์…˜ ๋ชจ๋ธ, ๋ช…์‹œ์  ๋ฆฌ์†Œ์Šค ๊ด€๋ฆฌ๋ฅผ ์‹ค์ „ ์ฝ”๋“œ ์˜ˆ์ œ์™€ ๋ฉด์ ‘ ๋Œ€๋น„ ์งˆ๋ฌธ์œผ๋กœ ์ƒ์„ธํ•˜๊ฒŒ ๋ถ„์„ํ•œ๋‹ค.

NestJS ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์™€ gRPC ๊ตฌ์„ฑ ๊ฐ€์ด๋“œ

2026๋…„ NestJS ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค: ์•„ํ‚คํ…์ฒ˜, gRPC, ๋ฉด์ ‘ ์งˆ๋ฌธ ์™„๋ฒฝ ๊ฐ€์ด๋“œ

NestJS ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์˜ ํ•ต์‹ฌ ๊ฐœ๋…, gRPC ํŠธ๋žœ์ŠคํฌํŠธ ๊ตฌ์„ฑ, ์ŠคํŠธ๋ฆฌ๋ฐ ํŒจํ„ด, ์•ˆ์ •์„ฑ ํŒจํ„ด, ๋ฉด์ ‘ ๋นˆ์ถœ ์งˆ๋ฌธ์„ ์‹ค๋ฌด ์ค‘์‹ฌ์œผ๋กœ ๋‹ค๋ฃน๋‹ˆ๋‹ค.