ruk·si

API
Design

Updated at 2021-03-02 08:45

Application programming interface (API) is a catch-all term for any communication channel between two pieces of software.

Representational state transfer (REST) APIs used in web services are the most common APIs you encounter in the wild but there are many many different API approaches and paradigms over a network and on a single machine.

In 9 cases out of 10, you want a REST over HTTP API. HTTP is excellent because of all the surrounding ecosystem. There are out-of-the-box tools for monitoring, security, caching, load balancing, routing, and so on. There is rarely a reason to go with anything more exotic. REST is nice as it gives the concept of "resources" or "models" that you interact with, but you don't need to be too strict with this one.

REST over HTTP has slight overhead but only meaningful for the extremely low latency use-cases. If you need to optimize for super low latency, look at binary protocols like Thrift. You could use WebSockets for some low latency streaming with browsers (HTTP handshake followed by a straight TCP connection).

Use JSON as your general data format. You'll probably want to use JSON+JSONPATH but XML+XPATH is also acceptable. Both are fine, JSON is simply more popular with the new kids and permissive, while XML is more legacy and structured.

Use lowercase plural nouns over verbs in the API endpoints. Mixing singular and plural is confusing while nouns are generally more used in REST APIs. You can have action verb after pointing to a specific resource.

# good
POST example.com/api/users
POST example.com/api/users/123/some-action

# bad
POST example.com/api/user
POST example.com/api/Users
POST example.com/api/users/create
POST example.com/api/create-users
POST example.com/api/create/user

Make RESTful endpoints as guessable as possible.

# bad
https://api.example.com/users/548167
# good
https://api.example.com/users/me
# even better
https://api.example.com/me

Avoid nested resources in RESTful APIs. Try to give unique primary keys for resources that have strong foreign keys. Nested resources are always a pain to deal with.

# bad
https://api.example.com/users/123/posts/456/comments/789
# good
https://api.example.com/comments/789

You must set a standard for the following topics if you are making a lot of APIs:

  • Outline consistent use of HTTP verbs (GET, POST).
  • If the API wants to return more results than feasible, how do you handle pagination?
  • Make sure developer understand and use HTTP return code discipline.
  • Are the endpoints versioned; and if they are, how to use?

Your API should own most of the business logic. Simple CRUDs are bad as we lose cohesion as behavior leaks all over the place.

Never share a database between multiple services i.e. using a database for communication. These integration or shared databases are bad design. They bleed service internals, force technology choices to other services and become a bottleneck in the long run. Bad, bad, bad.

Never use a library to automatically deserialize full database representation of a resource. Then you are exposing internal details to API users which will cause problems in the future. At the very least, whitelist which fields to expose and make sure you have the possible to rename fields or do transformation in between.

Consider following Hypermedia as the Engine of Application State (HATEOAS) standard. HATEOAS is a REST standard that says that the client should obtain all URIs for every resource it needs by following links in the representation of resources themselves. HATEOAS provides better decoupling but it's more chatty.

GET /accounts/12345
=>
{
    "id": 12345,
    "balance": 100.00,
    "links": {
        "deposit": "/accounts/12345/deposit",
        "withdraw": "/accounts/12345/withdraw",
        "transfer": "/accounts/12345/transfer",
        "close": "/accounts/12345/close"
    }
}

Here, getting the account details also lists all actions you can perform on it.

Include refresh endpoint for long-lived entities. As soon as you get an entity like an user from a database, it might have already changed, but this is usually not an issue inside a single service. However, if you pass the entity forward, always include reference to the source for a refresh. Especially with event-based APIs, it makes more sense to pass forward only the references, but sometimes including the past state makes sense.