Writing Rest-API
An API is a user interface for developers. Put the effort in to ensure it’s not just functional but pleasant to use.
Terminologies
The following are the most important terms related to REST APIs.
- A resource is an object or representation of something, which has some associated data with it, and there can be a set of methods to operate on it. E.g., Animals, schools, and employees are resources, and
delete, add, and updateare the operations to be performed on these resources. - Collections are a set of resources, e.g., Companies are the collection of Company resources.
- URL (Uniform Resource Locator) is a path through which a resource can be located, and some actions can be performed on it.
Convention
- Use Swagger for REST API documentation
- Use Nouns in URI: REST API should be designed for resources. For example, instead of
/createUseruse/users. - We prefer to use plurals, but there is no hard rule that one can’t use the singular for the resource name.
- Let the HTTP verb define action. Don’t misuse safe methods. Use HTTP methods according to the action which needs to be performed.
- Use SSL everywhere, with no exceptions.
- Pretty print by default & ensure gzip is supported.
- Always version your API. Version via the URL, not via headers.
- Depict resource hierarchy through URI. If a resource contains sub-resources, make sure to depict this in the API to make it more explicit.
GET /users/123/posts/1, which will retrieve Post with id 1 by user with id 123. - Field name casing: make sure the casing convention is consistent across the application. If the request body or response type is JSON, please follow camelCase to maintain the consistency.
API Version
Always version your API. Version via the URL, not via headers. Versioning APIs always helps to ensure backward compatibility of service while adding new features or updating existing functionality for new clients. The backend will support the old version for a specific amount of time.
- URL: Embed the version in the URL such as
POST /v2/users. - Header
- Custom header: Adding a custom X-API-VERSION header key by the client can be used by a service to route a request to the correct endpoint.
- Accept header: Use the accept header to specify your version.
Filter, Search, and Sort
Use query parameters for advanced filtering, sorting & searching.
Pagination:
GET /companies?page=23&size=50
Sort:
GET /companies?sort=-created_at
Filter:
GET /companies?category=software&location=saigon|hanoi
Filter by time range:
GET /companies?time=fromTime-toTime
Search:
GET /companies?search=Mckinsey
Batch Delete
- There is no clean
Restful wayto delete a collection of id without any limitation:- DELETE method with list of ID in query params is limit by its length (2048 characters)
- DELETE method doesn’t require a BODY, so most of Gateway services ignore the body when direct the request to servers.
- We use a custom
POSTmethods to achieve a batch delete functionality:
POST /companies/batch_delete
BODY: {
ids: ["id_1", "id_2"]
}
Auto-loading related resource representations
There are many cases where an API consumer needs to load data related to (or referenced from) the resource being requested.
In this case, embed would be a separate list of fields to be embedded. Dot-notation could be used to refer to sub-fields.
GET /companies/12?embed=lead.name|assigned_user
Return something useful from POST, PATCH & PUT requests
POST, PUT, or PATCH methods, used to create a resource or update fields in a resource, should always return updated resource representation as a response with the appropriate status codes as described in further points.
POST, if successful in adding a new resource, should return HTTP status code 201 along with the URI of the newly created resource in the Location header (as per HTTP specification)
Stateless Authentication & Authorization
REST APIs should be stateless. Every request should be self-sufficient and must be fulfilled without knowledge of the prior request. This means that request authentication should not depend on cookies or sessions. Instead, each request should come with some sort of authentication credentials.
Previously, developers stored user information in server-side sessions, which is not a scalable approach. Use token-based authentication, transported over OAuth2 where delegation is needed.
- For service-to-service communication, try to have the encrypted API key passed in the header.
- For user authorization, JWT with OAuth2 provides a way to go.
Rate limiting
To prevent abuse, it is standard practice to add some rate-limiting to an API. At a minimum, include the following headers:
X-Rate-Limit-Limit- The number of allowed requests in the current periodX-Rate-Limit-Remaining- The number of remaining requests in the current periodX-Rate-Limit-Reset- The number of seconds left in the current period
Some APIs use a UNIX timestamp (seconds since epoch) for X-Rate-Limit-Reset. Don’t do this! Use RFC 1123 date formats instead.
Caching
HTTP provides a built-in caching framework. You must include some additional outbound response headers and do a little validation when you receive some inbound request headers.
There are 2 approaches: ETag and Last-Modified
- ETag: When generating a response, include an HTTP header ETag containing a hash or checksum of the representation. This value should change whenever the output representation changes. If an inbound HTTP request includes an If-None-Match header with a matching ETag value, the API should return a 304 Not Modified status code instead of the resource’s output representation.
- Last-Modified: This works like ETag, except that it uses timestamps. The response header Last-Modified contains a timestamp in RFC 1123 format, which is validated against If-Modified-Since. Note that the HTTP spec has had 3 different acceptable date formats, and the server should be prepared to accept any of them.
HTTP status codes
HTTP defines a bunch of meaningful status codes that can be returned from your API. These can be leveraged to help the API consumers route their responses accordingly. I’ve curated a shortlist of the ones that you definitely should be using:
200 OK- Response to a successful GET, PUT, PATCH or DELETE. It can also be used for a POST that doesn’t result in creation.201 Created- Response to a POST that results in a creation. Should be combined with a Location header pointing to the location of the new resource204 No Content- Response to a successful request that won’t be returning a body (like a DELETE request)304 Not Modified- Used when HTTP caching headers are in play400 Bad Request- The request is malformed, such as if the body does not parse401 Unauthorized- When no or invalid authentication details are provided. Also useful to trigger an auth popup if the API is used from a browser403 Forbidden- When authentication succeeded but the authenticated user doesn’t have access to the resource404 Not Found- When a non-existent resource is requested405 Method Not Allowed- When an HTTP method is being requested that isn’t allowed for the authenticated user410 Gone- Indicates that the resource at this endpoint is no longer available. Useful as a blanket response for old API versions415 Unsupported Media Type- If the incorrect content type was provided as part of the request422 Unprocessable Entity- Used for validation errors429 Too Many Requests- When a request is rejected due to rate limiting
Response
Single data entry response
{ "id": 1, "name": "system32", "plug": "system32"}
Multi-data entries or array
{ "data": [], "metadata": { "pageSize": 20, "currentPage": 2, "totalPages": 15, "totalCount": 295, "hasNextPage": true }}
Error
A JSON error body should provide a few things for the developer - a useful error message, a unique error code (that can be looked up for more details in the docs), and a possibly detailed description. For this part, please check out Error Handling.
References
https://github.com/dwarvesf/playbook/blob/master/engineering/restful.md