REST API Guidelines

What is REST

REST is an acronym for REpresentational State Transfer. It is an architectural style for distributed hypermedia systems and was first presented by Roy Fielding in 2000 in his famous dissertation.

Like any other architectural style, REST also does have it’s own 6 guiding constraints which must be satisfied if an interface needs to be referred as RESTful. These principles are listed below.

Guiding Principles of REST

  1. Client-server – By separating the user interface concerns from the data storage concerns, we improve the portability of the user interface across multiple platforms and improve scalability by simplifying the server components.
  2. Stateless – Each request from the client to server must contain all of the information necessary to understand the request, and cannot take advantage of any stored context on the server. Session state is therefore kept entirely on the client.
  3. Cacheable – Cache constraints require that the data within a response to a request be implicitly or explicitly labelled as cacheable or non-cacheable. If a response is cacheable, then a client cache is given the right to reuse that response data for later, equivalent requests.
  4. Uniform interface – By applying the software engineering principle of generality to the component interface, the overall system architecture is simplified and the visibility of interactions is improved. In order to obtain a uniform interface, multiple architectural constraints are needed to guide the behaviour of components. REST is defined by four interface constraints: identification of resources; manipulation of resources through representations; self-descriptive messages; and, hypermedia as the engine of application state.
  5. Layered system – The layered system style allows an architecture to be composed of hierarchical layers by constraining component behaviour such that each component cannot “see” beyond the immediate layer with which they are interacting.
  6. Code on demand (optional) – REST allows client functionality to be extended by downloading and executing code in the form of applets or scripts. This simplifies clients by reducing the number of features required to be pre-implemented.

Best practices to implement REST APIs

In this blog, I will share the practices that have helped me implement Rest APIs. These practices are irrespective of the stack or the framework used to implement REST APIs. We will be covering the following:

  • Key requirements
  • URLs and Actions
  • SSL
  • Versioning
  • Authentication
  • Query Parameters for Advanced filtering
  • Fields returned from an API
  • Resource Representation for POSTPUT and PATCH
  • JSON and XML responses
  • Pagination
  • HTTP Status Codes
  • Documentation

Key Requirements

An API is a developer’s UI-just like any UI. It is important to ensure the user’s experience is thought out carefully. So an API should:

  • use web standards where they make sense.
  • be friendly to the developer and be explorable.
  • be simple, intuitive and consistent to make adoption not only easy but pleasant.
  • provide enough flexibility to power the majority of the enchant UI.
  • be efficient, scalable, while maintaining balance with the other requirements.

URLs and Actions

The key principles of REST involve separating your API into logical resources.

These resources are manipulated using HTTP requests where the method (GET, POST, PUT, PATCH, DELETE) has a specific meaning.

A resource should be a noun, not a verb. Examples:

  • GET /users – Retrieves a list of users
  • GET /users/12 – Retrieves a specific user #12
  • POST /users – Creates a new user
  • PUT /users/12 – Updates a given user #12
  • PATCH /users/12 – Partially updates a given user #12
  • DELETE /users/12 – Deletes a user #12

URLs and Actions – Naming format

There are no method naming conventions to follow and the URL structure must be clean and clear.

The keep-it-simple rule applies here. Although your inner- grammatician will tell you it’s wrong to describe a single instance of a resource using a plural, the pragmatic answer is to keep the URL format consistent and always use a plural.

Not having to deal with odd pluralisation (person/people, goose/geese) makes the life of the API consumer better and is easier for the API provider to implement (as most modern frameworks will natively handle /users and /users/12 under a common controller).

URLs and Actions – Relations

If a relationship can only exist within another resource, RESTful principles provide useful guidance. Let us look at few examples:

A user can consist of a number of messages. These messages can be logically mapped to the /users endpoint as follows:

  • GET /users/12/messages – Retrieves a list of messages for user #12
  • GET /users/12/messages/5 – Retrieves message #5 for user #12
  • POST /users/12/messages – Creates a new message in user #12
  • PUT /users/12/messages/5 – Updates message #5 for user #12
  • PATCH /users/12/messages/5 – Partially updates message #5 for user #12
  • DELETE /users/12/messages/5 – Deletes message #5 for user #12

Alternatively, if a relationship can exist independently of the resource, it makes sense to just include an identifier for it within the output representation of the resource. The API consumer would then have to hit the relation’s endpoint.

However, if the relationship is commonly requested alongside the resource, the API could offer functionality to automatically embed the relation’s representation and avoid the second hit to the API.

URLs and Actions – Not CRUD

This is where things can get fuzzy. There are a number of approaches:

  • Restructure the action to appear like a field of a resource. This works if the action doesn’t take parameters. For example, an activate action could be mapped to a boolean activated field and updated via a PATCH to the resource. Example: PATCH /users/12/activate
  • Treat it like a sub-resource with RESTful principles. For example, GitHub’s API lets you star a gist PUT /gists/:id/star and unstar with DELETE /gists/:id/star.

SSL

Always use SSL. It is one of the basic security measures while designing an API. Strive to expose your endpoints via SSL.

Versioning

Always version your API. Versioning helps you iterate faster and prevents invalid requests from hitting updated endpoints. It also helps smooth over any major API version transitions as you can continue to offer old API versions for a period of time.

Use the version number in the URL. Example:/v1/users/

Authentication

A RESTful or REST-like API should be stateless. This means that request authentication should not depend on cookies or sessions. Instead, each request should come with some sort of authentication credentials.

