CRUD

CRUD

This chapter applies only to TypeScript

CRUD ํŒจํ‚ค์ง€ (@nestjsx/crud)๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด CRUD ์ปจํŠธ๋กค๋Ÿฌ ๋ฐ ์„œ๋น„์Šค๋ฅผ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์œผ๋ฉฐ RESTful API๋ฅผ ์œ„ํ•œ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ฆ‰์‹œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๊ด€๊ณ„์—†์ด ํ™•์žฅ ๊ฐ€๋Šฅํ•œ CRUD ์ปจํŠธ๋กค๋Ÿฌ

  • ํ•„ํ„ฐ๋ง, ํŽ˜์ด์ง€ ๋งค๊น€, ์ •๋ ฌ, ๊ด€๊ณ„, ์ค‘์ฒฉ ๊ด€๊ณ„, ์บ์‹œ ๋“ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ ๋ฌธ์ž์—ด ๊ตฌ๋ฌธ ๋ถ„์„

  • ํ”„๋ก ํŠธ ์—”๋“œ ์‚ฌ์šฉ์„ ์œ„ํ•œ ์ฟผ๋ฆฌ ๋นŒ๋”๊ฐ€ ์žˆ๋Š” ํ”„๋ ˆ์ž„ ์›Œํฌ์— ๊ตฌ์• ๋ฐ›์ง€ ์•Š๋Š” ํŒจํ‚ค์ง€

  • ์ฟผ๋ฆฌ, ๊ฒฝ๋กœ ๋งค๊ฐœ ๋ณ€์ˆ˜ ๋ฐ DTO ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ

  • ์ปจํŠธ๋กค๋Ÿฌ ๋ฉ”์†Œ๋“œ๋ฅผ ์‰ฝ๊ฒŒ ์žฌ์ •์˜

  • ์ž‘์ง€๋งŒ ๊ฐ•๋ ฅํ•œ ๊ตฌ์„ฑ (๊ธ€๋กœ๋ฒŒ ๊ตฌ์„ฑ ํฌํ•จ)

  • ์ถ”๊ฐ€ ํ—ฌํผ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ

  • Swagger ๋‹คํ๋จผํŠธ

warning ์•Œ๋ฆผ ์ง€๊ธˆ๊นŒ์ง€@nestjsx/crud๋Š” TypeORM ๋งŒ ์ง€์›ํ•˜์ง€๋งŒ Sequelize ๋ฐ Mongoose์™€ ๊ฐ™์€ ๋‹ค๋ฅธ ORM๋„ ๊ฐ€๊นŒ์šด ์‹œ์ผ ๋‚ด์— ํฌํ•จ๋  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด ๊ธฐ์‚ฌ์—์„œ๋Š” TypeORM์„ ์‚ฌ์šฉํ•˜์—ฌ CRUD ์ปจํŠธ๋กค๋Ÿฌ ๋ฐ ์„œ๋น„์Šค๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. @nestjs/typeorm ํŒจํ‚ค์ง€๋ฅผ ์ด๋ฏธ ์„ฑ๊ณต์ ์œผ๋กœ ์„ค์น˜ํ•˜๊ณ  ์„ค์ •ํ–ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์—ฌ๊ธฐ๋ฅผ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค.

Getting started

CRUD ๊ธฐ๋Šฅ ์ƒ์„ฑ์„ ์‹œ์ž‘ํ•˜๋ ค๋ฉด ํ•„์š”ํ•œ ๋ชจ๋“  ์ข…์†์„ฑ์„ ์„ค์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

npm i --save @nestjsx/crud @nestjsx/crud-typeorm class-transformer class-validator

ํ”„๋กœ์ ํŠธ์— ์ด๋ฏธ ์ผ๋ถ€ ์—”ํ„ฐํ‹ฐ๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค.

@@filename(hero.entity)
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

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

  @Column()
  name: string;

  @Column({ type: 'number' })
  power: number;
}

์šฐ๋ฆฌ๊ฐ€ ํ•ด์•ผ ํ•  ์ฒซ๋ฒˆ์งธ ๋‹จ๊ณ„๋Š” service๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

@@filename(heroes.service)
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { TypeOrmCrudService } from '@nestjsx/crud-typeorm';
import { Hero } from './hero.entity';

@Injectable()
export class HeroesService extends TypeOrmCrudService<Hero> {
  constructor(@InjectRepository(Hero) repo) {
    super(repo);
  }
}

์„œ๋น„์Šค๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์œผ๋ฏ€๋กœ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ๋งŒ๋“ค์–ด ๋ด…์‹œ๋‹ค :

@@filename(heroes.controller)
import { Controller } from '@nestjs/common';
import { Crud } from '@nestjsx/crud';
import { Hero } from './hero.entity';
import { HeroesService } from './heroes.service';

@Crud({
  model: {
    type: Hero,
  },
})
@Controller('heroes')
export class HeroesController {
  constructor(public service: HeroesService) {}
}

๋งˆ์ง€๋ง‰์œผ๋กœ ๋ชจ๋“ˆ์— ๋ชจ๋“  ๊ฒƒ์„ ์—ฐ๊ฒฐํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.

@@filename(heroes.module)
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

import { Hero } from './hero.entity';
import { HeroesService } from './heroes.service';
import { HeroesController } from './heroes.controller';

@Module({
  imports: [TypeOrmModule.forFeature([Hero])],
  providers: [HeroesService],
  controllers: [HeroesController],
})
export class HeroesModule {}

warning ์•Œ๋ฆผ HeroesModule์„ ๋ฃจํŠธ ApplicationModule๋กœ ๊ฐ€์ ธ ์˜ค๋Š” ๊ฒƒ์„ ์žŠ์ง€ ๋งˆ์‹ญ์‹œ์˜ค.

์ดํ›„์— Nest ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ƒˆ๋กœ ์ž‘์„ฑ๋œ ์—”๋“œ ํฌ์ธํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

  • GET /heroes - get many heroes.

  • GET /heroes/:id - get one hero.

  • POST /heroes/bulk - create many heroes.

  • POST /heroes - create one hero.

  • PATCH /heroes/:id - update one hero.

  • PUT /heroes/:id - replace one hero.

  • DELETE /heroes/:id - delete one hero.

Filtering and pagination

CRUD provides rich tools for filtering and pagination. Example request:

info Request GET /heroes?select=name&filter=power||gt||90&sort=name,ASC&page=1&limit=3

์ด ์˜ˆ์—์„œ๋Š” ํžˆ์–ด๋กœ ๋ชฉ๋ก์„ ์š”์ฒญํ•˜๊ณ  ํžˆ์–ด๋กœ์˜ ํŒŒ์›Œ๊ฐ€ 90๋ณด๋‹ค ํฐ name์†์„ฑ๋งŒ ์„ ํƒํ•˜๊ณ  1 ํŽ˜์ด์ง€ ๋‚ด์—์„œ ๊ฒฐ๊ณผ ์ œํ•œ์„ 3์œผ๋กœ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ASC ์ˆœ์„œ๋กœ name์œผ๋กœ ์ •๋ ฌ๋ฉ๋‹ˆ๋‹ค.

์‘๋‹ต ๊ฐ์ฒด๋Š” ๋‹ค์Œ๊ณผ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค.

{
  "data": [
    {
      "id": 2,
      "name": "Batman"
    },
    {
      "id": 4,
      "name": "Flash"
    },
    {
      "id": 3,
      "name": "Superman"
    }
  ],
  "count": 3,
  "total": 14,
  "page": 1,
  "pageCount": 5
}

warning ์•Œ๋ฆผ ๊ธฐ๋ณธ ์—ด์€ ์š”์ฒญ ์—ฌ๋ถ€์— ๊ด€๊ณ„์—†์ด ๋ฆฌ์†Œ์Šค ์‘๋‹ต ๊ฐœ์ฒด์— ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ์˜ ๊ฒฝ์šฐ์—๋Š” id ์—ด์ž…๋‹ˆ๋‹ค.

์ฟผ๋ฆฌ ๋งค๊ฐœ ๋ณ€์ˆ˜ ๋ฐ ํ•„ํ„ฐ ์—ฐ์‚ฐ์ž์˜ ์ „์ฒด ๋ชฉ๋ก์€ ํ”„๋กœ์ ํŠธ์˜ Wiki์—์„œ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Relations

์–ธ๊ธ‰ํ•  ๊ฐ€์น˜๊ฐ€ ์žˆ๋Š” ๋˜ ๋‹ค๋ฅธ ๊ธฐ๋Šฅ์€ "๊ด€๊ณ„"์ž…๋‹ˆ๋‹ค. CRUD ์ œ์–ด๊ธฐ์—์„œ API ํ˜ธ์ถœ ๋‚ด์—์„œ ํŽ˜์น˜ํ•  ์ˆ˜ ์žˆ๋Š” ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„ ๋ชฉ๋ก์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@Crud({
  model: {
    type: Hero,
  },
  join: {
    profile: {
      exclude: ['secret'],
    },
    faction: {
      eager: true,
      only: ['name'],
    },
  },
})
@Controller('heroes')
export class HeroesController {
  constructor(public service: HeroesService) {}
}

@Crud()๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์˜ต์…˜์—์„œ ํ—ˆ์šฉ๋œ ๊ด€๊ณ„๋ฅผ ์ง€์ •ํ•œ ํ›„ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์š”์ฒญ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

info Request GET /heroes/25?join=profile||address,bio

์‘๋‹ต์—๋Š” address ๋ฐ bio ์—ด์ด ์„ ํƒ๋˜๊ณ  ๊ฒฐํ•ฉ๋œ ํ”„๋กœํŒŒ์ผ์„ ๊ฐ€์ง„ hero ๊ฐ์ฒด๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

