๐ Nestjs ๊ธฐ๋ณธ๊ตฌ์กฐ
- ESLint, Prettier๋ ์ฝ๋ ์ปจ๋ฒค์ ๊ท์น์ ์์ฑํด ํ ์ธ์ ๊ฐ ํ์ ํ ๋ ์ฝ๋ ์ปจ๋ฒค์ ์ ๋ง๋๋ ๊ณณ.
- Decorator = ๊ธฐ๋ฅ ์ถ๊ฐ ์ญํ . (๋ถ์ฌ์จ์ผํจ)
- Controller = ๋ค์ด์ค๋ ์์ฒญ ์ฒ๋ฆฌ ๋ฐ ์๋ต๊ฐ์ ๋ฐํํ๋ ์ญํ . ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ํ ํน์ ์์ฒญ ์์ ๋ชฉ์ .
- Request ๊ฐ์ฒด(@Req()). Body ๊ฐ์ฒด (@Body()). Param ๊ฐ์ฒด (@Param()).
- Provider = ์๋น์ค, ๋ฆฌํฌ์งํ ๋ฆฌ, ํฉํ ๋ฆฌ ๋ฑ Nest์ Class๋ค์ ๋๋ถ๋ถ ๊ณต๊ธ์๋ก ์ทจ๊ธ๋๋ค. ์ด์ ๊ณต๊ธ์์๊ฒ๋ ์ข ์์ฑ ์ฃผ์ ์ด ๊ฐ๋ฅํ๋ค.(Dependency Injection)
- Controller (์๋น์)๋ Service(๊ณต๊ธ์)๋ฅผ Injection๋ฐ์์ ์ฌ์ฉํ๋ ๊ฒ์ด๋ค. (์์กด์ฑ์ฃผ์ ํจํด)
- constructor(private readonly appService: AppService) {}
- ๊ฐ๊ฐ์ Provider โ Controller๋ก DI์ด ์งํ๋๊ณ โ ์ด๊ฒ์ด ๋ค์ App.module.ts๋ก ํฉ์ณ์ ธ์ main.ts์์ ์คํ๋๋ค.
๐ ์บก์ํ
- ๊ฐ์ฒด์ ์์ฑ๊ณผ Methods๋ฅผ ํ๋๋ก ๋ฌถ๊ณ โ ๊ตฌํ๋ ์ผ๋ถ๋ฅผ ๊ฐ์ถ์ด ์๋ํจ
- ๋ชจ๋์ ๊ธฐ๋ณธ์ ์ผ๋ก ๊ณต๊ธ์๋ฅผ ์บก์ํ ํ๋ค. ๋๋ฌธ์ Exportํ ๋ชจ๋์ ๋ํด์๋ง ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค.
- ์บก์ํ ๋ ๋ชจ๋์ ๋ค๋ฅธ ๋ชจ๋์์ ์ฌ์ฉํ ๋๋ provider์ ์ค์ค์ด ์ถ๊ฐํ์ง ๋ง๊ณ , exports ํด์ฃผ๊ณ โ import ํด์์ ์จ๋ผ. (๋จ์ผ์ฑ ์์์น)
๐ ๋ฏธ๋ค์จ์ด (nest g middleware <NAME>)
- Route Handler (Controller) ์ด์ ์ ํธ์ถ๋๋ ํจ์.
- req, res, next๋ก ๊ตฌ์ฑ๋จ. (Express์ ๋์ผ)
- ๋ฏธ๋ค์จ์ด ๋ง๋ค๊ธฐ
- // nest g middleware <NAME> import { Injectable, NestMiddleware } from '@nestjs/common'; import { NextFunction, Request, Response } from 'express'; @Injectable() export class TestMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { console.log("This is for test Middleware"); console.log(req.ip) next(); } }
๐ ๋ฏธ๋ค์จ์ด ์ ์ฉ
- App.module.tsํ์ผ์์ ์๋์ฒ๋ผ ์ ์ฉํจ.
- apply(<์ ์ฉํ ๋ฏธ๋ค์จ์ดํจ์>)
- forRoutes(<์ ์ฉ๊ฒฝ๋ก>)
export class AppModule **implements NestModule{
configure(consumer : MiddlewareConsumer) {
consumer.apply(TestMiddleware).forRoutes("*")
}
}**
๐ ์์ธ ์ฒ๋ฆฌ ๋ฐฉ๋ฒ (Exception Filter)
- Nest๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฒ๋ฆฌ๋์ง ์์ ์์ธ ๋ด์ฉ์ ๋ํ์ฌ ์์ธ ๋ ์ด์ด๋ฅผ ์ ์ฉ๋์ด ์์ธ์ฒ๋ฆฌ๊ฐ ์๋์ ์ผ๋ก ์งํ๋๋ค. ์ฆ, ๋ณ๋์ ์ฒ๋ฆฌ์์ด๋ ์์์ ์ฒ๋ฆฌํ๊ธด ํ๋ค. ๋ค๋ง ์ํ๋ ์์ธ์ฒ๋ฆฌ๊ฐ ์๋.
- ๋ง์ฝ ์๋ฌ๊ฐ ๋ ๋๋ง๋ค ์ํ๋ ํผ์ Err๋ฅผ ๋ฆฌํดํ๊ณ ์ถ๋ค๋ฉด ๋๋ ์๋ฌ์ ๋ํ ๋ก๊ฑฐ๋ฅผ ์ ์ฉํ๊ณ ์ถ๋ค๋ฉดException Filter๋ฅผ ํตํด ๋ฐ์ํ๋ ์๋ฌ์ ๋ํ์ฌ ์ฒ๋ฆฌ๋ฅผ ํด์ค ์ ์๋ค.
- ๊ธฐ๋ณธ์ ์ธ ์์ธ ๋ฐ์ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ๋ค.
@Get()
getHello(): string {
throw new HttpException('Forbidden2', HttpStatus.FORBIDDEN);
}