By always using SSL, the authentication credentials can be simplified to a randomly generated access token that is delivered in the user name field of HTTP Basic Auth. The great thing about this is that it’s completely browser explorable – the browser will just pop up a prompt asking for credentials if it receives a 401 Unauthorized status code from the server.

However, this token-over-basic-auth method of authentication is only acceptable in cases where it’s practical to have the user copy a token from an administration interface to the API consumer environment. In cases where this isn’t possible, OAuth 2 should be used to provide a secure token transfer to a third party. OAuth 2 uses Bearer tokens & also depends on SSL for its underlying transport encryption.

Query Parameters for Advanced Filtering

It’s best to keep the base resource URLs as lean as possible.

Complex result filters, sorting requirements and advanced searching (when restricted to a single type of resource) can all be easily implemented as query parameters on top of the base URL.

  • Filtering: Use a unique query parameter for each field that implements filtering. For instance, when requesting a list of users from the /users endpoint, you may want to limit these to only those in the active status. This could be accomplished with a request like GET /users?status=active. Here, the status is a query parameter that implements a filter.
  • Sorting: Similar to filtering, a generic parameter sort can be used to describe sorting rules. Accommodate complex sorting requirements by letting the sort parameter take in a list of comma-separated fields, each with a possible unary negative to imply descending sort order. Let us see some examples: GET /users?sort=-priority -Retrieves a list of users in descending order of priorityGET /users?sort=-priority,created_at-Retrieves a list of users in descending order of priority. Within a specific priority, older users are ordered first.
  • Searching: Sometimes basic filters aren’t enough and you need the power of full-text search. When a full-text search is used as a mechanism of retrieving resource instances for a specific type of resource, it can be exposed on the API as a query parameter on the resource’s endpoint. Let’s say q. Search queries should be passed straight to the search engine and API output should be in the same format as a normal list result.

Combining these together, we can build queries like:

  • GET /users?sort=-updated_at – Retrieve recently updated users
  • GET /users?status=closed&sort=-updated_at – Retrieve recently closed users
  • GET /users?q=return&statuse=active&sort=-priority,created_at – Retrieve the highest priority active users mentioning the word ‘return’.

Query Parameters for Advanced Filtering – Alias

To make the API experience more pleasant for the average consumer, consider packaging upset of conditions into easily accessible RESTful paths. Example:

  • GET /users/recently_closed – Retrieves recently closed users

Return Fields

The API consumer doesn’t always need the full representation of a resource.

The ability to select and chose returned fields goes a long way in letting the API consumer minimise network traffic and speed up their own usage of the API.

Use a field query parameter that takes a comma-separated list of fields to include. For example, the following request would retrieve just enough information to display a sorted listing of active users:

GET /users?fields=id,subject,customer_name,updated_at&status=active&sort=- updated_at

Resource for POST, PUT and PATCH

PUT, POST or PATCH call may make modifications to fields of the underlying resource that weren’t part of the provided parameters (for example created_at or updated_at timestamps).

To prevent an API consumer from having to hit the API again for an updated representation, have the API return the updated (or created) representation as part of the response.

In case of a POST that resulted in the creation, use an HTTP 201 status code and include a Location header that points to the URL of the new resource.

JSON and XML

The API should either return JSON or XML data. Those two data formats are easily parsable. The recommended one is the JSON data type. One can also return other data types if needed.

If you are using JSON the “right” thing to do is to follow JavaScript naming conventions – and that means camelCase for field names.

By default the following naming conventions are adopted:

  • camelCase for C# & Java
  • snake_case for python & ruby.

Pagination

For pagination one can use the following query string parameters to define it:

  • Page: the page number
  • PageSize: the number of items on a page

You should provide the total count(total number of records)the total number of pages in the resource that is returned or use a custom HTTP header to provide the total count like X-Total- Count, X-Total-Page. You can add more in regard to the pagination like the previous page and next page URLs.

HTTP Status Codes

HTTP defines a bunch of status codes that can be returned from your API.

These can be leveraged to help the API consumers route their responses accordingly. I’ve curated a shortlist of the ones that you definitely should be using:

  • 200 OK – Response to a successful GET, PUT, PATCH or DELETE. Can also be used for a POST that doesn’t result in creation.
  • 201 Created – Response to a POST that results in creation. Should be combined with a Location header pointing to the location of the new resource if possible.
  • 204 No Content – Response to a successful request that won’t be returning a body (like a DELETE request).
  • 304 Not Modified – Used when HTTP caching headers are in play
  • 400 Bad Request – The request is malformed, such as if the body does not parse.
  • 401 Unauthorized – When no or invalid authentication details are provided. Also useful to trigger an auth popup if the API is used from a browser.
  • 403 Forbidden – When authentication succeeded but authenticated user doesn’t have access to the resource.
  • 404 Not Found – When a non-existent resource is requested.
  • 405 Method Not Allowed – When an HTTP method is being requested that isn’t allowed for the authenticated user.
  • 410 Gone – Indicates that the resource at this endpoint is no longer available. Useful as a blanket response for old API versions
  • 415 Unsupported Media Type – If an incorrect content type was provided as part of the request.
  • 422 Unprocessable Entity – Used for validation errors
  • 429 Too Many Requests – When a request is rejected due to rate limiting

Documentation

An API is as good as the documentation that accompanies it. One can use Swagger as a tool to document its REST API to make even easy to use.

The docs should show examples of complete request/response cycles. Once you release a public API, you’ve committed to not breaking things without notice.

The documentation must include any depreciation schedules and details surrounding externally visible API updates. Updates should be delivered via a blog (i.e. a change-log) or a mailing list (preferably both!).

Leave a Reply

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