Best Practices for better RESTful API Design

For this guide, I’m assuming you’re familiar with very basics concepts of HTTP, JSON or API and this guide won’t cover its fundamentals or other types of API’s like SOAP or RPC.

Designing RESTful API’s can be tricky because there are a tons of possibilities to build it. Also, REST is an architectural style and not a strict standard and so it puts flexibility and freedom of structure on designing. However, following the best practices you can raise your chances to get succeed.

A well-designed web API should aim to support a platform independence, it means your application clients should be able to consume the API without requiring how it’s physically implemented and service consistency, where your service should be able to evolute regardless your client applications.

The topics listed below covers the developments standards that I’m used to follow to design and implement successful API solutions.

1. Organizing the API around resources

REST is more than implement a simple CRUD. You should expose operations that performs actions on your business entities, but shouldn’t expose physical details of these.

The first principle of RESTful designing is to keep simple things simple. And for an easy understanding of this structure, the naming conventions are very important.

Keep your base URL simple and intuitive (two per resource are good) and do not use verbs:

Do this:

/students
/students/1234

Don’t do this:

/getAllStudents
/createStudent
/saveStudent
/getStudent

Use HTTP verbs to operate on your resources:

GET    /students
GET    /students?state=active
POST   /students
PUT    /students/1234
DELETE /students/1234

If a resource is related to another resource, simplify associations:

Do this:

GET  /students/1234/grades Gets all grades for student 1234
POST /students/1234/grades Creates a new grade for student 1234

Avoid go deeper than we have above /resource/identifier/resource.

Some tips on this topic:

  • Adopt a consistent naming convention in URIs, use plural nouns for your resource names.
  • Concrete names are better than abstract.
  • Expose a manageable number of resources, no more than 25.
  • Follow JavaScript conventions for naming attributes
  • If the data in an HTTP PUT request message includes date and time information, make sure that your web service accepts dates and times formatted following the ISO 8601 standard.
  • Consider implementing bulk HTTP PUT operations that can batch updates to multiple resources in a collection
  • It is common to see support for GET and POST and not PUT and DELETE. On this case, make the method an optional parameter in the URL. But be careful about its usage.

2. Multiple formats

When a client application sends a request that returns data in the body of a message, it can specify the media types/formats it can handle. I recommend you to support more than one format to present response messages from the web server and keep the JSON as the default format.

You can accomplish that by using the Accept header or the type request parameter as below:

Accept: application/json
?type=json

Also, accept serialized JSON in request bodies as an additional in order to create symmetry with JSON-serialized response bodies.

Some tips on this topic:

  • Use JSON as default.
  • The type argument, if present, overrides the Accept header.

3. Use HATEOAS

Hypermedia as the Engine of Application State is a principle that hypertext links should be used to create a better navigation through the API. One of the primary motivations behind REST is that it should be possible to navigate the entire set of resources without requiring prior knowledge of the URI scheme.

Currently there are no standards or specifications that define how to model the HATEOAS principle, so here goes an example of how I use.

Request:

GET /students

Response:

...
{ 
  "id": 1234,
  "name": "Pablo",
  "links": [
    {
      "rel": "grades",
      "href": "/students/1234/grades"
    }
  ]
}
...

4. Filtering, sorting, field selection and paging

Filtering

A better strategy to filtering data is to provide the filter criteria in the query string that is passed to the API:

/students?status=active

Sorting

In order to sorting data you could provide a sort parameter that takes a field name as the value, such as:

/students?sort=name

Field Selection

Here’s how to get just the information we need from our API:

/students?fields=name,location

Paging

It is almost never a good idea to return all resources of your database at once. Consequently, you should provide a pagination mechanism. It is common to use the parameters offset and limit, which are well-known from databases.

If the client requests the students 20 to 50:

/students?offset=20&limit=30

If the client omits the parameter, use defaults and returns the students 0 to 20:

/students

Also, you should provide the total number of resources in the response:

{
  "offset": 20,
  "limit": 10,
  "total": 1643,
  "students": [
    ...
  ],
  "links": [
    {
      "rel": "nextPage"
      "href": "/students?offset=30&limit=10"
    },
    {
      "rel": "previousPage"
      "href": "/students?offset=10&limit=10"
    }
  ]
}

Some tips on this topic:

  • Support partial response by adding optional fields in a comma delimited list.
  • Use limit and offset to make it easy for developers to paginate objects.

5. Handle Errors with HTTP status codes

It’s always hard to work with an API that doesn’t handle error codes very well. You should return appropriate HTTP status codes with each response and the body of this message can contain additional information about the problem with the request and the formats expected, or it can contain a link to a URL that provides more details.

The HTTP standard provides over 70 status codes to describe the return values and we don’t need them all. Start by using the following 4 codes and if you need more, add them.

  • 200 – OK
  • 400 – Bad Request
  • 404 – Not found
  • 500 – Internal Server Error

You can also use of the authentication and authorization error codes:

  • 401 – Unauthorized: Request failed because user is not authenticated.
  • 403 – Forbidden: Request failed because user does not have authorization to access a specific resource.

All exceptions should be mapped in an error payload and should be as verbose as possible:

{
  "developerMessage": "You submitted an invalid state. Valid state values are 'active' or 'deactive'",
  "userMessage": "You submitted an invalid state.",
  "errorCode": 29888,
  "additionalInformation" : "http://api.rectius.com.br/v1/errors/29888"
}

6. Version your API

It’s only acceptable you don’t versioning your API if it’s an internal usage one. Otherwise, do not release an unversioned API and make the version mandatory.

Specify the version with a ‘v’ prefix as below:

/v1/students
/v1/students/1234

And you can also specify the version through the query string, header or media types. So Keep on mind:

  • If it changes the logic you write to handle the response, put it in the URL so you can see it easily.
  • If it doesn’t change the logic for each response, like OAuth information, put it in the header.

Some tips on this topic:

  • Maintain at least one version back.
  • Use a simple ordinal number. You don’t need a minor version number (“v2.1”).

7. Security

The last and maybe the most important things to build into your web service, but so many developers make it ridiculously hard.

For most cases, I prefer a simple token-based authentication like JWT. But another very good option is OAuth 2. It’s reasonably simple to implement on the server side, and libraries are available for many common programming languages.

Some tips on this topic:

  • Use https.
  • Avoiding sessions.
  • Use API keys, not passwords – for entropy, independence, speed, reduced exposure, traceability, rotation.
  • Authenticate every request.
  • Encrypt every request.
  • Avoid sessions (not RESTful).
  • Redirects and forwards? Avoid them – if used, validate the value.
  • Rate limit requests from clients to protect the health of the service and maintain high service quality for other clients.
  • Don’t do your own OAuth like solution.

Following the guidance in this post will help ensure that your web API is clean, well-documented, and easy-to-use. It was based in my own experiences and researches. Hope it will help you.

Leave a Reply

Your email address will not be published. Required fields are marked *