How to design a really good REST API
REST became the gold standard when making APIs in the 2010s, replacing the bloated mess that SOAP was offering. However, while most engineers and architects are familiar with the basics of REST, when it comes to actually design an API, a lot of services fail to fully grasp the concept.
This article is intended to give a deeper understanding of all the key elements to create a REST API that developers will love consuming.
Being data-oriented is the cornerstone
APIs are meant to be an interface for processing information. Best design for a REST API naturally flows from the data model.
Endpoints should reflect this architecture. Good thing is RFC3986 (section 1.2.3) states explicitly that “syntax is organized hierarchically”, giving the example of “groups or trees of documents”. As a result, URIs should be a relative path that reflects a traversal of your relational data model.
Let’s take an example with a simple data model that represents companies, their employees, and the offices they have.
The corresponding endpoints should be as follows:
/companies
for getting a list of all companies/companies/1234
for fetching a company details from its unique ID (resource1234
within the list ofcompanies
)/companies/1234/employees
provides a list of employees from the company1234
/companies/1234/employees/2345
gets the employee2345
from the company1234
/companies/1234/locations
is the list of office locations from the company1234
The query string is the optional part of the URI that affects the data you want returned with the response.
Whether you need to filter, limit, paginate, sort, use key/value pairs from the query string, for example :
/companies?country=FR
returns a list of companies from France/companies?country=FR&sort=desc
returns a list of companies from France sorted in reverse alphabetical order
This design leads to a clean and intuitive list of endpoints. The resulting implementation should have a simpler code too, as its follows the data model more closely.
Response fine-tuning
The ultimate finesse for requesting data allows developers to tailor the response. When using the API , they will be able to fetch the data they need for a feature by limiting the amount of calls and the size of the response with unnecessary information.
Limiting the information comes in the shape of a fields parameter containing a comma-separated list of fields to return:
/companies/1234?fields=id,name
returns a company details with only its ID and name.
Response can also allow inclusion of linked entities, using for example an include or expand parameter. The fields parameter introduced above can help limiting the selected fields in those extra entities:
/companies/1234?include=offices
returns a company details with only its associated offices./companies/1234?include=offices&fields=company.id,company.name,office.id,office.name
returns only the ID and name fields for the company and the offices it has.
As you follow your data model closely, implementation with a relational database is very straightforward by just adding joins and limiting what you select.
While this approach is valid, it works only when you know the joined entity has a limited set of data, for example the genres linked to a movie.
Things become way more difficult if you wanna include employees when requesting a company endpoint. With a large corporation of 10,000 employees, you will need to address pagination, sort, filtering… and your API will start becoming less intuitive. Use this approach with a lot of caution.
Action verbs and status codes
Operations done on the API must be carried through HTTP verbs, and not through API endpoint URI. This is probably the most common mistake in REST API design. You already know the design is wrong if you see this:
POST /companies/create
There already are a full set of verbs available to indicate the action on data:
- GET retrieves a single item or a list of items
- DELETE removes one or several items
A common mistake comes from the overuse of POST to create or update everything, while alternative verbs provide finer granularity :
- POST creates a new item
- PUT update all the fields of an item
- PATCH partially updates an item (only certain fields)
HTTP protocol also defines various codes to respond appropriately to most use cases of a web application. Wikipedia or Mozilla Developer portal provide a complete annotated list, however the following are the most common codes that you should need for your API:
- 200: Successful request with content in the response
- 201: Used for confirming the creation of a resource with POST.
- 204: Successful request but no content to return.
- 400: Invalid input parameters, that usually go with a valid URI.
- 401: When your user forgot to provide authentication credentials
- 402: When trying to access a resource available only for paid customers/subscribers.
- 403: Authentication is provided but is invalid (wrong API key, wrong user credentials, session expired etc.)
- 404: Either a wrong URI, or a non-existing resource referenced in the URI (see HTTP 410)
- 405: Calling an endpoint with the wrong HTTP verb.
- 409: Useful for conflicts, for example when trying to create or updating a resource while another one with the same characteristics exist (ex: creating an account with an existing nickname)
- 410: Smarter version of 404, when an existing resource has been deleted. Extremely useful to inform search engine crawlers of dereferenced content (GoogleBot, BingBot, etc.).
- 429: If you use rate limiting schemes on your REST API, this is the code for a consumer that exceeded the limit.
- 500: Platform is down due to a component failure (DB, server crash, app exception…).
- 503: Platform is down for maintenance.
Headers for technical information exchange
Headers are extremely useful to exchange information between a client and an API that are not directly related to the entities you are reading or writing.
- Language or locale (Accept-Language)
- Cache management (ETag, Pragma, Cache-Control…)
- API and user authentication (Authorization, Cookie, Set-Cookie)
- Response format (Accept, Content-Type)
- Security (HSTS, CORS…), particularly if your API is called directly from a web client.
Any other relevant information can be passed with custom headers, prefixed with “X-”. Some well known custom headers include X-Robots, that you should set to noindex to prevent search engine crawlers from exposing your API.
Write a real documentation
API documentations are usually bad, as too many developers see it as a chore rather than a crucial tool.
Please dismiss Swagger from your memory, which is to API documentation what Javadoc websites are to code. Postman collections will be useful for toying and testing, but only when you already have a very good knowledge of the API you are using. You can do better.
Some people strongly advocate HATEOAS for discoverability and links, but I kindly disagree. It adds unnecessary extra information in the response that has nothing to do with the data. A separate documentation with a good design should be enough to work efficiently with the API.
Remember you’re creating a service for developers first. They are your end-users. Approach empathically your documentation design. Become the developer who would begin for the first time without any knowledge. Here are a few questions to ask:
- what would you require to start consuming endpoints as fast as possible?
- what concepts would you like to be immediately familiar with? what are developers likely to use all the time?
- what are the advanced features developers can optionally deal with later?
Usually, documentations provide everything on a single dedicated website: how to install the connector for your technology, configure it and make your first call, detailed concepts and APIs available for each. Extra points for you if you provide an API console where developers can toy with the endpoints.
If you are still unsure, get inspiration from the best. Here are some examples of well-crafted developer docs:
Conclusion
HTTP protocol provides a complete and mature toolbox to create REST APIs. Leveraging its verbs, URIs, headers, response codes, will lead to a design that not only will be flexible, intuitive and also works with any type of device.
Remember that documentation is part of a great API UX. Provide an eye-candy, organized and comprehensive website, that has all the tools and the information developers need to start immediately, with progression to leverage advanced features whenever they will need them.