Tổng quan – NestJS

Exception filters

Nest đi kèm với một lớp exception tích hợp, lớp này chịu trách nhiệm xử lý tất cả các trường hợp exception chưa được xử lý trên một ứng dụng. Khi một exception không được mã ứng dụng của bạn xử lý, nó sẽ bị lớp này bắt giữ, lớp này sau đó sẽ tự động gửi một phản hồi thân thiện với người dùng thích hợp.

Ngoài ra, hành động này được thực hiện bởi một bộ lọc exception chung tích hợp sẵn, bộ lọc này xử lý các exception của kiểu HttpException (và các lớp con của nó). Khi một ngoại lệ không được công nhận (không phải là HttpException cũng không phải là một lớp kế thừa từ HttpException), bộ lọc exception tích hợp sẽ tạo ra phản hồi JSON mặc định sau:

{
  "statusCode": 500,
  "message": "Internal server error"
}

Throwing standard exceptions

Nest cung cấp lớp HttpException tích hợp sẵn, được hiển thị từ gói @nestjs/common. Đối với các ứng dụng dựa trên API HTTP REST / GraphQL điển hình, cách tốt nhất là gửi các đối tượng response HTTP tiêu chuẩn khi các điều kiện lỗi nhất định xảy ra.

Ví dụ, trong CatsController, chúng ta có một phương thức findAll() (một route handler GET). Giả sử rằng route hander này ném ra một exception vì một số lý do. Để chứng minh điều này, chúng tôi sẽ sửa nó như sau:

cats.controller.ts

@Get()
async findAll() {
  throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}

HINT Chúng tôi đã sử dụng HttpStatus ở đây. Đây là một enum helper được nhập từ gói @nestjs/common.

Khi client gọi endpoint này, response sẽ giống như sau:

{
  "statusCode": 403,
  "message": "Forbidden"
}

Hàm tạo HttpException nhận hai đối số bắt buộc để xác định phản hồi:

  • Đối số response xác định body response JSON. Nó có thể là 1 string hoặc 1 object như mô ta bên dưới.
  • Đối số status được định nghĩa như ở HTTP status code.

Theo mặc định, body response JSON chứa hai thuộc tính:

  • statusCode: mặc định thành HTTP status code được cung cấp ở đối số status
  • message: một đoạn mô ta ngắn về lỗi HTTP dựa trên status

Để chỉ ghi đè phần thông báo của body response JSON, hãy cung cấp một string trong đối số response. Để ghi đè toàn bộ body response JSON, hãy chuyển một đối tượng vào đối số response. Nest sẽ chuyển hóa đối tượng và trả về nó dưới dạng body response JSON.

Đối số phương thức khởi tạo thứ hai – status – phải là HTTP status code hợp lệ. Cách tốt nhất là sử dụng enum HttpStatus được nhập từ @nestjs/common.

cats.controller.ts

@Get()
async findAll() {
  throw new HttpException({
    status: HttpStatus.FORBIDDEN,
    error: 'This is a custom message',
  }, HttpStatus.FORBIDDEN);
}

Sử dụng ở trên, đây là cách response sẽ trông như này:

{
  "status": 403,
  "error": "This is a custom message"
}

Custom exceptions

Trong nhiều trường hợp, bạn sẽ không cần phải viết các exception tùy chỉnh và có thể sử dụng exception Nest HTTP tích hợp sẵn, như được mô tả trong phần tiếp theo. Nếu bạn cần tạo các exception tùy chỉnh, bạn nên tạo hệ thống phân cấp exception của riêng mình, nơi các exception tùy chỉnh của bạn kế thừa từ lớp HttpException cơ sở. Với cách tiếp cận này, Nest sẽ nhận ra các trường hợp exception của bạn và tự động xử lý các response lỗi. Hãy triển khai một exception tùy chỉnh như vậy:

forbidden.exception.ts

export class ForbiddenException extends HttpException {
  constructor() {
    super('Forbidden', HttpStatus.FORBIDDEN);
  }
}

Vì ForbiddenException mở rộng HttpException cơ sở, nó sẽ hoạt động liền mạch với exception handler được tích hợp sẵn và do đó chúng ta có thể sử dụng nó bên trong phương thức findAll().

cats.controller.ts

@Get()
async findAll() {
  throw new ForbiddenException();
}

Built-in HTTP exceptions

Nest cung cấp một tập hợp các exception tiêu chuẩn kế thừa từ HttpException cơ sở. Chúng được hiển thị từ gói @nestjs/common và đại diện cho nhiều exception HTTP phổ biến nhất:

  • BadRequestException
  • UnauthorizedException
  • NotFoundException
  • ForbiddenException
  • NotAcceptableException
  • RequestTimeoutException
  • ConflictException
  • GoneException
  • HttpVersionNotSupportedException
  • PayloadTooLargeException
  • UnsupportedMediaTypeException
  • UnprocessableEntityException
  • InternalServerErrorException
  • NotImplementedException
  • ImATeapotException
  • MethodNotAllowedException
  • BadGatewayException
  • ServiceUnavailableException
  • GatewayTimeoutException
  • PreconditionFailedException

