Thực hành: dựng lớp API mỏng bằng Hono trên Cloudflare Workers để bọc hệ thống cũ của khách
Hệ thống của khách thường không sửa được, nhưng bạn vẫn có thể đặt trước nó một lớp API sạch, có kiểu và có xác thực mà không phải đụng vào một dòng code cũ nào.
Tóm tắt nhanh
- Hono chạy gọn trên Cloudflare Workers: tạo project bằng create-hono, deploy bằng npm script gọi Wrangler.
- Lớp bọc phải chặn request sai trước khi chúng chạm tới hệ thống của khách: kiểm tra token, kiểm tra payload, ánh xạ lỗi.
- Token để trong secret, không lưu dữ liệu của từng request vào biến cấp module, bật observability trước khi lên production.
Tuần đầu ở chỗ khách, bạn hay gặp cảnh này: dữ liệu mà agent hoặc dashboard của bạn cần nằm trong một hệ thống đơn hàng mười năm tuổi. Tên trường viết hoa toàn bộ, lỗi trả về dạng HTML, và không ai dám sửa code của nó. Bạn cũng không cần sửa. Việc của bạn là dựng một lớp mỏng đứng trước nó.
Đây đúng là loại việc mà các tin tuyển FDE ghi thẳng vào mô tả công việc. Tin tuyển Forward Deployed Engineer II của Synapse Analytics yêu cầu ứng viên dựng và bảo trì các thành phần tích hợp riêng cho từng khách, gồm adapter, connector, script và API, rồi biến những mẫu lặp lại thành cải tiến cho sản phẩm.
Bài này dựng một lớp như vậy bằng Hono và TypeScript trên Cloudflare Workers. Theo tài liệu của Cloudflare, Hono là framework nhẹ và chạy rất hợp với Workers. Phần code dưới đây được rút gọn cho dễ theo dõi, nên đối chiếu với tài liệu chính thức trước khi đưa cho khách dùng.
Bạn sẽ dựng gì, và cần chuẩn bị gì?
Ví dụ giả định: khách có một API đơn hàng cũ nhận ITEM_CODE và QUANTITY. Lớp bọc của bạn mở ra endpoint POST /api/orders nhận JSON gọn gàng { sku, qty }, kiểm tra token của bên gọi, kiểm tra payload, gọi hệ thống cũ bằng token riêng, và đổi mọi sự cố phía sau thành lỗi JSON thống nhất.
Bạn cần Node.js và npm, một tài khoản Cloudflare nếu muốn deploy, cùng URL và token của hệ thống cần bọc. Khi luyện tập ở nhà, bất kỳ API nội bộ nào bạn có quyền gọi đều dùng được.
Bước 1: khung project và endpoint đầu tiên
Hono có lệnh create-hono để tạo project. Chạy lệnh dưới đây, rồi chọn template Cloudflare Workers khi được hỏi:
npm create hono@latest orders-wrapper
cd orders-wrapper
Entry point là src/index.ts, export app làm handler mặc định. Thêm một endpoint health check để chắc chắn mọi thứ đã chạy:
import { Hono } from 'hono'
const app = new Hono()
app.get('/health', (c) => c.json({ ok: true }))
export default app
Chạy npm run dev rồi mở URL local mà Wrangler in ra. Kiểm tra: /health phải trả về {"ok":true}. Mở package.json để xem các script dev và deploy gọi Wrangler như thế nào. Hai lệnh này sẽ đi cùng bạn suốt dự án.
Bước 2: token của khách nằm ở đâu?
Đây là chỗ nhiều người làm sai ngay từ đầu. Hướng dẫn best practices của Cloudflare nói rõ: API key, token và thông tin đăng nhập database không bao giờ được xuất hiện trong file cấu hình Wrangler hay trong source code. Hãy đưa chúng vào secret:
npx wrangler secret put UPSTREAM_TOKEN
npx wrangler secret put CLIENT_TOKEN
Khi chạy local bằng npm run dev, Wrangler đọc secret từ file .dev.vars ở thư mục gốc của project. Dùng token thử, không dùng token thật của khách, và thêm file này vào .gitignore:
UPSTREAM_TOKEN=dev-upstream-token
CLIENT_TOKEN=dev-client-token
Cloudflare cũng khuyên không tự viết interface Env mà sinh nó bằng wrangler types. Chạy lệnh này mỗi khi bạn thêm binding mới:
npx wrangler types
Sau đó sửa dòng const app = new Hono() ở bước 1 thành dòng dưới đây. Đây là thay thế, không phải khai báo thêm một app thứ hai:
const app = new Hono<{ Bindings: Env }>()
Kiểm tra: gõ c.env. trong editor phải gợi ý đúng tên các biến. URL của hệ thống cũ (UPSTREAM_URL) không phải bí mật nên có thể để làm biến cấu hình thông thường.
Bước 3: chặn request xấu trước khi nó chạm tới khách
Trước khi dịch bất cứ thứ gì, lớp bọc phải biết nói “không”. Middleware Bearer Auth của Hono không bắt token phải là JWT, và bạn có thể tự kiểm tra token bằng một hàm verifyToken:
import { bearerAuth } from 'hono/bearer-auth'
app.use('/api/*', bearerAuth({
verifyToken: async (token, c) => token === c.env.CLIENT_TOKEN,
}))
Tiếp theo là kiểm tra payload. Zod là thư viện validation thiết kế riêng cho TypeScript, và validator middleware của Hono kiểm tra được json, query, header, param, cookie và form. Cài gói rồi khai báo schema:
npm i zod @hono/zod-validator
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'
const OrderInput = z.object({
sku: z.string().min(1),
qty: z.number().int().positive(),
})
Kiểm tra: sau khi gắn schema vào route ở bước 4, gửi request không có header Authorization thì phải nhận 401. Gửi qty: -3 thì phải bị từ chối với lỗi 400 trước khi bất kỳ dòng nào phía sau kịp chạy.
Bước 4: dịch sang ngôn ngữ của hệ thống cũ và gom lỗi
Bây giờ mới đến phần dịch. Handler nhận dữ liệu đã được kiểm tra, đổi tên trường cho khớp với hệ thống cũ và gọi sang đó bằng token riêng:
import { HTTPException } from 'hono/http-exception'
const route = app.post('/api/orders', zValidator('json', OrderInput), async (c) => {
const { sku, qty } = c.req.valid('json')
const res = await fetch(`${c.env.UPSTREAM_URL}/createOrder`, {
method: 'POST',
headers: {
Authorization: `Bearer ${c.env.UPSTREAM_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ ITEM_CODE: sku, QUANTITY: qty }),
})
if (!res.ok) throw new HTTPException(502, { message: 'Order system unavailable' })
return c.json(await res.json(), 201)
})
Khi hệ thống cũ trả về trang lỗi HTML, bên gọi không nên phải nhìn thấy nó. Hono cho phép ném HTTPException và xử lý tập trung bằng app.onError:
app.onError((err, c) => {
if (err instanceof HTTPException) {
return c.json({ error: err.message }, err.status)
}
console.error(err)
return c.json({ error: 'Internal error' }, 500)
})
Kiểm tra: trỏ tạm UPSTREAM_URL sang một địa chỉ sai. Bên gọi phải nhận JSON có error, không bao giờ nhận stack trace hay HTML.
Bước 5: chia sẻ kiểu và deploy
Nếu chính bạn viết luôn phần code gọi API, chẳng hạn agent hay frontend, tính năng RPC của Hono giúp server và client dùng chung đặc tả API. Bạn chỉ cần export kiểu của app:
export type AppType = typeof route
Bên client import AppType và có ngay một client đã gắn kiểu. Đổi tên trường ở server là client báo lỗi lúc biên dịch, thay vì báo lỗi ở chỗ khách. Cloudflare cũng ghi chú rằng có thể ghép API Hono với một SPA qua Workers Assets nếu sau này bạn cần thêm giao diện.
Cuối cùng, chạy npm run deploy. Worker có thể chạy trên subdomain *.workers.dev hoặc trên một custom domain, và deploy được từ laptop lẫn từ bất kỳ hệ thống CI/CD nào. Ở chỗ khách, nên đưa lệnh deploy vào CI ngay từ tuần đầu để không ai phụ thuộc vào máy của bạn.
Những lỗi khiến lớp bọc gãy ở production
Lỗi khó thấy nhất: Workers tái sử dụng isolate giữa các request. Nếu bạn lưu token hay dữ liệu của một request vào biến cấp module, request sau có thể đọc được dữ liệu của request trước. Mọi thứ thuộc về một request phải nằm trong handler, lấy từ c.
Lỗi tiếp theo là bỏ qua giới hạn của gói dịch vụ. Gói Free cho 10 ms CPU mỗi request và 50 subrequest mỗi lần gọi. Thử hình dung một endpoint “lấy chi tiết đơn” phải gọi hệ thống cũ một lần cho mỗi dòng hàng: đơn có 60 dòng là vượt ngưỡng ngay. Hãy gom lời gọi lại hoặc xem lại gói dịch vụ trước khi hứa với khách.
Cuối cùng là deploy mà không có cách nào nhìn vào bên trong. Cloudflare gọi một Worker production không có observability là một hộp đen. Bật nó trong file cấu hình Wrangler (ví dụ rút gọn dưới đây cho wrangler.jsonc; nếu template của bạn dùng wrangler.toml thì khai báo tương đương), rồi deploy lại:
{
"observability": { "enabled": true }
}
Làm việc này trước khi lần đầu tiên có người hỏi bạn vì sao đơn hàng lúc 2 giờ sáng bị lỗi.
Bắt đầu ở chỗ khách và ghi vào CV ra sao?
Ở chỗ khách, việc đầu tiên không phải là gõ code mà là liệt kê ba thứ: endpoint nào thật sự cần, ai sẽ gọi, và hệ thống cũ báo lỗi ra sao. Sau đó, từ một endpoint đọc và một endpoint ghi, bạn đã có thứ để demo ngay trong tuần.
Khi viết CV, đừng ghi chung chung là “xây dựng API”. Hãy ghi bạn đã bọc hệ thống gì, dùng cách xác thực nào, chặn loại payload sai nào, và ánh xạ lỗi phía sau ra sao.
Đó chính là ngôn ngữ của mục “adapter, connector, API” trong tin tuyển FDE. Còn khi bạn bọc hệ thống thứ ba mà vẫn thấy lặp lại cùng một mẫu, đó là lúc kể cho đội sản phẩm nghe.
Bài này có hữu ích không?
Cảm ơn bạn đã góp ý!