DEV Community

Cover image for Quy ước đặt tên REST API: Cẩm nang phong cách thực tiễn
Sebastian Petrus
Sebastian Petrus

Posted on Originally published at apidog.com

Quy ước đặt tên REST API: Cẩm nang phong cách thực tiễn

10 quy tắc đặt tên REST API nhất quán

Mở bất kỳ codebase nào cũ hơn hai năm, bạn sẽ tìm thấy những vết sẹo: /getUser, /user_list, `[REDACTED PATH]

{% cta https://apidog.com/?utm_source=dev.to&utm_medium=wanda&utm_content=n8n-post-automation %} Dùng thử Apidog hôm nay {% endcta %}

Đặt tên là quyết định thiết kế API rẻ nhất để đưa ra nhưng tốn kém nhất để thay đổi. Khi client đã phụ thuộc vào /getOrders, bạn có thể phải duy trì nó trong nhiều năm.

Bài viết này đưa ra quy tắc cụ thể cho từng quyết định đặt tên trong REST API, kèm ví dụ nên và không nên. Nội dung bổ sung cho hướng dẫn REST API dành cho nhà phát triển, nhưng tập trung vào vấn đề gây tranh luận nhiều nhất: đặt tên.

1. Dùng danh từ số nhiều cho collection

URL nên mô tả tài nguyên, không phải thao tác. Collection chứa nhiều tài nguyên nên dùng danh từ số nhiều.

Nên:

http
GET /v1/products
GET /v1/products/89
GET /v1/orders

Không nên:

http
GET /v1/getProducts
GET /v1/product
GET /v1/productList

Cách này nhất quán ở cả hai cấp:

  • /products: collection sản phẩm
  • /products/89: sản phẩm có ID 89 trong collection

Danh từ số ít dễ tạo ra các URL khó hiểu như /product cho nhiều sản phẩm và /product/89 cho một sản phẩm. Hướng dẫn REST API của Microsoft và nhiều API công khai như Stripe, GitHub, Shopify đều dùng danh từ số nhiều.

Ngoại lệ: singleton thực sự. Nếu mỗi người dùng chỉ có một giỏ hàng, `[REDACTED PATH] hợp lý. Không cần dùng số nhiều cho tài nguyên luôn có đúng một thể hiện.

2. Không dùng động từ trong path

Phương thức HTTP đã là động từ. Đừng lặp lại thông tin đó trong URL.

Nên:

GET    /v1/orders/42
DELETE /v1/orders/42
PATCH  /v1/orders/42
Enter fullscreen mode Exit fullscreen mode

Không nên:

GET  /v1/fetchOrder/42
POST /v1/deleteOrder/42
POST /v1/updateOrderStatus
Enter fullscreen mode Exit fullscreen mode

Path dựa trên động từ cũng làm tăng bề mặt API. Một tài nguyên với bốn phương thức trở thành bốn endpoint cần tài liệu hóa, kiểm thử và cache riêng.

Ví dụ, CDN có thể liên kết GET /v1/orders/42 với việc vô hiệu hóa cache sau DELETE /v1/orders/42 vì chúng dùng cùng một URL. CDN không thể tự hiểu rằng /fetchOrder/42/deleteOrder/42 cùng tác động lên một tài nguyên.

3. Dùng kebab-case cho URL path

Các segment có nhiều từ nên dùng dấu gạch nối.

Nên:

/v1/gift-cards
/v1/shipping-addresses
Enter fullscreen mode Exit fullscreen mode

Không nên:

/v1/giftCards
/v1/gift_cards
/v1/GiftCards
Enter fullscreen mode Exit fullscreen mode

Có ba lý do:

  1. Google coi dấu gạch nối là dấu phân cách từ, giúp tài liệu API dễ lập chỉ mục hơn.
  2. Dấu gạch dưới dễ bị che khuất khi URL được gạch chân trong email hoặc tài liệu.
  3. CamelCase dễ dẫn đến lỗi viết hoa: /giftCards/giftcards thường là hai URL khác nhau.

Hướng dẫn RESTful API của Zalando xem kebab-case là quy tắc bắt buộc và áp dụng nó cho hàng trăm dịch vụ nội bộ.

4. Chọn một kiểu chữ cho JSON và ghi thành tài liệu

Với tên trường trong request và response, cả camelCase lẫn snake_case đều phù hợp. Vấn đề là trộn lẫn chúng.

Nên chọn một kiểu và dùng nhất quán:

{
  "orderId": 42,
  "createdAt": "2026-08-30T09:15:00Z",
  "totalAmount": 4999
}
Enter fullscreen mode Exit fullscreen mode

Hoặc:

{
  "order_id": 42,
  "created_at": "2026-08-30T09:15:00Z",
  "total_amount": 4999
}
Enter fullscreen mode Exit fullscreen mode

Không nên:

{
  "orderId": 42,
  "created_at": "2026-08-30T09:15:00Z",
  "TotalAmount": 4999
}
Enter fullscreen mode Exit fullscreen mode

camelCase thuận tiện cho client JavaScript và Java. snake_case dễ đọc, phù hợp với Ruby, Python và tên cột SQL; Stripe dùng kiểu này trên toàn bộ API.

Hãy chọn theo nhóm người dùng API chính, sau đó ghi vào style guide và kiểm tra trong quy trình review schema. Trộn lẫn kiểu chữ giữa các endpoint thường là vấn đề quản trị, không phải vấn đề sở thích.

5. Giới hạn lồng ghép ở tối đa hai cấp

Lồng ghép hữu ích để thể hiện quyền sở hữu:

[REDACTED PATH]
Enter fullscreen mode Exit fullscreen mode

có nghĩa là “các đơn hàng thuộc về người dùng 42”.

Nên:

GET /v1[REDACTED PATH]
GET /v1/orders/1337/refunds
Enter fullscreen mode Exit fullscreen mode

Không nên:

GET /v1[REDACTED PATH]/7/status
Enter fullscreen mode Exit fullscreen mode

Lồng ghép quá sâu buộc client phải truyền tất cả ID của tài nguyên cha để truy cập tài nguyên con, ngay cả khi tài nguyên con đã có ID duy nhất.

Nếu khoản hoàn tiền có ID 7, hãy cung cấp:

/refunds/7
Enter fullscreen mode Exit fullscreen mode

hoặc:

/orders/1337/refunds/7
Enter fullscreen mode Exit fullscreen mode

Một quy tắc nhanh: nếu URL có từ ba ID trở lên, hãy cân nhắc làm phẳng nó. Khi đơn hàng đã tồn tại, nó không còn cần user_id trong path; /orders/1337 có thể đứng độc lập.

6. Đưa lọc, sắp xếp và phân trang vào query parameters

Path xác định tài nguyên. Query parameters thay đổi cách truy vấn hoặc hiển thị tài nguyên.

Nên:

GET /v1/orders?status=active&sort=-created_at&limit=50&cursor=eyJpZCI6NDJ9
GET /v1/products?category=electronics&min_price=1000
Enter fullscreen mode Exit fullscreen mode

Không nên:

GET /v1/orders/active
GET /v1/orders/sorted-by-date-desc
GET /v1/getOrdersByStatusAndDate
Enter fullscreen mode Exit fullscreen mode

Mẫu sort=-created_at dùng dấu trừ để biểu thị thứ tự giảm dần, giúp tránh phải thêm tham số order=desc. Các path như /orders/active có vẻ đơn giản nhưng nhanh chóng dẫn đến một endpoint mới cho từng tổ hợp bộ lọc.

Tên tham số phân trang cũng phải nhất quán. Chọn một trong các hướng:

limit/cursor
Enter fullscreen mode Exit fullscreen mode

hoặc:

page/per_page
Enter fullscreen mode Exit fullscreen mode

và dùng lại trên mọi collection. Xem thêm hướng dẫn phân trang API để hiểu rõ đánh đổi giữa cursor và offset.

7. Đặt phiên bản chính trong path

Hai cách phổ biến là:

/v1/products
Enter fullscreen mode Exit fullscreen mode

hoặc phiên bản trong header:

Accept: application/vnd.myapi.v1+json
Enter fullscreen mode Exit fullscreen mode

Phiên bản trong header “thuần REST” hơn vì URL giữ nguyên tên tài nguyên. Hướng dẫn thiết kế API của Google ghi nhận cả hai phương pháp.

Tuy nhiên, phiên bản trong path thường dễ vận hành hơn:

  • xuất hiện trong mọi log;
  • có thể kiểm tra trực tiếp từ trình duyệt;
  • dễ cache mà không cần xử lý Vary;
  • client không thể vô tình quên phiên bản.

Vì vậy, ưu tiên:

/v1/products
Enter fullscreen mode Exit fullscreen mode

Chỉ dùng phiên bản chính như /v1, không dùng /v1.2. Thay đổi nhỏ nên được triển khai theo hướng bổ sung và không phá vỡ tương thích. Xem so sánh các chiến lược quản lý phiên bản API để xem cây quyết định đầy đủ, bao gồm content negotiation.

8. Xem ID là giá trị opaque

Các ID tuần tự như sau:

/orders/41
/orders/42
/orders/43
Enter fullscreen mode Exit fullscreen mode

có thể tiết lộ số lượng đơn hàng và hỗ trợ enumeration attack. Kẻ tấn công chỉ cần duyệt không gian ID để tìm lỗi phân quyền. Broken Object Level Authorization (BOLA) đứng đầu OWASP API Security Top 10.

Nên:

GET /v1/orders/ord_9f8e2a71b3
GET /v1[REDACTED PATH]e8400-e29b-41d4-a716-446655440000
Enter fullscreen mode Exit fullscreen mode

Không nên khi việc liệt kê là vấn đề:

GET /v1/orders/42
GET /v1/invoices/10883
Enter fullscreen mode Exit fullscreen mode

ID ngẫu nhiên có tiền tố, tương tự ord_9f8e2a71b3 của Stripe, có ba ưu điểm:

  • khó đoán;
  • dễ nhận diện trong log;
  • an toàn hơn khi bị công khai.

Kiểm tra phân quyền vẫn là bắt buộc. Opaque ID chỉ giảm tác động khi kiểm tra bị thiếu; nó không thay thế authorization. Bạn vẫn có thể dùng khóa chính dạng số nguyên bên trong hệ thống, miễn là không tiết lộ chúng trong URL.

9. Mô hình hóa hành động không phải CRUD thành controller resource

Các hành động như hủy đơn hàng, thử lại thanh toán hoặc gửi lại email không phải lúc nào cũng ánh xạ rõ ràng vào CRUD.

Nên:

POST /v1/orders/42/cancel
POST /v1/payments/pay_88a1/retry
Enter fullscreen mode Exit fullscreen mode

Không nên:

PATCH /v1/orders/42
{ "status": "cancelled" }

POST /v1/cancelOrder
{ "orderId": 42 }
Enter fullscreen mode Exit fullscreen mode

Đây là ngoại lệ được phép đối với quy tắc “không dùng động từ”: động từ nằm ở cuối path và bị giới hạn trong tài nguyên chịu tác động.

PATCH {"status":"cancelled"} che giấu một state machine bên trong việc cập nhật trường. Hủy đơn hàng có thể đồng thời:

  • hoàn tiền;
  • giải phóng tồn kho;
  • gửi thông báo;
  • ghi audit log.

Endpoint /cancel thể hiện rõ ý định, cho phép cấp quyền và ghi log riêng, đồng thời hỗ trợ input cụ thể như lý do hủy.

10. Nhất quán về kiểu chữ của header và query parameters

Header tùy chỉnh nên dùng Hyphenated-Pascal-Case, phù hợp với quy ước HTTP:

Idempotency-Key: ...
[REDACTED IDENTIFIER]
Enter fullscreen mode Exit fullscreen mode

Không thêm tiền tố X- cũ; tiền tố này đã bị phản đối bởi RFC 6648 từ năm 2012. Tên header không phân biệt hoa thường khi truyền tải, nhưng tài liệu và SDK vẫn nên trình bày nhất quán.

Query parameters nên khớp với kiểu chữ của JSON. Nếu JSON dùng snake_case, hãy dùng:

?min_price=1000&created_after=2026-01-01
Enter fullscreen mode Exit fullscreen mode