๋˜ํ•œ ์‘๋‹ต์—๋Š” eager: true๋กœ ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ๋ชจ๋“  ์‘๋‹ต์—์„œ ์ง€์†๋˜๋ฏ€๋กœ name ์—ด์ด ์„ ํƒ๋œ faction ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ์˜ WiKi์—์„œ ๊ด€๊ณ„์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Path params validation

๊ธฐ๋ณธ์ ์œผ๋กœ CRUD๋Š” ์ด๋ฆ„์ด id์ธ ์Šฌ๋Ÿฌ๊ทธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  number๋กœ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์ด ๋™์ž‘์„ ๋ณ€๊ฒฝํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์—”ํ‹ฐํ‹ฐ์— ๊ธฐ๋ณธ ์—ด _id (UUID ๋ฌธ์ž์—ด)๊ฐ€ ์žˆ๊ณ  ์ด๋ฅผ ์—”๋“œ ํฌ์ธํŠธ์˜ ์Šฌ๋Ÿฌ๊ทธ๋กœ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜์‹ญ์‹œ์˜ค. ์ด ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ž‘์—…์„ ์‰ฝ๊ฒŒ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@Crud({
  model: {
    type: Hero,
  },
  params: {
    _id: {
      field: '_id',
      type: 'uuid',
      primary: true,
    },
  },
})
@Controller('heroes')
export class HeroesController {
  constructor(public service: HeroesService) {}
}

๋” ๋งŽ์€ params ์˜ต์…˜์€ ํ”„๋กœ์ ํŠธ์˜ Wiki๋ฅผ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค.

Request body validation

์š”์ฒญ ๋ณธ๋ฌธ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋Š” ๊ฐ POST, PUT, PATCH ์š”์ฒญ์— Nest ValidationPipe๋ฅผ ์ ์šฉํ•˜์—ฌ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” @Crud()๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์˜ต์…˜์˜ model.type์„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ทœ์น™์„ ์„ค๋ช…ํ•˜๋Š” DTO๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ ์ œ๋Œ€๋กœ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด validation groups๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

@@filename(hero.entity)
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { IsOptional, IsDefined, IsString, IsNumber } from 'class-validator';
import { CrudValidationGroups } from '@nestjsx/crud';

const { CREATE, UPDATE } = CrudValidationGroups;

@Entity()
export class Hero {
  @IsOptional({ always: true })
  @PrimaryGeneratedColumn()
  id: number;

  @IsOptional({ groups: [UPDATE] })
  @IsDefined({ groups: [CREATE] })
  @IsString({ always: true })
  @Column()
  name: string;

  @IsOptional({ groups: [UPDATE] })
  @IsDefined({ groups: [CREATE] })
  @IsNumber({}, { always: true })
  @Column({ type: 'number' })
  power: number;
}

warning ์•Œ๋ฆผ create ๋ฐupdate ์กฐ์น˜์— ๋Œ€ํ•ด ๋ณ„๋„์˜ DTO ํด๋ž˜์Šค๋ฅผ ์™„์ „ํžˆ ์ง€์›ํ•˜๋Š” ๊ฒƒ์€ ๋‹ค์Œ CRUD ๋ฆด๋ฆฌ์Šค์˜ ์ฃผ์š” ์šฐ์„  ์ˆœ์œ„ ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค.

Routes options

@Crud()๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ ์šฉํ•˜์—ฌ ์ƒ์„ฑ๋˜๋Š” ํŠน์ • ๊ฒฝ๋กœ๋งŒ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ฑฐ๋‚˜ ํ™œ์„ฑํ™” ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@Crud({
  model: {
    type: Hero,
  },
  routes: {
    only: ['getManyBase'],
    getManyBase: {
      decorators: [
        UseGuards(HeroAuthGuard)
      ]
    }
  }
})
@Controller('heroes')
export class HeroesController {
  constructor(public service: HeroesService) {}
}

๋˜ํ•œ ๋ฉ”์†Œ๋“œ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ํŠน์ • ๊ฒฝ๋กœ decorators ๋ฐฐ์—ด์— ์ „๋‹ฌํ•˜์—ฌ ๋ชจ๋“  ๋ฉ”์†Œ๋“œ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ๋ฉ”์†Œ๋“œ๋ฅผ ์žฌ ์ •์˜ํ•˜์ง€ ์•Š๊ณ  ์ผ๋ถ€ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

Documentation

์ด ์ฑ•ํ„ฐ์˜ ์˜ˆ๋Š” CRUD ๊ธฐ๋Šฅ ์ค‘ ์ผ๋ถ€๋งŒ ๋‹ค๋ฃน๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ์˜ Wiki ํŽ˜์ด์ง€์—์„œ ๋” ๋งŽ์€ ์‚ฌ์šฉ ์งˆ๋ฌธ์— ๋Œ€ํ•œ ๋‹ต๋ณ€์„ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Last updated

Was this helpful?