+11

Các concepts cơ bản trong Nestjs

Bài viết được lược dịch từ:

Tổng quan

Theo như document của nestjs ta có 8 khái niệm cơ bản như sau:

  • Controllers
  • Providers
  • Modules
  • Middleware
  • Exception Filters
  • Pipes
  • Guards
  • Interceptors

3 khái niệm đầu tiên Controllers, Providers, Modules sẽ lần lượt đảm nhiệm các nhiệm vụ liên quan đến routing các request từ client cũng như business logic.

5 khái niệm còn lại đều liên quan đến đường đi của request cũng như response và được minh hoạ như trong sơ đồ dưới đây

zs4f5nwd0mf2m95sdpcye34x1gc8

Các đường chấm màu ghi sẽ là HTTP request, HTTP response và Exception (trong thực tế thì Exception cũng sẽ được trả về dưới hình thức HTTP response). App Module sẽ là root module chứa các modules con ở phía trong nó, trong mỗi module con sẽ lại có các controller và service.

Trên đường đi của Request sẽ là:

  • Middleware
  • Guard
  • Interceptor
  • Pipe

Còn với Response sẽ là:

  • Interceptor
  • Exception Filter (với trường hợp của Exception)

Khi đăng kí (Exception filter, Pipe, Guard, Interceptor) với app, ta sẽ lần lượt có 4 levels như sau:

  • Global
  • Controller
  • Method
  • Param

Providers

Sẽ được khai báo kèm theo @Injectable() với mục đích dùng cho DI. Thông thường đây sẽ là nơi thực hiện các task được uỷ nhiệm từ phía controller.

Middleware

Là hàm được gọi phía trước route, có khả năng truy cập đến Request Object, Response Object. Có khả năng đảm nhận những công việc sau:

  • Thay đổi, chỉnh sửa Request Object, Response Object.
  • Kết thúc vòng đời của Request hoặc Response.

Cũng được khai báo thêm với Injectable

import {Injectable, NestMiddleware} from "@nestjs/common";
import {Request, Response, NextFunction} from "express";

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

// 関数として定義する
export function logger(req: Request, res: Response, next: NextFunction) {
  console.log(`Request...`);
  next();
}

Có 2 cách sử dụng Middleware:

① Sử dụng theo từng module, controller cụ thể.

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes(CatsController);
  }
}

② Sử dụng cho global module.

const app = await NestFactory.create(AppModule);
app.use(LoggerMiddleware);

Exception filters

Thường được định nghĩa với decorator là Catch(). Được sử dụng để "bắt" các ngoại lệ không được xử lí, nó sẽ kiểm soát các ngoại lệ (HttpException) khi ngoại lệ được gửi về phía client.

Cơ chế mặc định ở đây đó là nó sẽ tìm ra ngoại lệ, sau đó sẽ chuyển sang dạng HTTP Response.

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

    // Chỉnh sửa lại response
    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}

Exception filter có thể được sử dụng cho cả 3 level: method, controller, global với cách thức như sau:

① Method

@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
  // ...
}

② Controller

@UseFilters(HttpExceptionFilter)
export class CatsController {}

③ Global

const app = await NestFactory.create(AppModule);
app.useGlobalFilters(HttpExceptionFilter);

Pipes

Được định nghĩa đi kèm với @Injectable(), và implement PipeTransform interface. Mục đích chính của việc sử dụng pipe đó là:

  • Thay đổi dữ liệu đầu vào (format dữ liệu đầu vào)
  • Validation: nếu dữ liệu không vấn đề gì thì ta sẽ tiến hành xử lí tiếp theo còn nếu không thì sẽ đưa ra ngoại lệ.

Có tất cả 9 loại pipes:

  • ValidationPipe
  • ParseIntPipe
  • ParseFloatPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe
  • ParseEnumPipe
  • DefaultValuePipe
  • ParseFilePipe

Ví dụ:

@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
  transform(value: string, metadata: ArgumentMetadata): number {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException("Validation failed");
    }
    return val;
  }
}

Pipe được sử dụng ở cả 4 levels: param, method, controller, global.

@Get(':id')
async findOne(@Param('id', ParseIntPipe) id) {
  return this.catsService.findOne(id);
}
@Post()
@UsePipes(ValidationPipe)
async create(@Body() createCatDto: CreateCatDto) {
  // ...
}
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(ValidationPipe);

Guards

Được định nghĩa với @Injectable(), implements CanActivate interface

Thường có nhiệm vụ quyết định xem có nên xử lí request không dựa theo (quyền, role, ACL, ...)

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest<Request>();
    return validateRequest(request);
}

Guard có thể được sử dụng ở method, controller, global levels.

@Post()
@UseGuards(AuthGuard)
async create(@Body() createCatDto: CreateCatDto) {
  // ...
}
@UseGuards(AuthGuard)
export class CatsController {}
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(AuthGuard);

Interceptors

Được định nghĩa với @Injectable(), implements NestInterceptor interface.

Có thể thực hiện được những điều sau đây:

  • Tiền / Hậu xử lí request.
  • Thay đổi giá trị trả về của hàm.
  • Thay đổi ngoại lệ được đưa ra từ hàm.
  • Mở rộng hành vi của hàm
  • Ghi đè hàm
@Injectable()
export class LoggingInterceptor 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`)));
  }
}

Nó được sử dụng ở method, controller, global levels

@Post()
@UseInterceptors(LoggingInterceptor)
async create(@Body() createCatDto: CreateCatDto) {
  // ...
}
@UseInterceptors(LoggingInterceptor)
export class CatsController {}
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(LoggingInterceptor);

Về repository

Repository trong một project NestJS sẽ đảm nhận nhiệm vụ giao tiếp với DB cũng như tiến hành chỉnh sửa, thêm mới dữ liệu.

Ngoài ra việc giao tiếp với các hệ thống bên ngoài cũng do repository đảm nhận

Tổng kết

Gom tất cả các thành phần đã kể trên lại, chúng ta có thể phác hoạ ra mô hình tổng quan của một ứng dụng sử dụng NestJS như sau:

Screen Shot 2023-10-09 at 12 42 09

Cảm ơn bạn đọc đã nồng nhiệt đón nhận bài viết của tôi, hi vọng bài viết đã đem lại cho bạn đọc một cái nhìn tổng quan nhất về một ứng dụng NestJS.

Hẹn gặp lại bạn đọc ở những bài viết kế tiếp, xin chân thành cảm ơn.

Happy Learning.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí