DDevArchive
Đăng nhập

RESTful API: best practices và thiết kế endpoint

REST là một bộ quy ước: dùng đúng phương thức HTTP cho đúng ý nghĩa, đặt tên resource bằng danh từ số nhiều, trả đúng mã trạng thái. Người khác đọc API của bạn sẽ hiểu ngay nếu bạn theo chuẩn.

Resource và phương thức

Phương thứcÝ nghĩaVí dụ URLMã trả về
GETĐọc/api/courses200
POSTTạo mới/api/courses201
PUTThay toàn bộ/api/courses/:id200
PATCHSửa một phần/api/courses/:id200
DELETEXoá/api/courses/:id204

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