☰ Mục lục series
Tài liệu NestJS tiếng Việt
  1. Nhập môn (Overview)
    1. Giới thiệu
    2. First steps
    3. Controllers
    4. Providers
    5. Modules
    6. Middleware
    7. Exception filters
    8. Pipes
    9. Guards
    10. Interceptors
    11. Custom decorators
  2. Nền tảng (Fundamentals)
    1. Custom providers
    2. Asynchronous providers
    3. Dynamic modules
    4. Injection scopes
    5. Circular dependency
    6. Module reference
    7. Lazy-loading modules
    8. Execution context
    9. Lifecycle events
    10. Discovery service
    11. Platform agnosticism
    12. Testing
  3. Kỹ thuật (Techniques)
    1. Configuration
    2. Database (SQL)
    3. MongoDB
    4. Validation
    5. Caching
    6. Serialization
    7. Versioning
    8. Task scheduling
    9. Queues
    10. Logging
    11. Cookies
    12. Events
    13. Compression
    14. File upload
    15. Streaming files
    16. HTTP module
    17. Session
    18. MVC
    19. Performance (Fastify)
    20. Server-Sent Events
  4. Bảo mật (Security)
    1. Authentication
    2. Authorization
    3. Encryption & Hashing
    4. Helmet
    5. CORS
    6. CSRF
    7. Rate limiting
    8. Vòng đời request
  5. OpenAPI (Swagger)
    1. Introduction
    2. Types and parameters
    3. Operations
    4. Security
    5. Mapped types
    6. Decorators
    7. CLI plugin
    8. Other features
  6. Recipes
    1. REPL
    2. CRUD generator
    3. SWC (fast compiler)
    4. Passport (auth)
    5. Hot reload
    6. MikroORM
    7. Router module
    8. Health checks (Terminus)
    9. CQRS
    10. Prisma
    11. Serve static
    12. Commander
    13. Async local storage
  7. WebSockets
    1. Gateways
    2. Exception filters
    3. Pipes
    4. Guards
    5. Interceptors
    6. Adapters
  8. Microservices
    1. Overview
    2. Redis
    3. MQTT
    4. NATS
    5. RabbitMQ
    6. Kafka
    7. gRPC
    8. Custom transporters
    9. Exception filters
    10. Pipes
    11. Pre-request hooks
    12. Guards
    13. Interceptors
  9. GraphQL
    1. Quick start
    2. Resolvers
    3. Mutations
    4. Subscriptions
    5. Scalars
    6. Directives
    7. Interfaces
    8. Unions and enums
    9. Field middleware
    10. Mapped types
    11. Plugins
    12. Complexity
    13. Extensions
    14. CLI plugin
    15. Generating SDL
    16. Sharing models
    17. Other features
    18. Federation
  10. CLI & triển khai
    1. CLI overview
    2. Workspaces
    3. Libraries
    4. Usage
    5. Scripts
    6. Deployment
    7. Standalone apps
  11. FAQ
    1. Serverless
    2. HTTP adapter
    3. Keep-Alive connections
    4. Global path prefix
    5. Raw body
    6. Hybrid application
    7. HTTPS & multiple servers
    8. Common errors
  12. Phụ lục
    1. Bảng thuật ngữ
Nhập môn (Overview)

Exception filters

Bản dịch từ docs.nestjs.com/exception-filters

Nest có sẵn một exceptions layer (tầng xử lý exception) chịu trách nhiệm xử lý tất cả các exception chưa được handle trong toàn bộ ứng dụng. Khi một exception không được code của bạn xử lý, tầng này sẽ bắt lấy nó và tự động gửi về một response thân thiện với người dùng.

Minh hoạ: Filter

Mặc định, việc này được thực hiện bởi một global exception filter có sẵn, xử lý các exception thuộc kiểu HttpException (và các class kế thừa nó). Khi một exception không được nhận diện (không phải HttpException cũng không phải class kế thừa từ HttpException), exception filter có sẵn sẽ sinh ra response JSON mặc định như sau:

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

Gợi ý Global exception filter hỗ trợ một phần thư viện http-errors. Về cơ bản, bất kỳ exception nào được throw ra mà chứa các thuộc tính statusCode và message sẽ được điền đúng và gửi về làm response (thay vì InternalServerErrorException mặc định cho các exception không nhận diện được).

Ghi chú người dịch Cơ chế này tương tự error-handling middleware của Express (hàm 4 tham số (err, req, res, next) đặt cuối chain) - chỉ khác là ở Nest bạn không cần tự viết middleware đó, framework đã có một filter mặc định lo phần “catch mọi lỗi rơi xuống đáy” cho bạn.

Throw exception chuẩn (Throwing standard exceptions)

Nest cung cấp sẵn class HttpException, được export từ package @nestjs/common. Đối với các ứng dụng HTTP REST/GraphQL API thông thường, best practice là gửi các đối tượng response HTTP chuẩn khi xảy ra một số điều kiện lỗi nhất định.

Ví dụ, trong CatsController, ta có method findAll() (một route handler cho GET). Giả sử route handler này throw ra một exception vì lý do nào đó. Để minh họa, ta hard-code như sau:

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

Gợi ý Ở đây ta dùng HttpStatus — một enum hỗ trợ được import từ package @nestjs/common.

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

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

Constructor của HttpException nhận hai tham số bắt buộc, quyết định nội dung response:

  • Tham số response định nghĩa phần body JSON của response. Nó có thể là một string hoặc một object như mô tả bên dưới.
  • Tham số status định nghĩa mã trạng thái HTTP.

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

  • statusCode: mặc định là mã trạng thái HTTP được truyền trong tham số status
  • message: mô tả ngắn gọn về lỗi HTTP dựa trên status

Để chỉ override phần message của body JSON response, hãy truyền vào một string ở tham số response. Để override toàn bộ body JSON response, hãy truyền vào một object ở tham số response. Nest sẽ tuần tự hóa (serialize) object đó và trả về làm body JSON của response.

Tham số thứ hai của constructor - status - phải là một mã trạng thái HTTP hợp lệ. Best practice là dùng enum HttpStatus được import từ @nestjs/common.

Có một tham số thứ ba (tùy chọn) của constructor - options - dùng để cung cấp nguyên nhân lỗi (cause). Object cause này không được tuần tự hóa vào response object, nhưng nó hữu ích cho mục đích logging, cung cấp thông tin có giá trị về lỗi gốc bên trong đã khiến HttpException được throw ra.

Đây là ví dụ override toàn bộ response body và cung cấp một error cause:

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

Với đoạn code trên, response sẽ trông như sau:

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

Ghi log exception (Exceptions logging)

Mặc định, exception filter không ghi log các exception có sẵn như HttpException (và bất kỳ exception nào kế thừa từ nó). Khi các exception này được throw, chúng sẽ không xuất hiện trên console, vì được coi là một phần của luồng hoạt động bình thường của ứng dụng. Hành vi tương tự cũng áp dụng cho các exception có sẵn khác như WsException và RpcException.

Tất cả các exception này đều kế thừa từ base class IntrinsicException, được export từ package @nestjs/common. Class này giúp phân biệt giữa các exception là một phần của hoạt động bình thường của ứng dụng và các exception không phải vậy.

Nếu bạn muốn ghi log các exception này, bạn có thể tạo một custom exception filter. Chúng ta sẽ giải thích cách làm điều này ở phần Exception filters bên dưới.

Theo dõi lỗi ở môi trường production (Tracking errors in production)

Một filter quyết định client sẽ thấy gì. Tự bản thân nó không cho bạn biết rằng một TypeError đã bắt đầu xảy ra trong OrdersService mười hai phút trước, trên 4% lượt checkout, chỉ với những khách hàng có giỏ hàng chứa sản phẩm giảm giá - và chắc chắn nó cũng không cho bạn biết dòng code nào đã throw ra lỗi đó.

Cách xử lý thông thường là ghi log stack trace và hy vọng ai đó sẽ grep ra nó sau này. Cách này nhanh chóng bộc lộ hạn chế: một stack trace trong file log trỏ đến output đã compile (/var/app/current/dist/orders/orders.service.js:35), không có ngữ cảnh source code, và không cho bạn biết đây là lần đầu tiên xảy ra hay lần thứ mười nghìn.

NestJS Observe coi một lỗi chưa được xử lý là một đối tượng hạng nhất (first-class object) thay vì một dòng log. Mọi lỗi đến được exceptions layer đều được ghi lại cùng với request đã gây ra nó, và:

  • Stack trace đi kèm source code. Frame được resolve thông qua source map trở về src/orders/orders.service.ts:35, và các dòng xung quanh được hiển thị trực tiếp - bạn đọc được đoạn code gây lỗi ngay trong error card, mà không cần clone bất cứ thứ gì.
  • Các lần xảy ra được nhóm lại thành defect. Các lỗi có cùng class và hình dạng stack sẽ được gộp lại thành một nhóm có fingerprint, kèm số lượng, thời điểm xuất hiện đầu tiên/gần nhất, và bản release đã gây ra nó. “Mới xuất hiện từ v2.4.1” là một thông tin bạn có thể đọc trực tiếp trên trang, thay vì phải tự suy luận.
  • Lỗi giữ nguyên ngữ cảnh của nó. Trace mà nó thuộc về, user đã gặp phải, các log được ghi trong request đó, và các span đã chạy trước khi throw đều được đính kèm, để bạn thấy được request đang làm gì khi xảy ra lỗi.
Error card with source context

Vì lỗi đã tự mang theo code của nó, chỉ một cú click là có thể chuyển toàn bộ thông tin này cho một coding agent: Copy agent prompt đóng gói lỗi, stack trace đã rút gọn kèm dòng source code, các span chậm, và các log xung quanh thành một prompt độc lập cho Claude Code, Cursor, hoặc bất kỳ công cụ nào đang mở repository của bạn.

Lưu ý rằng điều này bổ sung cho các filter được mô tả trong chương này, chứ không thay thế chúng - filter vẫn định hình response, còn instrumentation quan sát những gì đã xảy ra trên đường đi. Xem chương Observability để biết cách thiết lập, và Dashboard để biến một lỗi lặp lại thành một issue được theo dõi, tự xác minh khi nào nó đã được fix.

Custom exception

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

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

Vì ForbiddenException kế thừa từ base HttpException, nó sẽ hoạt động liền mạch với exception handler có sẵn, do đó ta có thể dùng nó bên trong method findAll().

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

HTTP exception có sẵn (Built-in HTTP exceptions)

Nest cung cấp sẵn một tập hợp các exception chuẩn kế thừa từ base HttpException. Các exception này được export từ package @nestjs/common, và đại diện cho phần lớn các HTTP exception 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

Tất cả các exception có sẵn đều có thể cung cấp cả cause và mô tả lỗi thông qua tham số options:

throw new BadRequestException('Something bad happened', {
  cause: new Error(),
  description: 'Some error description',
});

Với đoạn code trên, response sẽ trông như sau:

{
  "message": "Something bad happened",
  "error": "Some error description",
  "statusCode": 400
}

Mã lỗi máy có thể đọc được (Machine-readable error codes)

status và message của một exception mô tả lỗi khá tốt cho con người, nhưng lại bất tiện để client dựa vào đó rẽ nhánh xử lý. Nhiều lỗi khác nhau - email không hợp lệ, mật khẩu yếu - đều có thể xuất hiện dưới dạng 400 Bad Request, buộc client phải parse chuỗi message dễ đọc cho con người để phân biệt chúng.

Để tránh điều này, hãy truyền một errorCode thông qua tham số options. Đây là một định danh ổn định, máy có thể đọc được, được tuần tự hóa vào response body:

throw new BadRequestException('Password is too weak', {
  errorCode: 'WEAK_PASSWORD',
});

Response khi đó sẽ mang theo mã lỗi cùng với các field thông thường:

{
  "message": "Password is too weak",
  "errorCode": "WEAK_PASSWORD",
  "statusCode": 400
}

errorCode là tùy chọn và có thể kết hợp với cause và description. Nó cũng có sẵn trên chính HttpException, nên các custom exception cũng có thể set nó:

throw new HttpException(
  'Forbidden',
  HttpStatus.FORBIDDEN,
  { errorCode: 'ACCOUNT_SUSPENDED' },
);

Gợi ý Khác với cause, vốn dùng cho mục đích logging và không bao giờ được tuần tự hóa, errorCode là một phần của response body và được thiết kế để client sử dụng.

Ghi chú người dịch Đây là lý do thực tế nên dùng errorCode thay vì để frontend so sánh chuỗi message: message có thể đổi câu chữ (sửa lỗi chính tả, đổi ngôn ngữ, thêm chi tiết) mà không báo trước, còn errorCode là một hợp đồng (contract) ổn định giữa BE và FE, giống hệt việc bạn định nghĩa các mã lỗi cố định trong response của API Laravel hay Go thay vì if/else theo câu thông báo lỗi.

Exception filters

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

Hãy cùng tạo một exception filter chịu trách nhiệm bắt các exception là instance của class HttpException, và triển khai logic response tùy chỉnh cho chúng. Để làm điều này, ta cần truy cập các object Request và Response của platform bên dưới. Ta sẽ truy cập object Request để lấy ra url gốc và đưa vào thông tin logging. Ta sẽ dùng object Response để chủ động kiểm soát response gửi đi, bằng method response.json().

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,
      });
  }
}
import { Catch, HttpException } from '@nestjs/common';

@Catch(HttpException)
export class HttpExceptionFilter {
  catch(exception, host) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();
    const status = exception.getStatus();

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

Gợi ý Tất cả exception filter đều phải implement generic interface ExceptionFilter<T>. Điều này yêu cầu bạn cung cấp method catch(exception: T, host: ArgumentsHost) với chữ ký được chỉ định. T biểu thị kiểu của exception.

Cảnh báo Nếu bạn đang dùng @nestjs/platform-fastify, bạn có thể dùng response.send() thay vì response.json(). Đừng quên import đúng type từ fastify.

Decorator @Catch(HttpException) gắn metadata cần thiết vào exception filter, cho Nest biết rằng filter này chỉ tìm các exception thuộc kiểu HttpException và không gì khác. Decorator @Catch() có thể nhận một tham số duy nhất, hoặc một danh sách phân tách bằng dấu phẩy. Điều này cho phép bạn thiết lập filter cho nhiều kiểu exception cùng lúc.

Arguments host

Hãy xem xét các tham số của method catch(). Tham số exception là exception object đang được xử lý. Tham số host là một object ArgumentsHost - một tiện ích cho bạn quyền truy cập vào các tham số được truyền cho handler gốc, bất kể nó được gọi trong ngữ cảnh nào. Trong đoạn code mẫu ở trên, ta dùng các helper method của nó để lấy ra object Request và Response thuộc về request đã phát sinh exception. ArgumentsHost được trình bày đầy đủ ở chương execution context.

Lý do cho mức độ trừu tượng hóa này là ArgumentsHost hoạt động trong mọi ngữ cảnh - ngữ cảnh HTTP server mà ta đang dùng ở đây, nhưng cũng cả Microservices và WebSockets. Chương execution context sẽ cho thấy cách truy cập các tham số bên dưới cho bất kỳ ngữ cảnh nào thông qua cùng một object, đó chính là điều cho phép bạn viết một exception filter duy nhất hoạt động trên tất cả các ngữ cảnh đó.

Ghi chú người dịch Nếu bạn chỉ làm REST API thuần với Express/Fastify, khái niệm ArgumentsHost sẽ thấy hơi dư vì chỉ có một loại req/res. Nó tồn tại vì Nest là framework đa giao thức: cùng một filter phải chạy được cho HTTP, WebSocket gateway, hoặc RPC microservice, mỗi loại có “hình dạng” tham số khác nhau - switchToHttp() chỉ là cách bạn nói rõ “tôi muốn đọc theo hình dạng HTTP” từ object trừu tượng đó.

Gắn filter (Binding filters)

Hãy gắn HttpExceptionFilter mới của ta vào method create() của CatsController.

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

Gợi ý Decorator @UseFilters() được import từ package @nestjs/common.

Ta đã dùng decorator @UseFilters() ở đây. Tương tự decorator @Catch(), nó có thể nhận một instance filter duy nhất, hoặc một danh sách các instance filter phân tách bằng dấu phẩy. Ở đây, ta đã tạo instance của HttpExceptionFilter ngay tại chỗ. Ngoài ra, bạn cũng có thể truyền vào class (thay vì instance), để framework chịu trách nhiệm khởi tạo, và cho phép tiêm phụ thuộc (dependency injection).

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

Gợi ý Nên ưu tiên gắn filter bằng class thay vì instance khi có thể. Cách này giảm mức sử dụng bộ nhớ vì Nest có thể dễ dàng tái sử dụng các instance của cùng một class trong toàn bộ module của bạn.

Trong ví dụ trên, HttpExceptionFilter chỉ được áp dụng cho một route handler create() duy nhất, tức là có phạm vi method (method-scoped). Exception filter có thể có phạm vi ở nhiều cấp độ khác nhau: phạm vi method của controller/resolver/gateway, phạm vi controller, hoặc phạm vi global. Ví dụ, để thiết lập một filter ở phạm vi controller, bạn làm như sau:

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

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

Để tạo một filter phạm vi global, bạn làm như sau:

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

Cảnh báo Method useGlobalFilters() không thiết lập filter cho gateway hoặc hybrid application.

Filter phạm vi global được dùng trong toàn bộ ứng dụng, cho mọi controller và mọi route handler. Về mặt tiêm phụ thuộc, các global filter được đăng ký từ bên ngoài bất kỳ module nào (dùng useGlobalFilters() như ví dụ trên) không thể inject dependency vì việc này được thực hiện bên ngoài ngữ cảnh của bất kỳ module nào. Để giải quyết vấn đề này, bạn có thể đăng ký một filter phạm vi global trực tiếp từ bất kỳ module nào bằng cấu trúc sau:

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

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

Gợi ý Khi dùng cách tiếp cận này để thực hiện tiêm phụ thuộc cho filter, lưu ý rằng bất kể module nào được dùng để khai báo cấu trúc này, filter thực chất vẫn là global. Vậy nên đặt ở đâu? Hãy chọn module nơi filter (HttpExceptionFilter trong ví dụ trên) được định nghĩa. Ngoài ra, useClass không phải là cách duy nhất để xử lý việc đăng ký custom provider. Tìm hiểu thêm tại đây.

Gợi ý Các exception được throw từ middleware cũng được xử lý bởi exceptions layer. Vì middleware chạy trước khi một route handler được chọn, chỉ có exception filter global mới được áp dụng (app.useGlobalFilters() hoặc APP_FILTER). Các binding @UseFilters() phạm vi method và phạm vi controller sẽ không được gọi.

Bạn có thể thêm bao nhiêu filter tùy ý bằng kỹ thuật này; chỉ cần thêm từng filter vào mảng providers.

Bắt tất cả (Catch everything)

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

Ví dụ dưới đây độc lập với platform: nó gửi response thông qua HTTP adapter thay vì thao tác trực tiếp với các object Request và Response đặc thù của từng platform, nên cùng một filter này hoạt động trên cả Express lẫn Fastify.

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

@Catch()
export class CatchEverythingFilter implements ExceptionFilter {
  constructor(private readonly httpAdapterHost: HttpAdapterHost) {}

  catch(exception: unknown, host: ArgumentsHost): void {
    // In certain situations `httpAdapter` might not be available in the
    // constructor method, thus we should resolve it here.
    const { httpAdapter } = this.httpAdapterHost;

    const ctx = host.switchToHttp();

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

    const responseBody = {
      statusCode: httpStatus,
      timestamp: new Date().toISOString(),
      path: httpAdapter.getRequestUrl(ctx.getRequest()),
    };

    httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
  }
}

Cảnh báo Khi kết hợp một filter bắt-tất-cả với một filter được gắn cho một kiểu exception cụ thể, hãy khai báo filter bắt-tất-cả trước, để filter cụ thể hơn vẫn có thể xử lý kiểu mà nó được gắn vào.

Ghi chú người dịch Nếu quen kiểu middleware Express xếp theo thứ tự “cái cụ thể trước, catch-all sau cùng”, quy tắc ở đây dễ gây nhầm vì ngược lại. Lý do là Nest không chạy các filter cùng cấp theo kiểu chain gọi next() như Express - nó chọn filter khớp dựa trên metadata @Catch(), và khai báo filter bắt-tất-cả trước chỉ đơn giản là để nó không “che” mất các filter khai báo sau nó trong cùng danh sách.

Kế thừa (Inheritance)

Thông thường, bạn sẽ tạo các exception filter được tùy chỉnh hoàn toàn để đáp ứng yêu cầu ứng dụng của mình. Tuy nhiên, có những trường hợp bạn muốn kế thừa global exception filter có sẵn và chỉ override hành vi của nó cho một số điều kiện nhất định.

Để ủy quyền xử lý exception cho filter gốc, bạn cần kế thừa BaseExceptionFilter và gọi method catch() được kế thừa.

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);
  }
}
import { Catch } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';

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

Cảnh báo Các filter phạm vi method và phạm vi controller kế thừa BaseExceptionFilter không nên được khởi tạo bằng new. Thay vào đó, hãy để framework tự động khởi tạo chúng.

Các filter global có thể kế thừa filter gốc. Việc này có thể được thực hiện theo một trong hai cách.

Cách thứ nhất là inject tham chiếu HttpAdapter khi khởi tạo custom global filter:

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

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

  await app.listen(process.env.PORT ?? 3000);
}
await bootstrap();

Cách thứ hai là dùng token APP_FILTER như đã trình bày ở đây.

Nội dung gốc: NestJS Documentation © Kamil Myśliwiec, giấy phép MIT. Bản dịch và ghi chú: Lê Văn Dũng.