Exception filters

Mặc dù bộ lọc exception cơ sở (tích hợp sẵn) có thể tự động xử lý nhiều trường hợp cho bạn, bạn có thể muốn toàn quyền kiểm soát lớp exception. Ví dụ: bạn có thể muốn thêm ghi nhật ký hoặc sử dụng một lược đồ JSON khác dựa trên một số yếu tố động. Bộ lọc exception được thiết kế cho chính xác mục đích này. Chúng cho phép bạn kiểm soát luồng kiểm soát chính xác và nội dung của response được gửi lại cho client.

Hãy tạo một bộ lọc exception chịu trách nhiệm bắt các exception là một instance của lớp HttpException và triển khai logic response tùy chỉnh cho chúng. Để làm điều này, chúng tôi sẽ cần truy cập vào các đối tượng Request và Respose của nền tảng cơ bản. Chúng tôi sẽ truy cập đối tượng Request để có thể lấy ra url ban đầu và đưa url đó vào thông tin ghi nhật ký. Chúng tôi sẽ sử dụng đối tượng Response để kiểm soát trực tiếp response được gửi bằng cách sử dụng phương thức response.json().

http-exception.filter.ts

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,
      });
  }
}

HINT Tất cả các bộ lọc exception phải triển khai interface ExceptionFilter<T> chung. Điều này yêu cầu bạn cung cấp phương thức catch(exception: T, host: ArgumentsHost) với chữ ký được chỉ định của nó. T chỉ ra loại ngoại lệ..

@Catch(HttpException) decorator liên kết metadata bắt buộc với bộ lọc exception, cho Nest biết rằng bộ lọc cụ thể này đang tìm kiếm các exception của loại HttpException và không có gì khác. @Catch() decorator có thể nhận một tham số duy nhất hoặc một danh sách được phân tách bằng dấu phẩy. Điều này cho phép bạn thiết lập bộ lọc cho một số loại exception cùng một lúc.

Arguments host

Hãy xem các tham số của phương thức catch(). Tham số exception là đối tượng exception hiện đang được xử lý. Tham số máy chủ lưu trữ là một đối tượng ArgumentsHost. ArgumentsHost là một đối tượng tiện ích mạnh mẽ mà chúng ta sẽ xem xét kỹ hơn trong chương ngữ cảnh thực thi *. Trong mẫu mã này, chúng tôi sử dụng nó để lấy tham chiếu đến các đối tượng Request và Response đang được chuyển đến response handler ban đầu (trong controller nơi bắt nguồn exception). Trong mẫu mã này, chúng tôi đã sử dụng một số phương thức trợ giúp trên ArgumentsHost để có được các đối tượng Request và Response mong muốn. Tìm hiểu thêm về ArgumentsHost tại đây.

Lý do cho mức độ trừu tượng này là các function của ArgumentsHost trong tất cả các ngữ cảnh (ví dụ: bối cảnh máy chủ HTTP mà chúng tôi đang làm việc với Microservices và WebSockets). Trong chương ngữ cảnh thực thi, chúng ta sẽ xem cách chúng ta có thể truy cập các đối số cơ bản thích hợp cho bất kỳ ngữ cảnh thực thi nào với sức mạnh của ArgumentsHost và các hàm trợ giúp của nó. Điều này sẽ cho phép chúng tôi viết các bộ lọc exception chung hoạt động trên tất cả các ngữ cảnh.

Binding filters

Hãy liên kết HttpExceptionFilter mới của chúng ta với phương thức create() của CatsController.

cats.controller.ts

@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}

HINT @UseFilters() decorator được nhập từ gói @nestjs/common.

Chúng tôi đã sử dụng @UseFilters() decorator ở đây. Tương tự như @Catch() decorator, nó có thể sử dụng một instance bộ lọc duy nhất hoặc một danh sách các instance bộ lọc được phân tách bằng dấu phẩy. Ở đây, chúng tôi đã tạo instance HttpExceptionFilter tại chỗ. Ngoài ra, bạn có thể truyền qua lớp (thay vì một instance), để lại trách nhiệm khởi tạo cho khung và cho phép dependency injection.

cats.controller.ts

@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}

HINT Ưu tiên áp dụng bộ lọc bằng cách sử dụng các lớp thay vì các instance khi có thể. Nó làm giảm mức sử dụng bộ nhớ vì Nest có thể dễ dàng sử dụng lại các instance của cùng một lớp trên toàn bộ mô-đun của bạn

Trong ví dụ trên, HttpExceptionFilter chỉ được áp dụng cho route handler create() duy nhất, làm cho nó có phạm vi phương thức. Các bộ lọc exception có thể được xác định phạm vi ở các cấp độ khác nhau: phạm vi phương pháp, phạm vi controller hoặc phạm vi toàn cục. Ví dụ: để thiết lập bộ lọc dưới dạng phạm vi controller, bạn sẽ thực hiện như sau:

cats.controller.ts

@UseFilters(new HttpExceptionFilter())
export class CatsController {}

Cấu trúc này thiết lập HttpExceptionFilter cho mọi route handler được xác định bên trong CatsController.

Để tạo bộ lọc phạm vi toàn cục, bạn sẽ thực hiện như sau:

main.ts

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();

WARNING Phương thức useGlobalFilters() hông thiết lập bộ lọc cho cổng hoặc ứng dụng kết hợp.

Bộ lọc phạm vi toàn cục được sử dụng trên toàn bộ ứng dụng, cho mọi controller và mọi route handler. Về phương diện dependency injection, các bộ lọc toàn cục được đăng ký từ bên ngoài của bất kỳ mô-đun nào (với useGlobalFilters() như trong ví dụ trên) không thể inject dependencies vì điều này được thực hiện bên ngoài ngữ cảnh của bất kỳ mô-đun nào. Để giải quyết vấn đề này, bạn có thể đăng ký bộ lọc phạm vi toàn cục trực tiếp từ bất kỳ mô-đun nào bằng cách sử dụng cấu trúc sau:

app.module.ts

import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {}

HINT Khi sử dụng cách tiếp cận này để thực hiện dependency injection cho bộ lọc, hãy lưu ý rằng bất kể mô-đun nơi cấu trúc này được sử dụng, bộ lọc trên thực tế là toàn cục. Điều này nên được thực hiện ở đâu? Chọn mô-đun nơi bộ lọc (HttpExceptionFilter trong ví dụ trên) được xác định. Ngoài ra, useClass không phải là cách duy nhất để giải quyết việc đăng ký provider tùy chỉnh. Tim hiểu thêm ở đây.

Bạn có thể thêm nhiều bộ lọc bằng kỹ thuật này nếu cần; chỉ cần thêm từng cái vào mảng providers.

Catch everything

Để bắt mọi exception chưa được xử lý (bất kể loại exception), hãy để trống danh sách tham số của @Catch() decorator, ví dụ: @Catch().

import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
} from '@nestjs/common';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}

Trong ví dụ trên, bộ lọc sẽ bắt từng exception được ném ra, bất kể loại (lớp) của nó.

Inheritance

Thông thường, bạn sẽ tạo các bộ lọc exception hoàn toàn tùy chỉnh được tạo thủ công để đáp ứng các yêu cầu ứng dụng của bạn. Tuy nhiên, có thể có các trường hợp sử dụng khi bạn chỉ muốn mở rộng bộ lọc exception chung mặc định được tích hợp sẵn và ghi đè hành vi dựa trên các yếu tố nhất định.

Để ủy quyền xử lý exception cho bộ lọc cơ sở, bạn cần mở rộng BaseExceptionFilter và gọi phương thức catch() được kế thừa.

all-exceptions.filter.ts

import { Catch, ArgumentsHost } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';

@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    super.catch(exception, host);
  }
}

WARNINGBộ lọc phạm vi phương pháp và bộ lọc phạm vi controller mở rộng BaseExceptionFilter không nên được khởi tạo bằng mới. Thay vào đó, hãy để khuôn khổ khởi tạo chúng tự động

Việc thực hiện ở trên chỉ là một lớp vỏ thể hiện cách tiếp cận. Việc triển khai bộ lọc ngoại lệ mở rộng sẽ bao gồm logic nghiệp vụ được điều chỉnh của bạn (ví dụ: xử lý các điều kiện khác nhau).

Bộ lọc chung có thể mở rộng bộ lọc cơ sở. Điều này có thể được thực hiện theo một trong hai cách.

Phương pháp đầu tiên là chèn tham chiếu HttpServer khi khởi tạo bộ lọc chung tùy chỉnh:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const { httpAdapter } = app.get(HttpAdapterHost);
  app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));

  await app.listen(3000);
}
bootstrap();

Phương pháp thứ hai là sử dụng mã thông báo APP_FILTER như được hiển thị ở đây.

One Comment on “Tổng quan – NestJS

  1. Nếu một interceptor handle() được gọi ở bất kỳ đâu trên đường đi, phương thức create() sẽ không được thực thi. => Nếu một interceptor handle() “”không”” được gọi ở bất kỳ đâu trên đường đi, phương thức create() sẽ không được thực thi.

Leave a Reply

%d bloggers like this: