[NestJS] Xử lý lỗi tập trung với Exception Filters: Chuẩn hóa API Response
Khi xây dựng API, việc xử lý lỗi (Error Handling) cực kỳ quan trọng. Nếu không cẩn thận, application của bạn có thể trả về một object lỗi "lõng chõng" rất khó đọc, hoặc tệ hơn là làm sập NodeJS process. Hôm nay, chúng ta sẽ cùng tìm hiểu cách NestJS giải quyết triệt để bài toán này thông qua khái niệm Exception Filter.
1. Ẩn dụ: Đội ngũ dọn dẹp và viết báo cáo sự cố 👷♂️
Hãy tưởng tượng bạn đang điều hành một khách sạn (Application):
- Đôi khi, một nhân viên phòng bếp vô tình làm đổ đồ ăn (Code throw ra lỗi
HttpException). - Cách truyền thống (Try/Catch ở mọi nơi): Mỗi nhân viên phải tự chuẩn bị một cái chổi, tự dọn dẹp và tự viết giải trình gửi cho Giám đốc mỗi khi có lỗi (Phải bọc hàm bằng
try/catchcồng kềnh trong mỗi Controller). - Cách dùng Exception Filter: Khách sạn thuê hẳn một "Đội ngũ dọn dẹp và xử lý sự cố chuyên nghiệp". Khi có bất cứ lỗi gì xảy ra, nhân viên chỉ việc hô to (throw lỗi). Đội ngũ này lập tức xuất hiện, dọn dẹp sạch sẽ và điền một biểu mẫu báo cáo cực kỳ chuẩn mực theo đúng format của khách sạn đưa cho khách hàng (App Mobile/Web).
💡 Tại sao (Why)?
Exception Filter đứng ra "hứng" lỗi, và dịch nó ra ngôn ngữ thân thiện, định dạng giống hệt như chuẩn format của một request thành công (đều có cấu trúc rõ ràng: statusCode, message). Áp dụng cách này, chúng ta không cần dùngtry/catchthủ công cồng kềnh trong mỗi hàm nữa.
2. Luồng hoạt động của Exception Filter 🔄
Dưới đây là sơ đồ luồng đi của một Request và cách Filter can thiệp khi có lỗi:
3. Cài đặt GlobalExceptionFilter trong thực tế 🛠️
Để tạo đội phản ứng nhanh này, chúng ta cần khai báo một class implements interface ExceptionFilter. Dưới đây là đoạn code chuẩn bạn có thể áp dụng ngay vào dự án:
// filename: src/common/filters/global-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class GlobalExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
// 1. Chuyển đổi HTTP Context để lấy Request và Response của Express
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
// 2. Lấy HTTP Status Code từ lỗi ném ra
const status = exception.getStatus();
// 3. Phân tích message lỗi tuỳ theo nó là chuỗi hay object
const exceptionResponse = exception.getResponse();
const message = typeof exceptionResponse === 'string'
? exceptionResponse
: (exceptionResponse as any).message || exception.message;
// 4. Định dạng lại chuẩn Response đầu ra
response
.status(status)
.json({
statusCode: status,
// Nếu ValidationPipe trả về mảng các chuỗi lỗi, chúng ta nối chúng lại tạo thành 1 chuỗi dễ đọc
message: Array.isArray(message) ? message.join('; ') : message,
data: null, // Khi có lỗi đương nhiên data trống
timestamp: new Date().toISOString(),
path: request.url,
});
}
}