-
[Nest.js] 8 - CRUD summary code(#1 ~ #7)Nest.js 2021. 9. 10. 18:57
Life Cycle
- Nest Application
- Reqeust
- Middleware - Global bound
- Middleware - Module bound
- Guard - Global
- Guard - Controller
- Guard - Controller, Root
- Interceptor - Global
- Interceptor - Controller
- Interceptor - Route
- Pipe - Global
- Pipe - Controller
- Pipe - Route
- Pipe - Parameter
- Controller - Method Handler, DTOs
- Service - Business logic
- Repository - TypeORM, Entity
- Interceptor - Route
- Interceptor - Controller
- Interceptor - Global
- filter - Handling Exceptions
- Response
- Bold indicates what used in this sample project
1. Nest Application
- src/main.ts
import { ValidationPipe } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); // 1 app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, }), ); await app.listen(3000); } bootstrap(); // 2
// 1. await NestFactory.create(AppModule) : register a root module, AppModule
// 2. bootstrap() : run application
- src/app.module.ts
import { Module } from '@nestjs/common'; import { BoardsModule } from './boards/boards.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeORMConfig } from './configs/typeorm.config'; @Module({ imports: [ TypeOrmModule.forRoot(TypeORMConfig), // 1 BoardsModule // 2 ], controllers: [], providers: [], }) export class AppModule {}
// 1. TypeOrmModule.forRoot(TypeORMConfig) : Register TypeORM configuration(see below #6.2)
// 2. Register other module at this Root module(there is one module in this chapter.)
- src/boards/boards.module.ts
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { BoardRepository } from './board.repository'; import { BoardsController } from './boards.controller'; import { BoardsService } from './boards.service'; @Module({ imports: [TypeOrmModule.forFeature([BoardRepository])], // 1 controllers: [BoardsController], // 2 providers: [BoardsService], //3 }) export class BoardsModule {}
// 1. TypeOrmModule.forFeature([BoardRepository]) : register the repository in its Module (see below #6)
// 2. register its controller in its module
// 3. register its service in its module
2. Request
- REST API
GET /boards
GET /boards/:id
POST /boards
PUT /boards/:id
DELETE /boards/:id
- Request and Response With Postman for now
, will update those documents with Swagger
3. Pipes
3.1 Global Pipe
- src/main.ts
import { ValidationPipe } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes( // 1 new ValidationPipe({ // 2 whitelist: true, // 3 forbidNonWhitelisted: true, // 4 transform: true, // 5 }), ); await app.listen(3000); } bootstrap();
// 1. app.useGlobalPipes(...) - register a pipe in Global-level
// 2. new ValidationPipe({...}) - use a built-in pipe, ValidationPipe
// 3. whitelist: true - fillter out properties that should not be received by the method handler.
- e.g., a handler expect "email" and "password" properties
, but a request also includes an "age" property(called non-whitelisted property),
this property can be automatically removed from the resulting DTO// 4. forbidNonWhiteListed: true - making stop the request from processing when non-whtelisted are present,
and return an error response to the user.
To enable this, set the forbidNonWhitelisted option to true,
in combination with setting whitelist to true// 5. transform: true - perform conversion of primitive types.
- e.g., request type string -> parameter type number3.2 Parameter Pipe
- src/boards/pipes/board-status-validation.pipe.ts
import { BadRequestException, PipeTransform } from '@nestjs/common'; import { BoardStatus } from '../board-status.enum'; // 1 export class BoardStatusValidationPipe implements PipeTransform { readonly StatusOptions = [BoardStatus.PRIVATE, BoardStatus.PUBLIC]; // 1 transform(value: any) { value = value.toUpperCase(); // 2 if (!this.isStatusValid(value)) { // 3 throw new BadRequestException( // 4 `${value} isn't in the status options, which one of PRIVATE or PUBLIC`, ); } return value; // 5 } private isStatusValid(status: any) { // 3 const index = this.StatusOptions.indexOf(status); return index !== -1; } }
// 1. src/boards/board-status.enum.ts
export enum BoardStatus { PRIVATE = 'PRIVATE', PUBLIC = 'PUBLIC', }
// 2. toUpperCase() - accept case-insensitive value
// 3. validate whether the value matches one of options in enum class
// 4. trow new BadRequestException(...) - if not matched, pipe causes 400 error response to the client
// 5. if matched, pipe sends the value to the next
4. Controller
- src/boards/boards.controller.ts
import { Body, Controller, Delete, Get, Param, Post, Put,} from '@nestjs/common'; import { BoardStatus } from './board-status.enum'; import { Board } from './board.entity'; import { BoardsService } from './boards.service'; import { CreateBoardDto } from './dto/create-board.dto'; import { UpdateBoardDto } from './dto/update-board.dto'; import { BoardStatusValidationPipe } from './pipes/board-status-validation.pipe'; @Controller('boards') export class BoardsController { constructor(private boardsService: BoardsService) {} // 1 @Get('/:id') getBoardById(@Param('id') id: number): Promise<Board> { return this.boardsService.getBoardById(id); } @Get() getAllBoards(): Promise<Board[]> { return this.boardsService.getAllBoards(); } @Post() createBoard( @Body('status', BoardStatusValidationPipe) status: BoardStatus, // 2 @Body() createBoardDto: CreateBoardDto, // 4.1 ): Promise<Board> { return this.boardsService.createBoard(createBoardDto); } @Put('/:id') updateBoard( @Param('id') id: number, @Body() updateBoardDto: UpdateBoardDto, // 4.2 ): Promise<Board> { return this.boardsService.updateBoard(id, updateBoardDto); } @Delete('/:id') deleteBoard(@Param('id') id: number): Promise<void> { return this.boardsService.deleteBoard(id); } }
// 1. Dependency Injection in Constructor
// 2. Bind the Custom pipe in Parameter-Level to check "status" value
4.1 create-dto
- src/boars/dto/create-board.dto.ts
import { IsEnum, IsNotEmpty, IsOptional } from 'class-validator'; import { BoardStatus } from '../board-status.enum'; export class CreateBoardDto { @IsNotEmpty() // 1 title: string; @IsNotEmpty() description: string; @IsOptional() // 1 @IsEnum(BoardStatus) // 2 readonly status: BoardStatus = BoardStatus.PUBLIC; // 3 }
// 1. Decorator-based validation - more decorators click the link (http://github.com/typestack/class-validator#usage)
// 2. @IsEnum(BoardStatus) - accept a value in one of enum options.
// 3. initialize a default value when it is not provided from client.
4.2. update-dto : Partial Mapped type
- src/boars/dto/update-board.dto.ts
import { PartialType } from '@nestjs/mapped-types'; import { CreateBoardDto } from './create-board.dto'; // 1 export class UpdateBoardDto extends PartialType(CreateBoardDto) {} // 2
// 1. import which dto is used as base class
// 2. use PartialType to make all properties as optional in "UpdateBoardDto"
- @IsNotEmpty() is not provided into "Partial" Mapped-Type, which is update-board.dto.ts
- @IsEnum(BoardStatus) is provided into "status" property in update-dto.ts
- more types click the link(https://docs.nestjs.com/openapi/mapped-types)
5. Service
- src/boards/boards.service.ts
import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { BoardStatus } from './board-status.enum'; import { Board } from './board.entity'; // 6.1 import { BoardRepository } from './board.repository'; import { CreateBoardDto } from './dto/create-board.dto'; import { UpdateBoardDto } from './dto/update-board.dto'; @Injectable() // 1 export class BoardsService { constructor( @InjectRepository(BoardRepository) // 2 private boardRepository: BoardRepository, ) {} // READ async getBoardById(id: number): Promise<Board> { // 3 const found = await this.boardRepository.findOne(id); if (!found) { throw new NotFoundException(`can't find Board with id ${id}`); } return found; } // READ async getAllBoards(): Promise<Board[]> { const boards = await this.boardRepository.find(); if (!boards) { throw new NotFoundException(`There is no boards in DB`); } return boards; } // CREATE async createBoard(createBoardDto: CreateBoardDto): Promise<Board> { const { title, description, status } = createBoardDto; const board = this.boardRepository.create({ // 4 title, description, status, }); await this.boardRepository.save(board); return board; } // UPDATE async updateBoard( id: number, updateBoardDto: UpdateBoardDto, ): Promise<Board> { const board = await this.getBoardById(id); board.title = updateBoardDto.title; board.description = updateBoardDto.description; board.status = updateBoardDto.status; await this.boardRepository.save(board); return board; } // DELETE async deleteBoard(id: number): Promise<void> { const board = await this.boardRepository.delete(id); console.log(board); } }
// 1. @Injectable() : this class, Provider, can be managed by the Nest IoC container
// 2. @InjectRepository(BoardRepository) : inject the BoardRepository into the BoardsService
// 3. async/ await : allows us to write asynchronous code in more synchronous and readable way.
Promise : an object is handled in asynchronous processing and represents a task.
-> click link for more detail(https://kamilmysliwiec.com/typescript-2-1-introduction-async-await/)// 4. this.boardRepository.create({...}); : create an entity which is registered in the called repository.
-> the same as "const board = new Board(...);
6. Repository
- src/boards/board.repository.ts
import { EntityRepository, Repository } from 'typeorm'; import { Board } from './board.entity'; @EntityRepository(Board) // 1 export class BoardRepository extends Repository<Board> {} // 2
// 1. @EntityRepository(Board) : Used to declare a class as a Custom Repository.
Custom repository can manage some specific entity or just be generic.
, optionally can extend AbstractRepository, Repository or TreeRepository// 2. ... extends Repository<Board> : work with "Board" entity, supports basic query methods.
-> more methods and details (https://typeorm.delightful.studio/classes/_repository_repository_.repository.html)- register the repository in its Module
... import { BoardRepository } from './board.repository'; @Module({ imports: [TypeOrmModule.forFeature([BoardRepository])], ... }) export class BoardsModule {}
6.1 Entity
- src/boards/board.entity.ts
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; import { BoardStatus } from './board-status.enum'; @Entity() // 1 export class Board extends BaseEntity { // @PrimaryGeneratedColumn() // id: number; @Column() // title: string; @Column() description: string; @Column() status: BoardStatus; }
// 1. @Entity() : is a class that maps to a database table
board table in mysql 6.2 TypeORM
- src/configs/typeorm.config.ts
import { TypeOrmModuleOptions } from '@nestjs/typeorm'; export const TypeORMConfig: TypeOrmModuleOptions = { type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: '1234', database: 'test', entities: [__dirname + '/../**/*.entity.{js,ts}'], // 1 synchronize: true, // 2 };
// 1. Register entities by file path and name(Using Team Naming Convention)
-> if needed to regist specific entities only, use below.... import { Board } from 'src/boards/board.entity'; export const TypeORMConfig: TypeOrmModuleOptions = { ... entities: [Board], ... };
7. Response
CRUD( => Create)
- STATUS 201 : CREATE without optional value "status", which is set to the default value "PUBLIC"
- STATUS 201 CREATE with "status"
- 400 ERROR : NOT CREATE because of @IsNotEmpty() for title and description
- 400 ERROR : NOT CREATE because of @IsEnum(BoardStatus)
- 400 ERROR : NOT CREATE because of new ValidationPipe({whitelistL true, forbidNonWhiteListed: true})
CRUD( => Read )
- STATUS 200 : Read all boards
- STATUS 200 : Read a board
- 404 ERROR : Read a board with unavailable id
CRUD( => Update )
- STATUS 200 : Update a board for title and description
- STATUS 200 : Update a board for titl, description, status
- 400 ERROR : non-whitelisted value from the client
- 400 ERROR : update description and status, which is not an option in enum
CRUD( => Delete )
- STATUS 200 : delete a board
- 404 ERROR : delete a board with unavailable board ID
'Nest.js' 카테고리의 다른 글
[Nest.js] 10 - Authentification - sign up, log in (0) 2021.09.13 [Nest.js] 9 - Authentification - module/components (0) 2021.09.13 [Nest.js] 7- Entity & Repository (0) 2021.09.09 [Nest.js] 6 - DTO & PIPES (0) 2021.09.09 [Nest.js] 5 - TypeORM / MySql(MariaDB) (0) 2021.09.08