0018.1 Nest.js ๐Ÿฑ


Help Centre

Packages

Overview

Controllers


@Param

๋™์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” Path์ธ์ž. /cat/1์—์„œ์˜ 1๊ณผ ๊ฐ™์€ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ๋งค๊ฐœ ๋ณ€์ˆ˜๊ฐ€ ์žˆ๋Š” ๊ฒฝ๋กœ๋Š” ์ •์  ๊ฒฝ๋กœ ๋’ค์— ์„ ์–ธ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๋งค๊ฐœ ๋ณ€์ˆ˜ํ™”๋œ ๊ฒฝ๋กœ๊ฐ€ ์ •์  ๊ฒฝ๋กœ๋กœ ํ–ฅํ•˜๋Š” ํŠธ๋ž˜ํ”ฝ์„ ๊ฐ€๋กœ์ฑ„๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

@Query

? ์ดํ›„์— ๋‚˜์˜ค๊ณ  &๋กœ ๋ถ„๋ฆฌ๋œ ์ธ์ž๋“ค์„ ์˜๋ฏธ. key=value ์Œ์œผ๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ๋‹ค.


์˜์กด์„ฑ ์ฃผ์ž…๋  ์ˆ˜ ์žˆ๋Š” ๋‹ค์–‘ํ•œ ํด๋ž˜์Šค๋“ค์ด๋‹ค. ๋Œ€ํ‘œ์ ์œผ๋กœ Service, Repository, Factory, Helper, Middleware๊ฐ€ ์žˆ๋‹ค. ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†๋Š” ๋ณต์žกํ•œ ์ž‘์—…์„ Provider๋“ค์—๊ฒŒ ๋„˜๊ฒจ์ฃผ๋Š” ์˜ˆ์‹œ๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ  ์žˆ๋‹ค.

Inversion of Control

https://en.wikipedia.org/wiki/Inversion_of_control

์›๋ž˜๋Š” ์œ ์ €๊ฐ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ฝ”๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์ง€๋งŒ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ฝ”๋“œ๊ฐ€ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์œ ์ € ์ฝ”๋“œ๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“œ๋Š” ํŒจํ„ด์„ IoC๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.

Dependency Injection์ด๋ผ๊ณ , ์ƒ์„ฑ์ž ํƒ€์ž„์— ์˜์กด์„ฑ Provider๋ฅผ ์ฃผ์ž…ํ•˜๋Š” ๊ณผ์ •์„ ์˜๋ฏธ. ์•„๋ž˜์˜ ์ฝ”๋“œ์˜ Provider์ธ CatsService๋Š” @Injectable ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ๋‹ฌ๊ณ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ปจํŠธ๋กค๋Ÿฌ์˜ ์ƒ์„ฑํƒ€์ž„์— ์ฃผ์ž…๋  ์ˆ˜ ์žˆ๋‹ค.

// interfaces/cats.interface.ts
export interface Cat {
	name: string;
	age: number;
	breed: string;
}

// cats/cats.service.ts
@Injectable()
export class CatsService {
	private readonly cats: Cat[] = [];
	create(cat: Cat) {
		this.cats.push(cat);
	}
	findall(): Cat[] {
		return this.cats
	}
}

// cats/cats.controller.ts
@Controller('cats')
export class CatsController {
	/**
	 * ์ž๋™์œผ๋กœ ์ฃผ์ž…๋˜๋Š” CatsService
	 */
	constructor(private catsService: CatsService) {}

	@Post()
	async create(@Body() createCatDto: CreateCatDto) {
		this.catsService.create(createCatDto)
	}

	@Get()
	async findAll(): Promise<Cat[]> {
		return this.catsService.findAll();
	}
}

์ƒ์„ฑ์ž์— ์˜์กด์„ฑ์„ ๋„ฃ๊ณ ์‹ถ์ง€ ์•Š๋‹ค๋ฉด Property-based injection์„ ์ฐธ๊ณ .

๊ฐ๊ฐ์˜ ๋„๋ฉ”์ธ๋“ค (users, cats, posts, comments, ...)์€ ๊ฐ๊ฐ์˜ app์œผ๋กœ ๊ด€๋ฆฌ๋œ๋‹ค. ์ด๋•Œ app์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ์ฝ”๋“œ๋“ค์˜ ์ง‘ํ•ฉ์„ ๋ชจ๋“ˆ์ด๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค. nest g module <module-name> CLI ๋ช…๋ น์–ด๋กœ ์ƒˆ ๋ชจ๋“ˆ ์ƒ์„ฑ์ด ๊ฐ€๋Šฅํ•˜๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ฃจํŠธ๋ชจ๋“ˆ์— import๋ฅผ ํ•ด์•ผํ•œ๋‹ค.

exports ์†์„ฑ์œผ๋กœ ๋ชจ๋“ˆ ๋‚ด provider๋ฅผ ๋‹ค๋ฅธ ๋ชจ๋“ˆ์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ ๋‹ค.

