Simple personal rules to avoid common mistakes and foster consistency while designing HTTP APIs.

Design the API first, write the service later.

Iterating over an API specification is easier and faster than in a real service. API specifications can autogenerate servers, clients, documentation, they will really save you more hours than you think. There are a bunch of API languages to help with this task 1 2

  • RAML 3
  • Swagger 4
  • Open API Specification 3.0 5
  • API Blueprints 6

Even though I worked with RAML for several years, my personal favorite is OAS 3.0, it has teh good parts of RAML and OAS2, also several major companies backing it and creating tooling.

Hail the status codes.

As long as you use HTTP as underlying protocol, your API must follow the status codes. I’ve seen several times things like:

1
2
3
4
5
6
7
HTTP/1.1 200 OK
Content-Type: application/json

{
  "ok": false,
  "errors": ["Some error message"]
}

It is ok to have ok: boolean as part of the response’s body, the problem comes when it is not consistent with the status code, and that may generate friction while using different client tools or even confuse other services like load balancers.

The rule is easy even without using RESTful APIs

  • 2xx codes for successful results
  • 3xx codes for redirections/responses without body
  • 4xx codes for bad/invalid/unauthorized requests (user is doing something wrong)
  • 5xx codes for server errors
  • Complete list of status codes

At root level, always return objects, never arrays.

If you are returning a list, you can always return an object containing a list:

1
2
3
4
5
6
7
// do
type Response = {
  data: Array<Element>
}

// instead of
type Response = Array<Element>

The chances you will have to add more metadata to your response are high, and compiled languages are not that happy parsing root level arrays.

Avoid “maps” when possible.

Compiled languages have a hard time optimizing generated code for maps or they don’t even offer the feature without libraries. In almost any case the problem can be solved by returning an array of key-value pairs. It also adds the possibility to add more fields to the registers which is a good add-on for future features.

1
2
3
4
5
6
7
8
9
10
11
12
// do
type Response = {
  data: Array<{
    name: String
    value: String
  }>
}

// instead of
type Response = {
  data: Map<String,String>
}

There is only one date format

ISO86017, and if possible, use it without timezones. It works almost anywhere, even in JavaScript.

  • 2019-05-10T20:33:55Z Without timezone (tailing Z)
  • 2019-05-10T23:33:55+03:00 With timezone (tailing offset)

You don’t need GraphQL.

It often make simple things complicated. It adds a lot of blackboxes in your

It doesn’t matter when you are reading this. Every attempt to use GraphQL I’ve seen only added complexity to the projects and created a ton of unoptimized requests, and even denial of services.

Final note

I hope those recommendations (more will be added as they come) will avoid you some headaches. One last but not least important suggestion, adopt a consistent standard, it may be REST8 (although nobody does RESTful APIs correctly) or JSON:API9 or something that doesn’t exist yet, it only has to be consistent and it has to make sense.