Mục lục

Lesson 4.3: Global Exception Handling

Xây dựng cơ chế bắt lỗi tập trung (Global Handler) để đảm bảo tính nhất quán và bảo mật cho API.

Error Handling
Middleware
Security

Thời lượng: 25 phút
Level: Intermediate
Yêu cầu: Đã hoàn thành bài về Validation và Error Codes.

Mục tiêu bài học

Sau bài học này, bạn sẽ:

  • Xây dựng được Middleware xử lý lỗi tập trung cho ứng dụng Backend.
  • Đảm bảo thông tin nhạy cảm không bị rò rỉ (leak) ra ngoài khi xảy ra lỗi hệ thống.
  • Biết cách log lỗi chi tiết ở server trong khi vẫn trả về lỗi gọn gàng cho client.

Nội dung chính

1. Tại sao cần Global Handler?

Thông thường, nếu bạn không bắt lỗi (try-catch), khi có lỗi phát sinh (VD: DB die, Code crash), framework sẽ tự động trả về lỗi thô sơ của nó (VD: Stack trace dài dằng dặc, lộ DB connection string...).

Lợi ích của Global Handler:

  • Nhất quán: Mọi lỗi đều trả về đúng format Envelope đã học ở Module 2.
  • Bảo mật: Giấu đi các thông tin kỹ thuật nhạy cảm.
  • Tiện lợi: Developer chỉ cần throw lỗi, không cần lo việc format response ở từng controller.

2. Quy trình xử lý lỗi 4 bước

Khi một lỗi xảy ra ở bất cứ đâu trong code, nó sẽ trôi về Global Handler:

  1. Nhận diện (Identify): Kiểm tra xem đây là lỗi ĐÃ BIẾT (Business Error) hay lỗi KHÔNG MONG MUỐN (System Crash/500).
  2. Ghi nhật ký (Logging): Ghi lại toàn bộ Stack trace, chi tiết lỗi vào hệ thống log (VD: Winston, ELK, Datadog) kèm theo trace_id.
  3. Lọc thông tin (Sanitize): Nếu là lỗi 500, thay thế thông báo kỹ thuật bằng một câu chung chung ("Internal Server Error") để bảo mật.
  4. Phản hồi (Respond): Đóng gói vào Envelope JSON và trả về cho Client cùng HTTP Status phù hợp.

3. Ví dụ Code Pattern (Node.js/Express)

typescript:
// Middleware xử lý lỗi tập trung
function globalErrorHandler(err, req, res, next) {
  // 1. Log lỗi chi tiết ở Server (Dành cho Dev)
  console.error(`[ERROR] TraceID: ${req.traceId}`, err);

  // 2. Xác định cấu trúc trả về
  const success = false;
  let statusCode = err.statusCode || 500;
  let errorCode = err.code || 'INTERNAL_ERROR';
  let message = err.message || 'Một lỗi bất ngờ đã xảy ra';

  // 3. Bảo mật: Nếu là lỗi server chưa biết, giấu message chi tiết
  if (statusCode === 500) {
    message = 'Lỗi hệ thống, vui lòng thử lại sau';
  }

  // 4. Trả về đúng chuẩn Design System
  res.status(statusCode).json({
    success,
    data: null,
    error: {
      code: errorCode,
      message: message,
      details: err.details || []
    },
    meta: {
      trace_id: req.traceId
    }
  });
}

Nguyên tắc then chốt

"Log everything internally, expose only what is necessary externally."

Hệ thống log của bạn có thể chứa 10 trang stack trace để bạn debug, nhưng Client chỉ nên nhận được 3 dòng JSON gọn gàng.


Thực hành

Bài tập 1: Xây dựng Custom Error Class

Mục tiêu: Tạo ra các lớp lỗi kế thừa từ Error chuẩn để dễ dàng ném lỗi kèm theo Metadata.

Yêu cầu:

  • Tạo class AppError chứa statusCodeerrorCode.
  • Tạo class NotFoundError kế thừa từ AppError với status mặc định là 404.
Xem đáp án mẫu
typescript:
class AppError extends Error {
  constructor(public statusCode: number, public errorCode: string, message: string) {
    super(message);
  }
}

class NotFoundError extends AppError {
  constructor(message = 'Resource không tồn tại') {
    super(404, 'NOT_FOUND', message);
  }
}

// Cách dùng trong Controller:
// throw new NotFoundError('Không tìm thấy đơn hàng này');

Tình huống thực tế

Scenario 1: Lỗi từ thư viện bên thứ 3

Bối cảnh: Bạn dùng thư viện kết nối Database. Khi DB sập, nó ném ra lỗi: Error: Connect timeout at 10.0.0.5:5432 after 30s. Stack: ...

Vấn đề: Nếu trả về nguyên văn, hacker sẽ biết địa chỉ IP nội bộ và loại database bạn đang dùng.

Giải quyết: Trong Global Handler, bạn phải kiểm tra: "Nếu lỗi này không phải là instance của AppError của tôi, thì mặc định nó là 500 và trả về message an toàn."


Câu hỏi phỏng vấn

Senior Level

  1. Q: Tại sao ta nên gán một trace_id (hoặc request_id) vào mọi Error Response? A:
    • Để hỗ trợ việc Support Người dùng: Khi user báo lỗi kèm theo ID này, dev có thể tra lại chính xác dòng log liên quan trong hàng triệu dòng log của server.
    • Để debug trong Microservices: Giúp theo vết yêu cầu đi qua nhiều service khác nhau.

Tóm tắt

Những điều cần nhớ

  • Global Handler giúp API luôn trả về một format duy nhất.
  • Bảo mật thông tin bằng cách lọc bỏ chi tiết lỗi 500.
  • Log chi tiết ở server kèm theo Trace ID.

Key Takeaways

  1. Safety: Không bao giờ để lộ Stack trace cho Client.
  2. Developer Experience: Giúp việc ném lỗi (throw error) trở nên nhẹ nhàng và nhất quán.

Bước tiếp theo

🎉 Chúc mừng! Bạn đã hoàn thành Module 4: Validation & Errors.

Giờ bạn đã nắm vững:

  • Cách validate dữ liệu chặt chẽ.
  • Cách quản lý mã lỗi tập trung.
  • Cách xây dựng hệ thống xử lý lỗi an toàn và chuyên nghiệp.

Module tiếp theo: Module 5: Pagination & Filtering - Học cách chuẩn hóa việc lấy danh sách dữ liệu lớn, sắp xếp và tìm kiếm.

Bắt đầu Module 5 →

Quảng cáo
mdhorizontal