What is good RESTful API and what are consequences of designing bad API?

Nowadays more and more service suggest their APIs, we as web/backend developers usually provide api to our frontend applications or to other backend services. In this situation we are highly dependent on the quality of API and not only in terms of understanding, but usability in general.

As consumers of the API we have to deal with such problems as:

  • not clear API with bad documentation
  • duplicated or looks like duplicated endpoints
  • misleading methods of the method
  • misleading status codes
  • not straight forward behaviour of the endpoints
  • performance heavy calls
  • redundant/not enough information in the payload
  • exposing internal mechanics and business logic

On the other hand well built API:

  • is self-described with methods and paths
  • has just sufficient amount of data in the response/request payload
  • has as simple and straightforward as possible behaviour of the endpoints
  • has easily understandable signature of endpoints
  • has endpoints that complement each other without overlapping
  • hides internal functionality

A typical consequence of the having and using bad API is not needed complexity of dependent code: when we need to do several calls to complete one simple action or implement filtering/caching functionality to overcome problems of the consumed API.

Ideas worth to follow

Most of the problems above can be leveled by simply following some practices and approaches that just help to make your contract cleaner and more predictable.

Plural names

It is better to use plural names in your endpoints related to domain model:

/ducks
/ducks/123

The idea behind this rule is that you know that you deal with collection of entities instead of one entity. Singular duck would lead to confusion: is it one default duck or it is collection of ducks.

Nouns without verbs

Use HTTP methods meaningfully - each method is created for it’s own purpose, this purpose is described with a verb like get, post, delete and others. These verbs in addition to the nouns in path can describe well the intended action.

So instead of having:

POST /createDuck
POST /deleteDuck/123
PUT /changeDuckName/123

we can have something like:

POST /ducks
PUT /ducks/123
DELETE /ducks/123

In concise manner we can clearly describe intention without need to specify additional path for common operation.

You should be able to read the signature of this endpoint as:

  • WHAT is meant to be done with your domain model - GET, POST, DELETE etc.
  • WHICH domain part will be affected - /users, /ducks - plural nouns
  • WHAT are the concrete details of the request - path variables, query parameters, body or combination of them

Filtering

Use query parameters to filter results of the request.

/duckByName/Rubber
/duckByAge/1

instead use parameters

/ducks?name=Rubber&age=1

With parameters you queries will become much cleaner and help you to combine several filters at once. So you still have one endpoint, but behaviour of it is controlled using parameters.

Pagination

This rule is related to the response payload. It is a good practice to paginate results of the request. Sometimes we even have no other choice because the set of requested data might be really huge. So use offset, limit, page parameters to limit the output of the request.

Content type

It is wise to specify the content type of the request/response of your API and implement according restrictions on the backend (it will help to improve security aspect)

Corresponding status codes

It is obvious, but it is worth to mention again: we have to use different status codes that describe returned result. So just check the list of possible statuses to find the best match.

NOTE: Be aware that sometimes this approach can lead to security vulnerabilities like enumeration. For example: you have just created user and this request returned 201 Created, the next call then returns 400 Bad Request because this user already exists. In this case you can potentially enumerate users and conduct an attack.

Documentation

Right now it is good practice to have your API described as a document. There are several services that can help you with that:

They helps you to create nicelooking page which has the list of endpoints with the description and all the details needed to successfully use the API. There are several approaches:

  • code first when you generate documentation based on the implementation
  • contract first when you create documentation first and only then start implementation

Contract first approach can help you to define common ground with all the teams beforehand and to do this all you need to do is to create yaml contract definition which will be converted into html page. This page might look like this: swagger ui

Advantages:

  • it is pretty handy and easy to use
  • contract definition can be checked against real contract to see whether it is the same or not (of course it is only shallow check)
  • specification can be converted to postman collection
  • dummy response from the hosted contract can be used to start implementation

Disadvantages:

  • we need to make efforts to keep contract up to date (there is a swagger library that helps you to define contract using annotations)
  • there is some learning curve
  • sometimes we have to deal with the gap between real implementation and defined contract

Explicitly define error object

Error or exceptions are required part of any application and contract as well. We have to be really specific of what can go wrong and what type of object you will receive as a response in this case. The list of such exceptions has to be succinct and sufficient to fulfill your needs. Be aware to not create a lot of different classes for exceptions because:

  • it might be tricky to handle all of them
  • you might expose to much internal information
  • to specific exceptions can strengthen coupling between systems

Idempotency and safe methods

Keep in mind standard behaviour of the methods and stay inline with it:

  • GET, OPTIONS, TRACE, HEAD are idempotent and safe methods which means they do not change the state on the server.
  • PUT and DELETE are idempotent methods
  • POST is not safe and is not idempotent

Additional info about idempotency

NOTE: Keep in mind the main rule not to mix methods with wrong functionality: GET /ducks/123/delete is totally wrong.

Exceptions

Sometimes we have to obey other rules like specific path that will be used by some third party service or we have to do something out of the standard behaviour - try to minimize this cases and make implementation as clear and concise as possible.

Conclusions

There are not a lot of rules that we have to follow if we want to have neat and pleasant to use RESTful API. Let’s make our world a bit better by creating new APIs and refactoring current API’s with this rules in mind.

Thank you and as always I hope it was useful for you.

Updated: