Bạn đang viết một REST API, mọi thứ chạy ổn. Rồi một ngày user gửi request với ID không tồn tại, token hết hạn, hoặc database bỗng dưng timeout — và app trả về… một trang lỗi trắng tinh, hoặc tệ hơn là crash luôn.
Xử lý error tốt không chỉ là try/catch cho qua — mà là biết log ở đâu, throw lên đâu, và trả về response lỗi nhất quán cho client. Bài này sẽ đi qua từng tình huống cụ thể, đối chiếu giữa Express và Hono.
Tư duy cốt lõi
Trước khi vào code, cần hiểu một nguyên tắc quan trọng:
Chỉ có handler mới có c (context) — chỉ handler mới trực tiếp trả được response.
Điều này nghĩa là:
Nếu lỗi xảy ra trong handler → return c.json() tại chỗ Nếu lỗi xảy ra trong service/repository (không có c) → throw lên để handler bắt Nếu muốn log và xử lý tập trung → dùng onError Service/Repository Handler onError
│ │ │
│ throw Error() │ │
│──────────────────────>│ │
│ │ throw tiếp │
│ │───────────────────>│
│ │ │ log + return response
Cách 1: Xử lý lỗi tại chỗ trong Handler
Phù hợp khi logic đơn giản, lỗi có thể đoán trước ngay trong handler.
Hai cách viết gần như giống nhau, chỉ khác res → c và phải return.
Cách 2: Throw từ Service, Handler bắt lại
Khi logic nằm ở service layer — không có c, không thể trả response trực tiếp.
Dùng Error thông thường
So sánh string để phân loại lỗi rất dễ gây bug — typo một chữ là sai hết.
Dùng HTTPException — Hono có sẵn
HTTPException giải quyết vấn đề trên bằng cách gắn status code trực tiếp vào error:
Custom Error Class trong Express — tương đương
Trong Express không có HTTPException sẵn, nhưng bạn có thể tự tạo tương tự:
HTTPException của Hono về bản chất cũng là pattern này — chỉ là có sẵn, không cần tự viết.
Cách 3: onError — Bắt lỗi tập trung
Khi app có nhiều routes, viết try/catch lặp đi lặp lại ở mỗi handler rất tẻ nhạt. Cả Express và Hono đều có cách bắt lỗi tập trung.
Khi dùng onError, handler không cần try/catch nữa — throw lên là onError tự bắt:
Lưu ý: Đặt app.onError ở cuối file, sau khi đã khai báo hết routes — tương tự error middleware của Express.
Kết hợp cả ba cách
Trong thực tế, bạn sẽ dùng cả ba tùy tình huống:
Best Practices
✅ Nên làm:
Dùng HTTPException (Hono) hoặc custom AppError (Express) khi throw từ service — để giữ status code theo cùng error Đặt onError / error middleware ở cuối file, sau tất cả routes Log error ở một chỗ duy nhất — trong onError, không phải rải rác ở từng handler Trả về cùng một shape cho mọi error response: { message: string } ❌ Tránh:
Tóm tắt
Nguyên tắc cốt lõi không đổi dù dùng Express hay Hono: throw lỗi có status code từ service, bắt và trả response ở handler hoặc onError, log một chỗ duy nhất.
Bước tiếp theo: Khi đã nắm error handling, bạn có thể tìm hiểu thêm về Validation với Zod (@hono/zod-validator) để bắt lỗi input từ sớm trước khi vào service, và Logging middleware để ghi lại mọi request/response một cách có hệ thống.