Giao diện
BFF: /api/** đi đâu
Trình duyệt không bao giờ gọi thẳng backend. Mọi lời gọi đi qua Nitro của chính app — lớp Backend-for-Frontend (BFF) — để token nằm nguyên trong cookie httpOnly.
Trình duyệt ──GET /api/orders──▶ Nitro (layer)
│ đọc cookie tasco_session, giải mã
│ gắn Authorization: Bearer <token>
│ xoá authorization/cookie do client gửi
▼
NUXT_API_PROXY_TARGET/ordersClient không giữ token, không tự gắn header, không đụng tới CORS — với trình duyệt thì mọi thứ đều cùng origin.
Quy tắc chuyển tiếp
Route server/api/[...].ts của layer nhận mọi đường dẫn dưới /api:
| Việc | Chi tiết |
|---|---|
| Đường dẫn | Cắt tiền tố /api, nối vào apiProxyTarget. /api/orders?page=1 → <target>/orders?page=1 |
| Xác thực | Có phiên và token còn hạn → thêm Authorization: Bearer <token> |
| Token hết hạn | Không gắn header — để backend trả 401 thay vì chuyển tiếp token chết |
| Header client gửi lên | authorization và cookie bị xoá sạch, không rò rỉ cookie trình duyệt sang backend |
| Method, body, query, các header khác | Chuyển tiếp nguyên trạng, kể cả upload |
| Response | Trả nguyên trạng cả status lẫn body |
Chưa đặt NUXT_API_PROXY_TARGET thì mọi lời gọi /api/** trả 500 kèm thông báo apiProxyTarget chưa cấu hình.
Những gì BFF cố tình không làm
- Không làm mới token. Cookie phiên không giữ refresh token (giới hạn 4KB), nên hết hạn là đăng nhập lại. Xem Phiên đăng nhập.
- Không biến đổi dữ liệu. Body đi và về nguyên trạng. Backend trả định dạng lệch thì quy đổi ở fetcher, không sửa ở proxy.
- Không kiểm tra quyền. Đó là việc của backend; phần chặn ở client chỉ phục vụ trải nghiệm.
- Không gộp nhiều lời gọi. Cần gộp thì viết route Nitro riêng (mục dưới).
Gọi từ trang
ts
const api = useApi()
const orders = await api('/orders', { query: { page: 1 } })useApi đã gắn sẵn baseURL = /api, nên đường dẫn viết như trên gateway. Chi tiết: useApi và fetcher.
Viết route Nitro riêng
Route cụ thể trong server/ của dự án thắng catch-all của layer. Ba trường hợp đáng làm:
- Gộp nhiều lời gọi backend thành một response cho màn hình.
- Backend trả envelope hoặc định dạng lệch hẳn, quy đổi ở client sẽ rối.
- Cần dùng thông tin chỉ có ở server (secret của dự án, khoá ký nội bộ).
ts
// server/api/orders/summary.get.ts
export default defineEventHandler(async (event) => {
const { apiProxyTarget } = useRuntimeConfig(event)
// getTascoSession auto-import từ layer — không tự giải mã cookie.
const session = getTascoSession(event)
if (!session?.accessToken) {
throw createError({ statusCode: 401, statusMessage: 'Phiên đăng nhập đã hết hạn.' })
}
const [orders, revenue] = await Promise.all([
$fetch<{ total: number }>(`${apiProxyTarget}/orders/count`, {
headers: { authorization: `Bearer ${session.accessToken}` },
timeout: 10_000,
}),
$fetch<{ amount: number }>(`${apiProxyTarget}/reports/revenue`, {
headers: { authorization: `Bearer ${session.accessToken}` },
timeout: 10_000,
}),
])
return { orderCount: orders.total, revenue: revenue.amount }
})Quy ước bắt buộc cho route tự viết:
- Trả object có khoá đặt tên (
{ orderCount, revenue }), không trả mảng trần. Không có dữ liệu thì trảnull, đừng trả 404. - Lỗi luôn qua
createError({ statusCode, statusMessage }), thông báo viết tiếng Việt cho người dùng đọc: 400 dữ liệu vào sai · 401 chưa đăng nhập · 403 thiếu quyền · 500 thiếu cấu hình (nêu tên biến) · 502 lỗi từ upstream. - Mọi
$fetchra ngoài đều cótimeout(10 giây). - Không
console.log; cần ghi thìconsole.warn/console.errorkèm tiền tố tên dự án. Không bao giờ log token.
Gỡ lỗi
| Hiện tượng | Nguyên nhân thường gặp |
|---|---|
500 apiProxyTarget chưa cấu hình. | Thiếu NUXT_API_PROXY_TARGET trong .env hoặc biến CI |
| 401 tuy vừa đăng nhập | Token hết hạn (BFF bỏ header), hoặc backend không chấp nhận token của IAM |
| 404 trong khi gọi thẳng gateway lại được | Đường dẫn thừa/thiếu /: <target> đã có path sẵn, hoặc gọi /api//orders |
| Backend không thấy cookie của trình duyệt | Đúng như thiết kế — BFF xoá cookie trước khi chuyển tiếp |
| Request treo tới khi timeout | Gateway không phản hồi; route tự viết thiếu timeout |
Mở tab Network và xem request tới /api/...: header Authorization không được xuất hiện ở phía trình duyệt. Thấy nó là dấu hiệu code đang tự gắn token — sửa ngay.
Liên quan
- useApi và fetcher — cách gọi từ phía trang.
- Hợp đồng dữ liệu — hình dạng request/response chuẩn.
- Phiên đăng nhập — token vào cookie thế nào.