@Module({
	controllers: [CatsController],
	providers: [CatsService],
	exports: [CatsService]
})
export class CatsModule {}

@Global ์–ด๋…ธํ…Œ์ด์…˜ ๋‹ฌ๋ฉด providers๋ฅผ ๋‹ค๋ฅธ ๋ชจ๋“  ๋ชจ๋“ˆ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

express.js์˜ ๋ฏธ๋“ค์›จ์–ด์˜ ์Šคํ‚ด์„ ๊ณ ์Šค๋ž€ํžˆ ๋”ฐ๋ผ๊ฐ€๊ณ  ์žˆ๋‹ค. ๋‹จ์ˆœ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ next() ๋ฅผ ์‚ฌ์šฉ, ๋‹ค์Œ ๋ฏธ๋“ค์›จ์–ด๋กœ ์š”์ฒญ์„ ์ „๋‹ฌํ•  ์ˆ˜๋„ ์žˆ์œผ๋ฉฐ, NestMiddleware ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ปจํŠธ๋กค๋Ÿฌ์— ์˜์กด์„ฑ ์ฃผ์ž…์„ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

// logger.middleware.ts
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
	use(req: Request, res: Response, next: NextFunction) {
		console.log('reqest...');
		next();
	}
}

// app.module.ts
@Module({
	imports: [CatsModule],
})
export class AppModule implements NestModule {
	configure(consumer: MiddlewareConsumer) {
		consumer
			.apply(LoogerMiddleware)
			.forRoutes('cats');
	}
}

consumer.forRoutes ์˜ ์ธ์ž๋กœ ์ข€ ๋” ๊ตฌ์ฒด์ ์ธ path, method, ์‹ฌ์ง€์–ด๋Š” controller๋ฅผ ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

Nest์—๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด์—์„œ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ๋ชจ๋“  ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์˜ˆ์™ธ ๊ณ„์ธต์ด ๋‚ด์žฅ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ์™ธ๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ์—์„œ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์œผ๋ฉด ์ด ๊ณ„์ธต์—์„œ ์˜ˆ์™ธ๋ฅผ ํฌ์ฐฉํ•˜์—ฌ ์ ์ ˆํ•œ ์‚ฌ์šฉ์ž ์นœํ™”์ ์ธ ์‘๋‹ต์„ ์ž๋™์œผ๋กœ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.

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();

    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
      });
  }
}
@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
	throw new ForbiddenException();
}

ํŒŒ์ดํ”„๋Š” ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ๋ผ์šฐํŒ…์„ ํ•˜๊ธฐ ์ „ ๋‘ ๊ฐ€์ง€ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

  1. ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ (์˜ˆ์‹œ. string โŸถ number)
  2. ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ

๋ชจ๋“  PipeTransform ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  @Injectable() ์–ด๋…ธํ…Œ์ด์…˜์„ ๊ฐ–๊ณ ์žˆ๋Š” ํด๋ž˜์Šค๋Š” Pipe์ด๋‹ค.

path variable์„ ์›ํ•˜๋Š” ํƒ€์ž…์œผ๋กœ ํŒŒ์‹ฑํ•ด์•ผํ•  ๊ฒฝ์šฐ, ์˜ˆ๋ฅผ ๋“ค์–ด ์ธ์ž์— ๋“ค์–ด์˜ฌ @Param์— ParseXXXPipe๋ฅผ ๋„ฃ์–ด์ฃผ๋ฉด ๋œ๋‹ค.

@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
	return this.catsService.findOne(id);
}

path variable๋งŒ ๋˜๋ƒ? query parameter๋„ ๋œ๋‹ค!

@Get()
async findOne(@Query('id'), ParseIntPipe) id: number) {
	...
}

ํŒŒ์‹ฑ์— ์‹คํŒจํ•œ ๊ฒฝ์šฐ (์˜ˆ๋ฅผ ๋“ค์–ด GET cats/hello๊ฐ€ ๋“ค์–ด์˜จ ๊ฒฝ์šฐ) ์˜ˆ์™ธ๋ฅผ ๋˜์ง„๋‹ค. ๊ทธ ์˜ˆ์™ธ๋Š” ์œ„์—์„œ ์„ค๋ช…ํ•œ Exception Filter์—์„œ ์ฒ˜๋ฆฌ๋˜์–ด ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ์‘๋‹ตํ•˜๊ฒŒ ๋œ๋‹ค.

๋งŒ์•ฝ์— ํŒŒ์ดํ”„๋ฅผ ํ†ตํ•ด์„œ ๋ณ€ํ™˜ํ•ด์•ผ ํ•˜๋Š” ํƒ€์ž…์ด ๊ธฐ๋ณธํƒ€์ž…์ด ์•„๋‹ˆ๋ผ๋ฉด? => DTO Validation์„ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. DTO Validation using class-validator {NestJS}

guard๋Š” @Injectable ์–ด๋…ธํ…Œ์ด์…˜์„ ๊ฐ€์ง€๊ณ  CanActivate ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค๋ฅผ ์˜๋ฏธํ•œ๋‹ค.

