[Nest.js] 공부하기 - Overview

Junsoo Choi·2021년 7월 7일
0

본문은 Nest.js의 공식문서를 요약한 것으로 Nest.js를 대략적으로 파악하는 것에 초점을 두고 현재 단계에서 너무 디테일하다고 판단되는 부분들은 스킵하였습니다.

Overview

First steps

src
ㄴ app.controller.spec.ts
ㄴ app.controller.ts
ㄴ app.module.ts
ㄴ app.service.ts
ㄴ main.ts
  • app.controller.ts: A basic controller with a single route.
  • app.controller.spec.ts: The unit tests for the controller.
  • app.module.ts: The root module of the application.
  • app.service.ts: A basic service with a single method.
  • main.ts: The entry file of the application which uses the core function NestFactory to create a Nest application instance.

Nest aims to be a platform-agnostic(platform과 무관한) framework. express and fastify are supported out-of-the-box.

Controllers

Controllers are responsible for handling incoming requests and returing responses to the client.

The routing mechanism controls which controller receives which requests.

In Nest.js, we use classes and decorators. Decorators associate classes with required metadata and enable Nest to create a routing map.

Routing

import { Controller, Get } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }
}
  • @Controller('cats')를 통해 endpoint를 지정하고
  • @Get()을 통해 HTTP method를 지정한다.
@Controller('customers')
export class CustomersController {
 @Get('profile')
  getProfile(): string {
   	return 'customer profile!' 
  }
}
  • 위와 같이 작성하면 GET customers/profile에 바인드 된다.

위의 두 예시에서 볼 수 있듯이 endpoint에 바인드되는 method의 이름은 아무런 의미가 없다.

request handler는 default로 JS primitive의 경우 그대로 반환하고, JS object나 array의 경우 JSON-serialize한 뒤 return 한다. 설정을 변경하면 Library-specific(e.g., Express)한 response object를 반환할 수 있다.

@Get(':id')
findOne(@Param() params): string {
  console.log(params.id);
  return `This action returns a #${params.id} cat`;
}

위와 같이 하면 cats/1과 같은 request를 처리할 수 있다.

Request payloads

POSTbody로 넘어오는 DTO(Data Transfer Object) Schema를 선언할 때 Typescript의 interface를 사용하여도 되고, ES6의 class를 사용하여도 된다. 하지만 Typescript는 transpile되어 실행될 때 사라지기 때문에 Nest가 runtime때 참고할 수 없기 때문에 class형식으로 선언하는 것을 더 추천한다.

export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}

//controller
@Post()
async create(@Body() createCatDto: CreateCatDto) {
  return 'This action adds a new cat';
}

Providers

Providers are a fundamental concept in Nest. The main idea of a provider is that it can be injected as dependency; this means objects can create various relationships with each other. Providers are plain JS classes that are declared as providers in a module.

Dependency injection is basically providing the objects that an object needs (its dependencies) instead of having it construct them itself. It's a very useful technique for testing, since it allows dependencies to be mocked or stubbed out.

Services

This service will be responsible for data storage and retrieval, and is designed to be used by CatsController.

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];

  create(cat: Cat) {
    this.cats.push(cat);
  }

  findAll(): Cat[] {
    return this.cats;
  }
}

The only new feature is that it uses the @Injectable() decorator. The @Injectable() decorator attaches metadata, which declares that CatsService is a class that can be managed by the Nest IoC container.

IoC stands for Inversion of Control

@Controller('cats')
export class CatsController {
  // 위어서 선언한 @Injectable CatsService를 declar & initialize
  // Dependency Injection
  constructor(private catsService: CatsService) {}

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

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

Modules

A module is a class annotated with @Module() decorator. Decorator provides metadata that Nest makes use of to organize the application structure.

Each application has at least one module, a root module. The root module is the starting point Nest uses to build the application graph - the internal data structure nest uses to resolve module and provider relationships and dependencies.

@Module decorator는 하나의 object를 받으며 해당 object는 아래의 property들을 가질 수 있다

  • provider
  • controllers
  • imports
  • exports

The module encapsulates providers by default. This means that it's impossible to inject providers that are neighter directly part of the current module nor exported from the imported modules. Thus, you may consider the exported providers from a module as the module's public interface, or API.

In Nest, modules are singletons by default, and thus you can share the same instance of any provider betwween multiple modules effortlessly.

Also, Every module is automatically a shared module. Once created it can be reused by any module.

// cats/cats.module.ts
// CAT MODULE
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}
// app.module.ts
// ROOT MODULE
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class AppModule {}

Middleware

Middleware is a function which is called before the route handler. Middleware functions have access to the request and response objects.

You implement custom Nest middleware in either a function, or in a class with an @Injectable() decorator.

Class middleware

The class should implement the NestMiddleware interface, while the function does not have any special requirements.

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

*Nest middleware fully supports DI

// app.module.ts
@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('cats');
    //.forRoutes({ path: 'cats', method: RequestMethod.GET });
    //.forRoutes({ path: 'ab*cd', method: RequestMethod.ALL });
  }
}

Excluding routes

consumer
  .apply(LoggerMiddleware)
  .exclude(
    { path: 'cats', method: RequestMethod.GET },
    { path: 'cats', method: RequestMethod.POST },
    'cats/(.*)',
  )
  .forRoutes(CatsController);

Functional middleware

// logger.middleware.ts
export function logger(req: Request, res: Response, next: NextFunction) {
  console.log(`Request...`);
  next();
};
// app.module.ts
consumer
  .apply(logger)
  .forRoutes(CatsController);

Multiple middleware

consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);

Applied 3 middlewares, cors, helmet, logger

Global middleware

const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);

Exception filters

Nest comes with a built-in exceptions layer which is responsible for processing all unhandled exceptions across an application. When an exception is not handled by your application code, it is caught by this layer.

Exception filters are designed for cases where you may want full control over the exception layer. They let you control the exact flow of control and the content of the response sent back to the client.

Pipes

A pipe is a class annotated with the @Injectable() decorator. Pipes should implement PipeTransform interface.

Pipes have two typical use cases:

  • transformation: transform input data to the desired form
  • validation: evaluate input data. if invalid, throw exception

In both cases, pipes operate on the arugments being processed by a controller route handler. Nest interposes a pipe just before a method is invoked, and the pipe receives the arguments destined for the method and operates on them.

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

parameter로 넘어오는 id가 integer 타입인지 확인하고 아닐경우 400 Bad Request를 발생시킨다.

Custom pipes

Every pipe must implement the transform() method to filtull the PipeTransform interface contract. This method has two parameters, value and metadata.

@Injectable()
export class ValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    return value;
  }
}

Guards

A guard is a class annotated with the @Injectable() decorator. Guards should implement the CanActivate interface.

Guards have a single responsibility. They determine whether a given request will be handled by the route handler or not, depending on certain conditions (like permissions, roles, ACLs, etc.) present at run-time. This is often referred to as authorization. Authorization (and its cousin, authentication, with which it usually collaborates) has typically been handled by middleware in traditional Express applications.

profile
Youtube: 개발자준

0개의 댓글

관련 채용 정보