Resource và phương thức
| Phương thức | Ý nghĩa | Ví dụ URL | Mã trả về |
|---|---|---|---|
| GET | Đọc | /api/courses | 200 |
| POST | Tạo mới | /api/courses | 201 |
| PUT | Thay toàn bộ | /api/courses/:id | 200 |
| PATCH | Sửa một phần | /api/courses/:id | 200 |
| DELETE | Xoá | /api/courses/:id | 204 |
Tên endpoint dùng danh từ số nhiều (/api/courses), không dùng động từ (/api/getCourses). Hành động được biểu diễn bằng phương thức HTTP.
⚠ Bẫy URL động từ
GET /api/courses/delete?id=1 là phản-chuẩn: việc xoá nên là DELETE /api/courses/1. Giữ danh từ số nhiều để API tự đồng nhất, dễ nhớ.
Trả đúng mã trạng thái và cấu trúc lỗi
// File: errors.js
app.get("/api/courses/:id", async (req, res) => {
const course = await db.course.findUnique({
where: { id: Number(req.params.id) },
});
if (!course) {
return res.status(404).json({
error: 'NOT_FOUND',
message: 'Không tìm thấy khoá học',
});
}
res.json(course);
});
// 400: lỗi từ người gọi, 404: không thấy, 401/403: chưa/cấm xác thực,
// 500: lỗi server. Đừng trả 200 cho mọi thứ!
Validate input trước khi xử lý
Không bao giờ tin dữ liệu từ client. Validate tay hoặc dùng thư viện (zod, joi). Thiếu validate là nguồn gốc của lỗi 500 và lỗ hổng bảo mật.
// File: validate.js
import { z } from 'zod';
const createCourseSchema = z.object({
title: z.string().min(3).max(120),
price: z.number().positive(),
level: z.enum(["cơ bản", "trung cấp", "nâng cao"]),
});
app.post("/api/courses", (req, res) => {
const result = createCourseSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ error: result.error.issues });
}
// result.data đã chắc chắn đúng kiểu
});
Filter, sort và pagination
// File: pagination.js
// Query params: ?category=sql&page=2&limit=20&sort=-created
app.get("/api/courses", async (req, res) => {
const { category, page = 1, limit = 20, sort = "-created" } = req.query;
const where = category ? { category } : {};
const orderBy = sort.startsWith("-")
? { [sort.slice(1)]: "desc" }
: { [sort]: "asc" };
const items = await db.course.findMany({
where, orderBy,
skip: (page - 1) * limit,
take: Number(limit),
});
res.json({ page, limit, items });
});
❓ API cần đổi mật khẩu. Endpoint nào đúng chuẩn REST?
- Đặt tên endpoint theo danh từ số nhiều
- Trả đúng mã trạng thái 200/201/204/400/404
- Validate mọi input từ client
- Có filter, sort, pagination cho danh sách