guards๋Š” express.js ๋ฏธ๋“ค์›จ์–ด์˜ ๋‹จ์ ์ธ next๊ฐ€ ๋ˆ„๊ตฌ์ธ์ง€ ๋ชจ๋ฅธ๋‹ค๋Š” ํŠน์ง•์„ ExecutionContext ์ธ์Šคํ„ด์Šค๋ฅผ ํ†ตํ•ด ๊ทน๋ณตํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ .

๊ฐ€์žฅ ๋Œ€ํ‘œ์ ์ธ ์‚ฌ์šฉ์‚ฌ๋ก€๋Š” ์—ญ์‹œ ์‚ฌ์šฉ์ž ์ธ๊ฐ€์ด๋‹ค. nestjs.com/security/authentication ์‚ฌ์šฉ์ž ์ธ๊ฐ€์— ๋Œ€ํ•œ ๋กœ์ง์„ ๋งŒ๋“ค์–ด ์ „์—ญ์ ์œผ๋กœ or ์ปจํŠธ๋กค๋Ÿฌ or ๋ฉ”์„œ๋“œ ์Šค์ฝ”ํ”„์—์„œ ๋™์ž‘ํ•˜๋„๋ก ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค.

ExecutionContext ๊ฐ์ฒด๋Š” ArgumentHost๋ฅผ ์ƒ์†ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— request๊ฐ์ฒด์™€ response ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
	/**
	 * ExecutionContext์—์„œ request ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค. 
	 * ํ—ค๋”์— ๋‹ด๊ธด ํ† ํฐ์„ validateํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ๊ฒฐ๊ณผ๋Š” ๋™๊ธฐ์ ์œผ๋กœ
	 * (Promise), ํ˜น์€ ๋น„๋™๊ธฐ์ ์œผ๋กœ (Observable), ํ˜น์€ ๊ทธ ์ฆ‰์‹œ
	 * (boolean) ๋ฆฌํ„ดํ•  ์ˆ˜ ์žˆ๋‹ค.
	 */
    return validateRequest(request);
  }
}

User Role์— ๋”ฐ๋ผ์„œ ๋‹ค๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์—ฌ์ค„ ์ˆ˜๋„ ์žˆ๋‹ค. admin ์œ ์ €๋งŒ์ด ์ ‘์†ํ•  ์ˆ˜ ์žˆ๋Š” ๊ด€๋ฆฌํŽ˜์ด์ง€์— ๋Œ€ํ•ด์„œ ์ปค์Šคํ…€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์œ ์ €์˜ ์—ญํ• ์„ ๊ฑฐ๋ฅผ ์ˆ˜๋„ ์žˆ๋‹ค. Role-based authentication

NestInterceptor ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  @Injectable ํ•œ ํด๋ž˜์Šค๋ฅผ ์ธํ„ฐ์…‰ํ„ฐ๋ผ๊ณ  ๋ถ€๋ฆ„. ExecutionContext(์ค„๊ธฐ์ฐจ๊ฒŒ ๋ดค๋˜๊ฑฐ) + CallHandler(handle์„ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค์Œ ์ปจํŠธ๋กค๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๊ฐ„์ ‘ ํ˜ธ์ถœ(RxJS๋ฅผ ์‚ฌ์šฉํ•œ๋Œ„๋‹ค))๋ฅผ ํ†ตํ•˜์—ฌ intercept() ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด ๋œ๋‹ค.

CallHandler๋ฅผ ์•„์˜ˆ ๊ฐ์‹ผ ๊ฑธ ๋ณด๋ฉด request ~ response ๋กœ์ง๋ณด๋‹ค ์Šค์ฝ”ํ”„๊ฐ€ ๋„“๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log('Before...');

    const now = Date.now();
    return next
      .handle() /** 
			     * handle์„ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์œผ๋กœ request-response 
				 * handler๊ฐ€ ์‹คํ–‰๋œ๋‹ค. ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ๋ญ˜ ์ŠคํŠธ๋ฆผ์— ๋„ฃ๋Š”์ง€๋Š”
				 * ์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹ค.
				 */
      .pipe(
		/**
		 * `tap`์€ observable stream์ด ์ •์ƒ์ ์œผ๋กœ (์˜ˆ์™ธ๋„ ํฌํ•จ) 
		 * ์ข…๋ฃŒ๋์„๋•Œ ๋ฐ˜์‘ํ•œ๋‹ค. 
		 */
        tap(() => console.log(`After... ${Date.now() - now}ms`)),
      );
  }
}

Transformer

Dto์—์„œ ์ฃผ๋กœ ์“ฐ์ด๋Š” Transformer๋Š” Validator์™€ ํ˜‘์—…ํ•˜์—ฌ ๋ฆฌํ€˜์ŠคํŠธ๋ฅผ ์ •์˜๋œ VO๋กœ deserializeํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•˜๋‹ค. 0018.5 Validators

Outdated