Trả lời nhanh: Một API REST sạch cho dự án nhỏ chỉ cần năm quyết định nhất quán: đặt tên tài nguyên bằng danh từ số nhiều, dùng đúng phương thức HTTP cho đúng hành động, trả mã trạng thái có nghĩa, có một định dạng lỗi duy nhất cho toàn hệ thống, và phân trang ngay từ đầu cho mọi danh sách. Năm điều đó quan trọng hơn mọi tranh luận về chuẩn mực, vì cái làm API khó dùng luôn là sự thiếu nhất quán chứ không phải chọn sai quy ước.
API nội bộ của dự án nhỏ hay được viết theo kiểu cần gì thêm nấy, và sau sáu tháng thì mỗi điểm cuối một kiểu: chỗ trả về mảng, chỗ trả về đối tượng bọc ngoài, chỗ báo lỗi bằng mã 200 kèm chữ. Bài này đưa ra bộ quy ước tối thiểu để tránh cảnh đó, không lý thuyết hóa.
Đặt tên và chọn phương thức
GET /api/v1/san-pham # danh sachGET /api/v1/san-pham/12 # mot mucPOST /api/v1/san-pham # tao moiPATCH /api/v1/san-pham/12 # sua mot phanPUT /api/v1/san-pham/12 # thay toan boDELETE /api/v1/san-pham/12 # xoaQuy tắc: đường dẫn là danh từ, hành động nằm ở phương thức. Tránh những đường dẫn kiểu /api/lay-san-pham hay /api/xoa-san-pham, vì khi đó bạn tự tạo ra một ngôn ngữ riêng mà người mới phải học thuộc.
Với quan hệ lồng nhau, chỉ lồng một cấp:
GET /api/v1/don-hang/45/muc # totGET /api/v1/khach/9/don-hang/45/muc/3 # qua sau, kho doc va kho bao triKhi cần lọc theo quan hệ sâu hơn, dùng tham số truy vấn thay vì lồng thêm: /api/v1/muc?donHang=45. Cách này vừa gọn vừa dễ mở rộng.
Mã trạng thái và định dạng lỗi
Sáu mã phủ gần hết nhu cầu: 200 thành công, 201 đã tạo, 400 yêu cầu sai, 401 chưa đăng nhập, 403 không có quyền, 404 không tìm thấy, 422 dữ liệu không hợp lệ. Thêm 500 cho lỗi phía máy chủ.
Phân biệt 401 và 403 hay bị nhầm: 401 là bạn chưa cho tôi biết bạn là ai, 403 là tôi biết bạn là ai nhưng bạn không được phép.
Định dạng lỗi phải thống nhất toàn hệ thống:
{ "loi": { "ma": "SAN_PHAM_HET_HANG", "thongDiep": "Sản phẩm đã hết hàng", "chiTiet": { "sanPhamId": 12, "conLai": 0 } }}Trường mã lỗi quan trọng nhất: nó để máy đọc, nên phía giao diện xử lý theo mã chứ không phải đọc chuỗi tiếng Việt. Sửa câu chữ thông báo về sau sẽ không làm hỏng phía trước.
Và đừng bao giờ trả mã 200 kèm một trường báo thất bại trong nội dung. Cách đó phá vỡ mọi công cụ theo dõi và khiến bạn không biết hệ thống đang lỗi.
Phân trang, lọc và sắp xếp
GET /api/v1/san-pham?trang=2&moiTrang=20&sapXep=-ngayTao&danhMuc=laptop { "duLieu": [ ... ], "phanTrang": { "trang": 2, "moiTrang": 20, "tong": 143 }}Phân trang mọi danh sách ngay từ đầu, kể cả khi hiện tại bảng chỉ có mười bản ghi. Đây là lời khuyên nghe thừa nhưng cứu bạn ở tháng thứ tám, khi bảng có năm mươi nghìn dòng và điểm cuối kia bắt đầu làm sập trang.
Đặt giới hạn cứng cho số bản ghi mỗi trang, ví dụ tối đa một trăm, và bỏ qua nếu bên gọi xin nhiều hơn. Nếu không, một lượt gọi xin mười nghìn bản ghi có thể làm nghẽn cả database.
Với dữ liệu lớn và cần cuộn liên tục, phân trang theo con trỏ hiệu quả hơn phân trang theo số trang, vì nó không phải đếm và bỏ qua hàng loạt bản ghi. Nhưng với dự án nhỏ thì phân trang theo trang đơn giản và đủ dùng.
Xác thực và giới hạn tần suất
Với API nội bộ phục vụ giao diện của chính bạn, cách gọn nhất là dùng phiên đăng nhập kèm cookie. Với API cho ứng dụng di động hoặc bên thứ ba, dùng thẻ mang theo trong tiêu đề yêu cầu.
Ba việc tối thiểu về an toàn:
- Kiểm tra quyền ở từng điểm cuối, không dựa vào việc giao diện đã ẩn nút. Ai cũng gọi thẳng API được.
- Giới hạn tần suất, ít nhất cho các điểm cuối đăng nhập và tìm kiếm. Không có nó thì một script đơn giản cũng làm nghẽn máy chủ.
- Ghi nhật ký các thao tác thay đổi dữ liệu, kèm ai làm và lúc nào. Bạn sẽ cần nó vào ngày có tranh cãi.
Chủ đề chọn cơ chế xác thực nào cho hợp nằm ở bài riêng trong cụm này. Ở đây chỉ nhấn một điều: đừng tự viết phần mã hóa mật khẩu, hãy dùng thư viện chuẩn của nền tảng bạn đang chạy.
Đánh phiên bản và tài liệu
Cách đơn giản nhất và đủ cho hầu hết dự án: đặt phiên bản ngay trong đường dẫn, ví dụ /api/v1/. Khi cần thay đổi phá vỡ tương thích thì mở /api/v2/ và giữ bản cũ chạy song song một thời gian.
Quy tắc phân biệt: thêm trường mới thì không cần đổi phiên bản, vì bên gọi cũ vẫn chạy bình thường. Bỏ trường hoặc đổi ý nghĩa trường thì phải đổi phiên bản, vì nó làm hỏng bên gọi cũ.
Về tài liệu, một file mô tả theo chuẩn mở là đủ, và bạn có thể để agent sinh bản nháp từ chính mã nguồn rồi bạn sửa lại. Nhớ kiểm chứng từng điểm cuối trong bản sinh ra, vì mô hình rất hay bịa thêm tham số nghe hợp lý mà không tồn tại. Cách phòng bịa đặt nằm ở bài viết plugin an toàn, nguyên tắc áp dụng chung cho mọi loại mã.
Bốn quyết định nên chốt trước khi viết dòng mã đầu tiên
| Quyết định | Vì sao chốt trước | Đổi sau có dễ không |
|---|---|---|
| Quy ước đặt tên đường dẫn | Đổi sau là phá vỡ mọi client đang dùng | Rất khó |
| Định dạng thông báo lỗi | Client xử lý lỗi theo định dạng này | Khó |
| Cách phân trang | Đổi cách phân trang là client phải viết lại | Khó |
| Cách đánh phiên bản | Không có thì không nâng cấp được mà không phá client | Khó nếu để muộn |
Bốn thứ này quyết định việc bạn có nâng cấp được giao diện lập trình sau này mà không phá ứng dụng của người dùng hay không. Đây là chỗ đáng dành nửa ngày bàn trước, thay vì sửa sau.
Bốn việc bảo mật bắt buộc
- Xác thực trên mọi đường dẫn trả về dữ liệu riêng. Nghe hiển nhiên nhưng đây là lỗ hổng phổ biến nhất: một đường dẫn quên kiểm quyền là lộ toàn bộ dữ liệu.
- Kiểm quyền theo từng bản ghi, không chỉ theo từng đường dẫn. Người dùng A không được đọc bản ghi của người dùng B chỉ vì cả hai đều đã đăng nhập.
- Giới hạn tần suất theo người dùng và theo địa chỉ mạng. Không có thì một kịch bản tự động làm chết dịch vụ.
- Không trả về thông tin lỗi chi tiết trên môi trường thật. Thông báo lỗi có đường dẫn tệp và câu truy vấn là bản đồ cho người muốn tấn công.
Kiểm trước khi đưa vào chạy thật
# 1. Liet ke moi duong dan va doi chieu: duong nao can xac thuc# Kiem tung duong bang mot the khong hop le, phai tra ve 401 # 2. Thu doc du lieu cua nguoi khac bang tai khoan cua minh# Phai tra ve 403 hoac 404, khong duoc tra ve du lieu # 3. Thu gui rat nhieu yeu cau lien tiepfor i in $(seq 1 200); do curl -o /dev/null -s -w "%{http_code} " https://api.tenmien.vn/v1/itemsdone; echo# Phai thay ma 429 xuat hien, neu khong la chua co gioi han tan suat # 4. Kiem thong bao loi tren moi truong that khong lo duong dan tepcurl -s https://api.tenmien.vn/v1/khong-ton-tai | head -c 300Về hiệu năng: điểm nghẽn của phần lớn giao diện lập trình nhỏ nằm ở cơ sở dữ liệu, không ở mã. Đo số truy vấn cho mỗi yêu cầu trước khi nghĩ tới việc nâng cấu hình máy. Xem bài về chẩn đoán nghẽn.
Câu hỏi thường gặp
Dự án nhỏ có cần đánh phiên bản API không?
Có, chỉ cần thêm tiền tố v1 vào đường dẫn ngay từ đầu. Nó gần như miễn phí lúc này và cứu bạn khi cần thay đổi phá vỡ tương thích, vì lúc đó bạn không phải đổi mọi bên gọi cùng lúc.
Nên trả về mảng trực tiếp hay bọc trong một đối tượng?
Bọc trong đối tượng, ví dụ có trường dữ liệu và trường phân trang. Trả mảng trần thì sau này muốn thêm thông tin phân trang hay siêu dữ liệu là phá vỡ tương thích.
Có nên dùng PATCH hay chỉ PUT?
Dùng PATCH cho việc sửa một phần vì đó là trường hợp phổ biến nhất, và bên gọi không phải gửi lại toàn bộ đối tượng. Giữ PUT khi thật sự thay toàn bộ, hoặc bỏ hẳn nếu không dùng tới.
API nội bộ có cần giới hạn tần suất không?
Nên có ít nhất cho đăng nhập và tìm kiếm. Nội bộ không có nghĩa là an toàn: một vòng lặp viết nhầm ở phía giao diện cũng đủ gọi hàng nghìn lượt mỗi phút và làm nghẽn database.