์์ธ Exception Filter ๋ง๋ค๊ธฐ
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
const error = exception.getResponse();
// You Can Add Logger Hear;
if (typeof error === 'string') {
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
error: error
})
} else {
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
...error, // ๋น๊ตฌ์กฐํ ๋น
})
}
}
}
- ์ ์ฒด์ ์ฉ
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './http-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();
๐ ํ์ดํ
- ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ์ ์ฌ์ฉํ๋ ํ์ดํ๋ผ์ธ Validation Check, Type๋ณํ์ ์ญํ ์ ํด์ค๋ค.
// Param์ ํ์ดํ ์ ์ฉ๋ฐฉ๋ฒ
@Get(":id")
getHello(@Param("id", **ParseIntPipe**) param): string {
console.log(param);
return this.appService.getHello();
}
- DTO์ ํ์ดํ์ ์ฉ๋ฐฉ์์ ์ฃผ๋ก class-validator ๋ฅผ ์ด์ฉํด ์งํํ๋ค.
// DTO Class ์ ์
import { IsNotEmpty, IsString, Matches, MaxLength, MinLength } from "class-validator";
export class SignInDto {
@IsString()
@IsNotEmpty()
@MinLength(5)
@MaxLength(50)
@ApiProperty({ description : "Email" })
email : string;
@IsString()
@IsNotEmpty()
@Matches(/^[a-zA-Z0-9]*$/, {
message : "Not Proper EOA"
})
@ApiProperty({ description : "EOA Public Key" })
eoa : string;
}
// Controller ์ ์ฉ
@Post("/register")
registerUser(**@Body(ValidationPipe) registerUserDto : RegisterUserDto**) : Promise<User> {
return this.userService.registerUser(registerUserDto);
}
๐ ์ธํฐ์ ํฐ
- @Injectable ๋ฐ์ฝ๋ ์ดํฐ๊ฐ ๋ฌ๋ฆฐ ํด๋์ค๋ก DI๊ฐ ๊ฐ๋ฅํ ๊ฒ์ธ๋ฐ, ๊ฐ๊ฐ์ ๋ชจ๋ (Users, Markets)์ ํก๋จํ๋ฉด์ ๊ณตํต์ ์ผ๋ก ์ฌ์ฉํ๋ ํจ์. (Middleware์ ์๋นํ ์ ์ฌํ๋ค)
- ๊ธฐ๋ณธ์ ์ผ๋ก ์์ฒญ์๋ช
์ฃผ๊ธฐ(Request LifeCycle) ๋ ๋ค์๊ณผ ๊ฐ๋ค.
- Request โ Middleware โ Guard โ Intercepter (Pre) โ Pipeline โ Controller โ Service โ Intercepter (After) โ Exception Filter โ Response
- ์์์ ์ดํด๋ณด๋ฉด Middleware์ Intercepter๋ ์๋ช ์ฃผ๊ธฐ ์ ์ ์ฉ๋๋ ์๊ธฐ๊ฐ ๋ค๋ฅด๋ค. Intercepter๋ Pre์ After ๋ก Controller ์คํ ์ด์ ์ดํ๋ก ์ ์ฉ์ด ๋๋ ๋ฐ๋ฉด Middleware๋ Controller ๋์ ์ ์๋ง ์ ์ฉ.
- ์ธํฐ์ ํฐ ๋ง๋ค๊ธฐ (src/success.interceptor.ts)
- import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; @Injectable() export class SuccessInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { console.log('Before...'); const now = Date.now(); return next .handle() .pipe( tap(() => console.log(`After... ${Date.now() - now}ms`)), ); } }
- ์ ์ฉ๋ฐฉ๋ฒ
- ์ฌ์ฉํ๋ฉด Beforeโฆ โ param โ After ์์ผ๋ก ์ฝ์์ ๋จ๊ฒ ๋๋ค.
- @Get(":id") **@UseInterceptors(SuccessInterceptor)** getHello(@Param("id", ParseIntPipe) param): string { console.log(param); return this.appService.getHello(); }
- ์ด๋ฐ์ฐ๋? โ ์๋ฌ์ ์ผ๊ด์ฑ์๋ ์ถ๋ ฅ์ ์ํด Exception Filter ์์ ์ฌ์ฉํ๋ค๋ฉด, Response์ ๋ํ ์ผ๊ด์ฑ์๋ ๋ฆฌํด์ ์ํด Intercepter๋ฅผ ์ ์ฉํ๋ค.
๐ Database Setting
- ํ๊ฒฝ๋ณ์์ค์
// Nestjs ์์ฒด ํ๊ฒฝ๋ณ์ ํธ๋ค๋ง ํจํค์ง ์ง์ ์๋ ๋ด์ฉ์ผ๋ก ์ค์นํ๊ณ , .envํ์ผ์ ํ๊ฒฝ๋ณ์ ์ฒ๋ฆฌ
npm i @nestjs/config
- DB ์ฐ๊ฒฐ ์ค์
// MySQL์ ์ฌ์ฉํ ๊ฒ์ด๋ฏ๋ก typeorm & mysql2 ์ค์น
npm i @nestjs/typeorm mysql2
// app.module.ts - imports๋ถ๋ถ์ ์๋ ์ถ๊ฐ.
TypeOrmModule.forRoot({
type : 'mysql',
host : process.env.DATABASE_HOST,
port : parseInt(process.env.DATABASE_PORT),
username : process.env.DATABASE_USER,
password : process.env.DATABASE_PASSWORD,
database : process.env.DATABASE_DB,
entities : [__dirname + "/entities/**/*.entity.{js,ts}"], // Entity ๊ฒฝ๋ก
synchronize : true // DB ๋๊ธฐํ
}),
- Entity ์ ์
import { BaseEntity, Column, Entity, CreateDateColumn, UpdateDateColumn, OneToMany, PrimaryGeneratedColumn } from "typeorm";
@Entity({
name : "users" // DB์ ์์ฑ๋ Table ๋ช
์ค์ ๊ฐ๋ฅ
})
export class User extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
email : string;
@Column()
@Exclude() // Return ๋, ๋ฐํํ์ง ์๋ ํ๋๋ก ์ต์
์ถ๊ฐํจ.
password : string;
@Column({ nullable : false, unique : true, default : () => 0 })
role : number;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
๐ DTO (Data Transfer Object)
- ๊ณ์ธต ๊ฐ ๋ฐ์ดํฐ ๊ตํ ๊ฐ์ฒด๋ก Controller์ ์ ๋ฌ๋๋ ๋ฐ์ดํฐ์ ๋ํ์ฌ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํ ์ ์์.
- DTO ์ ์ญ ์ค์ ํ ์ฌ์ฉ๋ฒ
// @ main.ts ํ์ผ์ Global Validation Pipe ์ฌ์ฉ์ ์ค์ ํ ์ ์๋ค. // ๋งค ์ปจํธ๋กค๋ฌ์ ์ ์ฉ๋ ๊ฐ๋ฅํ๋ ๊ธ๋ก๋ฒ๋ก ์ฌ์ฉํ๋ฉด ๋ณด๋ค ํธํ๋ฏ๋ก. async function bootstrap() { const app = await NestFactory.create(AppModule); // Global Exception Filter ์๋ฌ ๋ฆฌํด ํธ๋ค๋ง app.useGlobalFilters(new HttpExceptionFilter()); **app.useGlobalPipes(new ValidationPipe());** // CORS ์ค์ app.enableCors(); // Swagger await app.listen(process.env.SERVER_PORT); } bootstrap(); // controller @Get("/") **signUpUser(@Body() body : SignupUserDto) {** console.log(body); return ["hello", "apple"]; }
- DTO ๊ฐ๋ณ ์ ์ฉ๋ฐฉ๋ฒ
- ์๋์ฒ๋ผ Body Decorator์ Validation Pipe๋ฅผ ๋ฃ์ด์ ์ฌ์ฉํ ์๋ ์์.
// ํ์๊ฐ์ @Post("/register") registerUser(**@Body(ValidationPipe) registerUserDto : RegisterUserDto**) : Promise<User> { return this.userService.registerUser(registerUserDto); }
๐ PickType & OmitType
- PickType์ Class ์ ์ ์์ฑ ์ค ์ํ๋ ๊ฒ์ ์ ํํด ์๋ก์ด ํด๋์ค๋ฅผ ์์ฑํ๋ค. ์ฆ ํ์ํ๊ฑธ ๊ณจ๋ผ์ ์๋ก์ด ํ์ ์ ํ์ ๋ง๋ ๋ค.
- OmitType์ Class ์์ฑ ์ค ํ์์๋ ํ์ ์ ์ ์ธํ๊ณ ์๋ก์ด ํด๋์ค๋ฅผ ์์ฑํ๋ค. ์ฆ ํ์์๋๊ฒ๋ง ๋นผ๊ณ ์๋ก์ด ํ์ ์ ํ์ ๋ง๋ ๋ค.
- ์ฒ์์ ๊ทธ๋ฅ Promise์ ์ง์ ๋ฆฌํด ํ์ ๋ฃ์ด์ ํด๋ดค๋ค. ๊ทผ๋ฐ ์๊ฐํด๋ณด๋ ์ค์จ๊ฑฐ ๋๋ฌธ์ ๋ ์ ์ํด์ผ๋๋๋ผ. ๊ทธ๋ฌ๋ Promise์๋ค๊ฐ ํ์ ์ ์ํ์ง๋ง๊ณ .. DTO์๋ค๊ฐ ๋ฆฌํด ์ ์ํด ๋๊ณ ์ฐ๋๊ฒ์ด ๋์ ๊ฒ.
PickType Example
// Signup.dto.ts
...
import { User } from "src/entities/user.entity";
~~export class SignUpUserDto {}~~
// user entity ๋ด์ฉ ์ค **'id', 'email', 'role'**๋ง ๋ฆฌํดํ๋ ํ์
์ ๋ง๋ค๊ฒ ๋ค๋ฉด ?
**export class SignUpUserReturn extends PickType(
User, ['id', 'email', 'role']
) {}**
OmitType Example
// Signup.dto.ts
import { ApiProperty, **PickType**, **OmitType** } from "@nestjs/swagger";
import { User } from "src/entities/user.entity";
~~export class SignUpUserDto {}~~
// user entity ๋ด์ฉ ์ค Password๋ง ๋บด๊ณ ๋ฆฌํดํ๋ ํ์
์ ๋ง๋ค๊ฒ ๋ค๋ฉด ?
**export class SignUpUserReturn extends OmitType(
User, ['password']
) {}**
'Web Development > Back-end' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
HTTP VS Socket (0) | 2023.05.01 |
---|---|
HTTP & HTTPS & SSL ์ดํดํ๊ธฐ (0) | 2023.05.01 |
์ธ์ฆ ์ธ๊ฐ ๋ฐฉ์ ๋น๊ต (0) | 2023.05.01 |
Nestjs & Docker & Github Action ํ์ฉ ์๋ ๋ฐฐํฌ ๊ตฌ์ถํ๊ธฐ (2) | 2023.05.01 |
[CICD] Github Action (CI) + CodeDeploy (CD) (0) | 2022.10.10 |