RESTful API Design Best Practices
Introduction
REST - stands for Representational State Transfer. As many of you might know REST is currently one of the most popular protocols for defining APIs contracts.
The main difference of REST from it's rivals such as SOAP and RPC is that REST is built around the concept of state and relies on the underlying protocol HTTP for other elements, such as action and context. By state you can consider the data that is being accessed via the API.
Let's review the concept above in an example: an API provides the capability to change the user's profile picture. In this example the user's identity and the new picture would represent the state, or in other words the data or request payload. The fact that we are updating the picture represents the action - update and existing state. The client authorization for this action is part of the request context.
The idea that REST protocol operates on the state and heavily relies on HTTP is crucial to keep in mind while designing RESTful APIs.
Anatomy of a RESTful API call
A RESTful API call is essentially an HTTP request. As explained above REST is using elements of HTTP in order to define the operation intended by the client calling the API. Below are listed the main structural parts constituting an RESTful API call:
- URL consisting of hostname:port, endpoints (resource paths), query and path parameters.
- Parameters in form of query parameters, path parameters, request body, custom HTTP headers.
- HTTP verb (GET, POST, PUT, PATCH, DELETE).
- Metadata as HTTP headers (i.e. Content Type, Authorization).
- HTTP response code.
Best Practices
- Use plural form in naming resources in your path to avoid a mesh of singular and plural path variants for the same resource, which complicates the API implementation.
- Don't use verbs in naming your path resources, use plural nouns. The request action should be defined by the HTTP verb of the request.
- Use path parameters whenever a resource name is followed by its identifier. It serves for better readability.
- Place sensitive parameters and secrets in the request body. Consider having a POST endpoint respond to a reading type of request in this kind of situation.
- Version the API using the base path (i.e. '/v1/items'). Although HTTP provides a metadata field to indicate the API version, the industry is overwhelmingly using the base path for that purpose.
- Careful with using advanced 2xx HTTP response codes (like 201, 204 etc.), because many times a developer would hardcode the validation of a successful response only to HTTP 200 and in event of a successful response, say 202 Accepted, the client application would still consider that as a failed request. However, advanced 2xx HTTP response codes can significantly increase readability in a seamless way, whenever you control both the development of both service and client sides.
- If you have only one public hostname and need multiple services to respond through it, you can use a façade service or an API gateway to route the request depending on the base path (the first part of your endpoint path).
- Prefer using Unix epoch time format (long integer value) for timestamps, instead of a string representation. It simplifies the handling of this data in the majority of cases. However, a string representation is still preferable in cases like date of birth, document issue and expiry date, date of a holiday.
- Create an OpenAPI specification for your public API.
- Avoid using sensitive data to identify resources. It complicates the implementation of read and delete operations, because GET and DELETE requests don't have a request body, so the resource ID should be passed in the URL as a query or path parameter. The solution could be implementing the read or delete operation as a POST request, which is not ideal.
- Sometimes we implement an endpoint to do a specific action with a resource. Later on we discover that the endpoint is called by the client multiple times to serve one use case. At the same time our design prevents us from easily extending the endpoint to do the action on multiple resource instances in one request (i.e. the resource id is passed as a header parameter). When designing an endpoint that does an action on one resource, when you have many instances of that resource, consider placing the resource ID parameter in the request body, for example. That would allow extending it to a list later on, when there's a need to scale the API functionality.
- Similar to the situation above, if your endpoint does a search and provides a subset of resources based on only one criteria, you might want to have the search criteria defined as a request parameter, instead of endpoint path, so that later on the search operation can be scaled to use multiple search criterias. I.e. use the resource collection endpoint with optional search parameters:
/v1/departments/12/employees?title=swe&age.gt=25&age.lt=35
When do you think a team should consider moving / building a GraphQL API instead of a REST API? Great article btw.
Great question! GraphQL originated as a data access layer to provide uniformity across multiple data sources (graphs) that could be RDBMS’, NoSQL DBs, graph DBs, blob stores etc. Probably its main use case is to flexibly organize access to multiple data sources in an enterprise environment. Not very often you can see a GraphQL endpoint being exposed externally, because it wasn’t designed with appropriate security capabilities. However, there are ways to secure a GraphQL endpoint, i.e. by putting it behind a secure proxy. Thanks for the question Kevin!