thay vì:

?minPrice=1000
Enter fullscreen mode Exit fullscreen mode

Việc phải đọc created_at trong response nhưng gõ createdAfter trong query là nguồn lỗi không cần thiết.

Bảng tóm tắt

# Quy tắc Nên Không nên
1 Danh từ số nhiều cho collection /products, /products/89 /getProducts, /productList
2 Không dùng động từ trong path DELETE /orders/42 POST /deleteOrder/42
3 Path dùng kebab-case /gift-cards /giftCards, /gift_cards
4 Một kiểu chữ JSON có tài liệu order_id ở mọi nơi Trộn orderIdorder_id
5 Tối đa hai cấp lồng ghép /orders/1337/refunds `[REDACTED PATH]
6 Lọc và phân trang trong query ?status=active&sort=-created_at /orders/active
7 Phiên bản chính trong path /v1/products /v1.2/products, header phiên bản
8 ID tài nguyên opaque /orders/ord_9f8e2a71b3 /orders/42 công khai, dễ liệt kê
9 Controller resource cho hành động POST /orders/42/cancel PATCH {"status":"cancelled"}
10 Kiểu chữ nhất quán cho header/query Idempotency-Key, ?min_price= X-IDEMPOTENCY_KEY, ?minPrice=

Áp dụng quy ước ở quy mô lớn

Một style guide trên wiki sẽ không đủ. Các đội duy trì API nhất quán thường thiết kế trước và áp dụng quy ước trước khi viết code — đây là nền tảng của quản trị API.

Apidog hỗ trợ quy trình schema-first bằng công cụ thiết kế trực quan:

  • path, kiểu chữ và tên tham số trở thành thành phần thiết kế rõ ràng;
  • schema dùng chung như Pagination, ErrorMoney chỉ cần định nghĩa một lần;
  • review trong workspace giúp phát hiện /getUserOrders trước khi client tích hợp;
  • đặc tả có thể thúc đẩy tạo tài liệu, mock server và kiểm thử.

Khi tên endpoint đã được duyệt trong schema, đó cũng là tên đội ngũ sẽ triển khai. Bạn có thể tải Apidog và dùng thử miễn phí. Nâng cấp API cũ rất khó; giữ API mới nhất quán thì đơn giản hơn nhiều.

Câu hỏi thường gặp

URL REST nên dùng số nhiều hay số ít?

Dùng số nhiều cho mọi tài nguyên có nhiều hơn một thể hiện:

text
/products
/orders
/users

Dạng số nhiều vẫn tự nhiên cho cả collection (/orders) và một phần tử (/orders/42). Chỉ dùng số ít cho singleton thực sự, chẳng hạn `[REDACTED PATH]apidog.com/vi/blog/what-is-rest-api?utm_source=dev.to&utm_medium=wanda&utm_content=n8n-post-automation).

camelCase hay snake_case tốt hơn cho JSON?

Không có lựa chọn tuyệt đối tốt hơn:

  • camelCase phù hợp với người dùng JavaScript;
  • snake_case dễ đọc và khớp với Python, Ruby, SQL cũng như API của Stripe.

Hãy chọn một kiểu, ghi vào style guide và kiểm tra trong schema review. Trộn lẫn giữa các endpoint gây hại nhiều hơn bản thân lựa chọn kiểu chữ.

Nên đặt phiên bản API trong URL hay header?

Ưu tiên path như /v1/orders, trừ khi bạn có yêu cầu mạnh về hypermedia. Phiên bản trong path xuất hiện rõ trong log, cache và browser test; client cũng không thể quên thêm header. Chỉ dùng phiên bản chính và triển khai thay đổi nhỏ theo hướng tương thích ngược.

Có bao giờ được dùng động từ trong REST API path không?

Có, đối với controller endpoint cho hành động không phải CRUD:

POST /orders/42/cancel
POST /payments/pay_88a1/retry
Enter fullscreen mode Exit fullscreen mode

Động từ phải nằm ở cuối path, giới hạn trong tài nguyên chịu tác động và dùng phương thức POST. Ngoài trường hợp này, phương thức HTTP mang động từ còn path chỉ chứa danh từ.

Top comments (0)