In this article I tried to consolidate all the best practices for designing REST APIs. Note that all these results are based on my personal works after designing couple web API projects, and they are not official conventions that you should follow.
HTTP request/method has its basic CRUD operations:
POST
– Create a new record, submit an entityGET
– Retrieve existing list of records or one recordPUT
– Update/Replace an existing whole/entire record, or an existing entire list of records, such as update a record or a list of record resourcePATCH
– Update partial data of an existing record (edit user profile: change only User name), or update an array resource (add/delete records of the array resource)DELETE
– Delete existing list of records or an entityHEAD
– Get only header informationOPTION
– Get allowable HTTP headers and allowable HTTP methods that an API supportsCONNECT
– requests that a proxy establish a HTTP tunnel to a destination server, and if successful, blindly forward data in both directions until the tunnel is closedTRACE
– performs a message loop-back test along the path to the target resource
Method | Safe | Idempotent |
POST | NO | NO |
GET | YES | YES |
PUT | NO | YES |
PATCH | NO | YES/NO |
DELETE | NO | YES |
HEAD | YES | YES |
OPTION | YES | YES |
CONNECT | NO | NO |
TRACE | YES | YES |
Safe
Request methods are considered “safe” if they are read-only; the client does not request, and does not expect, any state change (resource change) on the origin server as a result of applying a safe method to a target resource. So, the GET
, HEAD
, OPTIONS
, and TRACE
methods are defined to be safe since they are just reading information, but other methods such as POST
will add new record and so the server state (DB resource) changes.
Idempotent
- It is a property of HTTP methods.
- A request method is considered idempotent if the intended effect on the server State by multiple identical requests with that method is the same as the effect for a single such request. The effect is produced on the state of the resource on the server but not about the response status code received by the client.
- APIs should be idempotent. They ensure safe retries by making repeated requests to produce the same result, especially for POST operations.
DELETE
method, which is defined as idempotent. A client performs aDELETE
request to delete a resource from the server. The server processes the request, the resource gets deleted and the server returns204
. Then the client repeats the sameDELETE
request and, as the resource has already been deleted, the server returns404
. Despite the different status code received by the client, the effect produced by a singleDELETE
request is the same effect of multipleDELETE
requests to the same URI.PATCH
can be idempotent. If just change thename
property of a object resource, you might send something like{"name": "foo"}
as a payload and that would indeed be idempotent since executing this request any number of times would yield the same result: The resourcesname
attribute is now “foo”.PATCH
can be NOT idempotent. When apply change an array resource, the sub-sequence changes will makes it NON idempotent.PATCH /users [{ "op": "add", "username": "newuser", "email": "newuser@example.org" }]
After repeated applying, the “users” resource will be changed again and again, even though we issued the exact same PATCH against the exact same endpoint.
URI Components
https://api.domain.com/namespace/v1/resource-name/collection?product_id=123&type=shoe
https://
– Scheme, Protocolapi.domain.com
– Domain name, DNS/namespace/resource-name/v1/collection
– Path/namespace
– Such as/api
, or not necessary/v1
– Version/resource-name
– Resource category name, such as/product-management
/collection
– Specific resource name, or collection of resource, such as/products
,/customers
product_id=123&type=shoe
– Query
Rules
- URI MUST be specified in all lower case
- Use hyphens
-
to separate words or path parameters (no spaces or underscores_
) - Use underscores
_
to separate words in query parameter names but not as part of the base URI
Collection Resources
Rules
- Nouns MUST be used
- Resource names MUST be plural, or appropriate noun if resource is non-standard
- Resource names MUST be lower-case and hyphens
- Sub resource can use be under /resource-category-name
/customers
/leaves
/fishes
/tree-nuts
/customer-management/types - under /customer-management
/customer-types - just use plural-nouns
Singleton Resource
Rules
- Use Collection resource URI and identify a single resource by its id
- Further sub-resource by concatenating further path
- Filtering using query parameter
/customers/{customerId}
/customers/{customerId}/accounts/{accountId}
/customers?customer_type=use_this_type&sort=date
// List of customers
GET /customers
// Filtering is a query:
GET /customers?year=2011&sort=desc
// Get one customer by ID
GET /customers/12345
// get a device by id
GET https://api.example.com/api/v2/device-management/managed-devices/123
// Get all orders of this customer
GET /customers/12345/orders
// filering
GET /customers/12345/orders?year=2011&type=shoe
// special resource
GET http://api.example.com/user-management/users/admin
Action Resources
Rules
- Use Collection resource URI or Singleton resource URL
- Use HTTP request methods CRUD, to indicate actions
- Use Verb to denote Controller resources that are like executable functions
// Add a new customer
POST /customers
// Update a customer with id (payload is entire entity)
// {"username":"Jason","email":"jason@email.com"}
PUT /customers/12345
// Update list of customer (payload is an entire list)
// [{"username":"Jason","email":"jason@email.com"}, {}, ...]
PUT /customers
// Delete a customer
DELETE /customers/12345
// Add a new order to a customer
POST /customers/12345/orders
// Update a customer order
PUT /customers/12345/orders/67890
// Update JUST customer name with id (payload is partially data)
// {"username":"Jason"}
PATCH /customers/12345
// Update list of customer (payload is partially operation data)
// [{ "op": "add", "username": "newuser", "email": "newuser@example.org" }]
PATCH /customers
// Action-only endpoints using POST
POST http://api.example.com/cart-management/users/{id}/cart/checkout
POST http://api.example.com/song-management/users/{id}/playlist/play
Request/Response Message Format
For request and response body field names or query parameter names, the naming MUST be consistent, and SHOULD be either camelCase OR snake_case:
{
// this_is_snake_case (use this)
"document_id" : 1837, // object id using int
"products": ["11","22"], // array using plural nouns
"is_student": true, // boolean
"has_candy": true, // boolean
}
{
// thisIsCamelCase
"documentId" : 1837
"products": ["11","22"]
}
products
– Fields that represent arrays SHOULD be named using plural nouns
Request Headers
Request Headers:
Header | Default Value |
Accept | application/json |
Response Convention
Status codes:
- 2xx, OK, request went well and response is sent
- 3xx, Resource was moved and then let client know it
- 4xx, Client request error, if the request cannot be fulfilled because of a client error (like requesting a resource that does not exist)
- 5xx, Server-side error, something wrong on the API side (like an exception happened)