Skip to content

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/orders

Client 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ệcChi tiết
Đường dẫnCắt tiền tố /api, nối vào apiProxyTarget. /api/orders?page=1<target>/orders?page=1
Xác thựcCó phiên và token còn hạn → thêm Authorization: Bearer <token>
Token hết hạnKhông gắn header — để backend trả 401 thay vì chuyển tiếp token chết
Header client gửi lênauthorizationcookie bị xoá sạch, không rò rỉ cookie trình duyệt sang backend
Method, body, query, các header khácChuyển tiếp nguyên trạng, kể cả upload
ResponseTrả 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:

  1. Gộp nhiều lời gọi backend thành một response cho màn hình.
  2. Backend trả envelope hoặc định dạng lệch hẳn, quy đổi ở client sẽ rối.
  3. 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 $fetch ra ngoài đều có timeout (10 giây).
  • Không console.log; cần ghi thì console.warn/console.error kèm tiền tố tên dự án. Không bao giờ log token.

Gỡ lỗi

Hiện tượngNguyê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ậpToken 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 timeoutGateway 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