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?