We’ve been designing and deploying micro-services with REST APIs for a while now, using API-First design. Time to document some of the lessons we’ve learnt during that process. They’re not presented in any particular order and they relate to various parts of the development lifecycle.
How to do paging
If a list query (say GET /members?start=0&count=100) could return a total of, say, 15.000 results, you need to indicate this in the response (so the client can show a paging control).
Our early APIs returned an “envelope” object, containing the list of 100 members plus the total count. This is OK, but it forces the client to implement an extra class for all list queries, whereas what he really expects is an array of members.
This can be achieved by putting the total count in a response header (X-Total-Count is often used) and returning a pure list of members. Note that even if you don’t expect a list to grow long, it’s a good idea to provide paging anyway for consistency.
How to handle long running tasks
Users of your API will expect immediate responses, you should not leave them waiting 20 seconds for an operation to complete (it’s likely to timeout on the client side). If the underlying operation might take longer than, say, one second, you should run it in the background and return a task id and provide an additional method to get the status of the task via its id (and to cancel it, if possible).
How to handle versioning of your API
The simplest way to handle versioning is simply to introduce the version into the path of your API, e.g. GET /items, GET /v2/items. This is a common pattern and users will accept it. However, you shouldn’t mix versions, so documentation and playground for older versions should only be available via an “previous versions” link.
Naming in REST APIs
Naming conventions are an important part of making your API easy to use. A number of de facto naming conventions have emerged which you can and should follow.
Objects should, wherever possible, have an id property which identifies them uniquely.
list vs objects
Use plural names for lists, singular for objects:
GET /items?start=20&count=10 will be expected to return a list of up to 10 items starting at item 20.
GET /item/2 will be expected to return an item with id=2.
Since REST APIs should be stateless, every API call should be authenticated separately.
REST APIs can use any authentication method supported by http(s). However, most APIs these days use an API key (a GUID like 7b515391-79e3-4857-92b1-9f7d0f099fcd). The most unobtrusive way to pass the API key to the server is to use a request header (called something like api-key). You can also use a query parameter (e.g. GET /items?api-key=7b515391-79e3-4857-92b1-9f7d0f099fcd). For ease of use (especially from a browser), you should support both styles.
An API alone is not enough
These days, people expect a lot from APIs. As well as a great API, you’ll also need online portal with documentation, code-samples in multiple languages and a sandbox/playground where people can try out your APIs in a production-near manner.
Which tools to use
We use Swagger/OpenAPI to develop the API and then we wire it to a back-end (Java or Python) using OpenAPI java or python inflectors1. We use Redoc for online documentation of REST APIs. Postman is indispensable for ad hoc testing during development and curl is a useful simple command-line API client. We use HAProxy for load-balancing and Letsencrypt for SSL certs. We use Docker for deployment (not just for the server, but also for all infrastructure components like SSL termination, proxying etc).
1. You’ll often see people generating a REST API from a server implementation. At first glance, this seems easier than the API-First approach we use, but it’s the wrong approach because your API must be pristine and if you generate it from an implementation, you will inevitably bleed some implementation details into the API.