REST API Design

What is RESTful API?

A RESTful API (RESTful web service), is based on Representational State Transfer (REST) technology, which is an architectural style and approach to communications often used in web services development.

RESTful API is mostly used for HTTP web APIs where there is no need for any additional libraries or packages to be installed.

REST itself is very flexible. There are not many limitations, but developers refer to best practices when architecting a REST API. REST has the ability to handle multiple types of requests and return different types of data (such as JSON and XML).

For example, a GET request to the following Facebook Graph API endpoint responds with the user information.

1
https://graph.facebook.com/v3.0/{user_id}

As another example, Google Geocoding API request takes the following form:

1
https://maps.googleapis.com/maps/api/geocode/json

It is important to design an intuitive and convenient API. Requests to API URLs are made using one of the HTTP methods, such as GET, POST, PUT or DELETE.

Roy Thomas Fielding is an American computer scientist, who defined the REST API Design in his 2000 doctorate dissertation.


What is HTTP?

Hypertext Transfer Protocol (HTTP) is an application layer protocol for transmitting hypermedia documents.

By default, HTTP follows the classic client-server model, where the client opens a connection to make a request and then waits for the server’s response.

For example, a web browser (on the client computer) may be the client and an application running on a computer hosting a website (the server computer) may be the server. The client submits an HTTP request message to the server. The server, which provides resources such as HTML files and other content, returns a response message to the client. The response contains completion status information about the request and the requested content in its message body.

HTTP is a stateless protocol, meaning that the server doesn’t keep any data between two requests.

HTTP messages are the blocks of data sent between HTTP applications. Each message contains either a request from a client or a response from a server. They consist of three parts : a start line describing the message, a block of headers containing attributes, and an optional body containing data.

You probably encountered HTTP Status Response Codes such as 404 (Not Found) or 500 (Internal Server Error).


HTTP Headers

HTTP headers are important parts of HTTP messages, passing along additional information along with the message body.

An HTTP header consists of a name followed by a colon ‘:’, then by its value. The name is case-sensitive.
In the example below, “Content-Type” is the name of the header and its value is “image/png”.

Content-Type : image/png

There are no line breaks between the header name and value.

Typically, when working with websites, two types of headers are sent : Request and Response.
Request headers consist of data about what the client wants to receive and about the client itself.
Response headers provide more information about the resource being sent, including information such as the server name, location, version, etc.

For example, a request header may look like this:

GET /file.html HTTP/version  
Host: HostName  
From: NameOfRequestingUser  
Cache-Control: no-cache  

The start line describes the message, meaning the server should respond to a GET request for the resource /file.html.
Host, Form and Cache-Control are sample headers.

There are more than 60 default header names and you can create any custom header by using alphabetical or numerical characters.


REST Architectural Constraint

REST (Representational State Transfer) advocates that web applications should use HTTP as it was originally envisioned.
Lookups should use GET methods.
PUT, POST, and DELETE requests should be used for mutation, creation, and deletion respectively.

REST allows independent evolution of the application without having the application’s services, models, or actions tightly coupled to the API layer itself.
This uniform interface should provide an unchanging, standardized means of communicating between the client and the server, such as using HTTP with URI resources, CRUD (Create, Read, Update, Delete) and JSON.

While designing REST API, try to make all client-server interaction stateless. The server shouldn’t store anything about the latest HTTP request client made. It should treat every request as new.
If the client app needs to keep states for an end user, then each request should contain all the information necessary to service the request (including authentication and authorization details).

Because a stateless API can increase request overhead by handling large loads of incoming and outbound calls, a REST API should be designed to encourage the storage of cacheable data.


Resource Structure

In REST, the data we are handling is called a resource.
Following a consistent naming convention makes your API easier to use. It is important to follow a correct structure for our resources.

Resource can be a Singleton or a Collection.

For example, a user is a singleton, but users is a collection.
So, we can identify a collection resource by /users URI. However, for a singleton resource, the URI will become: /users/{userID}.

sub-collection resources

Let’s imagine a sub-collection codes of a unique user: /users/{userID}/codes
Just like in the example above, a singleton code URI will become:

1
/users/{userID}/codes/{codeID}

This generally refers to the Uniform Interface constraint, where the same pattern repeats over the whole API.


Resource Naming

It is important to follow naming conventions to make your REST API look professional, readable and easy to use.

You should treat resources as a thing (noun) instead of an action (verb). The main reason is that nouns have properties but verbs don’t.
Let’s see some example of resources:

1
2
http://api.example.com/users/  
http://api.example.com/users/{ID}  

Use nouns instead of verbs to represent resources.

To simplify this further, let’s discuss 4 different types of resources, which will help you to choose a correct naming convention for your API.

Document

This type of resource is a singular concept (object). In REST API, you can view (get) it as a single resource inside the collection.
Use “singular” name to imply document resource type.

1
2
http://api.example.com/users/{ID}  
http://api.example.com/files/license-2018  

Collection

A collection resource is a directory where singular files are kept. It returns a list.
The collection allows you to add or remove new resource(s).
Use a “plural” name to imply a collection resource type.

1
2
http://api.example.com/users  
http://api.example.com/users/{ID}/posts  

Store

A store resource is a directory in which a user (client) decides when to add or remove from there.
Use a “plural” name to imply a store resource type.

1
2
http://api.example.com/users/{ID}/projects  
http://api.example.com/users/{ID}/playlists  

Controller

A controller resource is an action concept. Controller resources act like functions which may take parameters as an input and return an output.
Use a “verb” to imply a controller resource type.

1
2
http://api.example.com/users/{ID}/projects/{ID}/run  
http://api.example.com/users/{ID}/playlists/play  

Follow best practices in order to design easy to use API.


Naming Consistency

There are some more general rules that will help you to keep your REST API consistent and maintainable.

Always use slash (/) to separate your path portions

The forward slash character defines the hierarchical relationship between the resources.

1
http://api.example.com/users/{ID}/posts  

Never use trailing slash (/)

Trailing slashes do nothing and may be confusing. It is better to drop them completely.

1
2
http://api.example.com/users/{ID}/posts/ : BAD  
http://api.example.com/users/{ID}/posts : GOOD  

Hyphens (-) are good choices to separate words

Hyphens help users scan and read faster, especially in long URI names.

1
http://api.example.com/users/access-levels  

It is possible to use underscores instead of hyphens, but in some cases (based on system font) they are not clearly visible and not acceptable in URIs.

Always use lowercase letters

Using lowercase letters in URI paths is always preferred. However, the host component can be in capital letters.

Never use file extensions

They look bad in URIs and make them longer.
Use another methods to determine the file type; do not rely on the extension.

1
2
http://api.example.com/files/license.pdf : BAD  
http://api.example.com/files/license : GOOD  

Always use query component to filter URI collection

Use query components (?, &) to sort, paginate, or give any other parameters to your URI. Never create a new URI to achieve that.

1
2
http://api.example.com/projects/{ID}/run?lang=cpp  
http://api.example.com/projects/{ID}/run?lang=cpp&type=gcc  

Never use CRUD function names

Nothing in the URI should indicate what action it is performing.

1
2
3
4
5
GET http://api.example.com/users/ : GET ALL USERS  
GET http://api.example.com/users/{ID} : GET USER  
POST http://api.example.com/users/user : CREATE NEW USER  
PUT http://api.example.com/users/{ID} : UPDATE USER  
DELETE http://api.eample.com/users/{ID} : DELETE USER  

You are free to create and follow any new rules as long as they keep your URIs simple, readable, maintainable and uniform.


Read Operation (GET)

GET requests are used to retrieve resource representation / information only, not to edit in any way.
As GET requests go not change the state of the resource, these requests are known as safe methods. Additionally, GET APIs should be idempotent, which means that making multiple identical requests must produce the same result every time until another API (POST or PUT) has changed the state of the resource on the server.

For any given HTTP GET request, if a resource is found on the server, it must return HTTP response code 200 (OK) - along with response body which is usually either XML or JSON content (due to their platform-independent nature).

In case a resource is NOT found on the server it must return HTTP response code 404 (NOT FOUND).
Similarly, if it is determined that the GET request itself is not correctly formed then the server will return HTTP response code 400 (BAD REQUEST).

Examples:

1
2
GET http://api.example.com/posts  
GET http://api.example.com/posts/12345  

If the Request URI refers to a data-producing process, it is the produced data that is returned as the entity in the response and not the source text of the process, unless that text happens to be output of the process.


Create Operation (POST)

The POST operation is used to create new subordinate resources.

In terms of REST, POST methods are used to create a new resource in the collection of resources.
Passing data along with the POST request is done using the HTTP message body.

Ideally, if a resource has been created on the origin server, the response should be HTTP response code 201 (CREATED) and contain an entity which describes the status of the request and refers to the new resource.

Many times, the action performed by the POST method might not result in a resource that can be identified by a URI. In that case, either HTTP response 200 (OK) or 204 (NO CONTENT) is an appropriate response status.

Responses to this method are not cacheable unless the response includes appropriate Cache-Control or Expires header fields.

For example, a POST request to the following API endpoint creates a new “item” in the collection “items”. Additional parameters are passed along with the message body.

1
http://api.example.com/items

The POST method is neither safe nor idempotent, and invoking two identical POST requests will result in two different resources containing the same information (except resource ids).


Update Operation (PUT)

The PUT APIs are primarily used to update an existing resource (if the resource does not exist, then the API may decide to create a new resource or not).

If a new resource has been created by the PUT API, the server must inform the user agent via the HTTP response code 201 (CREATED), and if an existing resource is modified, either the 200 (OK) or 204 (NO CONTENT) response code should be sent to indicate successful completion of the request.

If the request passes through a cache and the Request URI identifies one or more currently cached entities, those entries should be treated as stale. Responses to this method are not cacheable.

Examples:

1
2
PUT http://api.example.com/users/12345  
PUT http://api.example.com/users/12345/accounts/23456  

The difference between the POST and PUT APIs can be observed in request URIs: POST requests are made of resource collections whereas PUT requests are made on the individual resource.


Delete Operation (DELETE)

The DELETE operation is used to delete a resource identified by a URI.

On successful deletion, return HTTP status 200 (OK), along with a response body, perhaps the representation of the deleted item (often demands too much bandwidth), or a wrapped response.
Alternatively, return HTTP status 204 (NO CONTENT) with no response body.

DELETE operations are idempotent. If you DELETE a resource, it is removed.
Repeatedly calling DELETE on that resource ends up the same: the resource is gone.
For example, if calling DELETE decrements a counter (within the resource), the DELETE call is no longer idempotent. The usage statistics and measurements may be updated while still considering the service idempotent as long as no resource data is changed. Using POST for non-idempotent resource requests is recommended.

Examples:

1
2
DELETE http://api.example.com/users/12345  
DELETE http://api.example.com/users/12345/posts/23456  

Calling DELETE on a resource a second time, will often return a 404 (NOT FOUND) since it was already removed and therefore is no longer available.
This, by some opinions, makes DELETE operations no longer idempotent; however, the end-state of the resource is the same.
Returning a 404 is acceptable and accurately communicates the status of the call.


API Versioning

Before releasing your REST API, you should also consider how to handle versioning, because you cannot guarantee that all of your users will always use the latest version of the API.

For example, imagine that you published an app in the app store with an API (v1). In the next release of your API (v2) which will be used in your website, you changed one of the URI calls and the response format. So if you do not support versioning, your app will crash until you update your app with the new API version.

Now that you know the reason behind the versioning, let’s see how it should be done.
The most common way of versioning an API is to include the version number in every URL. For example:

1
2
http://api.example.com/v1/users  
http://api.example.com/v2.1/users/{ID}/projects/{ID}/run  

This way the structure and version are correct in the URI, and it is simple to use.

The other approach is to include the version in the HTTP Header.
If you use this approach, you should still be able to handle the request where the version is missing and is not visible in the URI itself.

There are other types of approaches where the versioning is optional, and if a version is not specified, it will automatically get the latest version. The drawback of these approaches is that the developers should act immediately to update the apps in order not to break the experience.

Another way to version your API is to handle each platform separately. Let’s say you have Android, iOS, and Web apps and they behave differently. You may create different versions for your API to optimize for each platform separately:

1
2
3
http://api.example.com/a-v5/codes/{ID}  
http://api.example.com/i-v4/codes/{ID}  
http://api.example.com/w-v16/codes/{ID}  

Platform management is usually handled by using the HTTP headers.


Pagination and Partial Response

The idea here is not to return every resource in the database.
Your API should have enough agility to let the user customize the request with optional parameters to get exactly what they need, both in terms of types and amount.

This is especially important in mobile apps, where bandwidth and resources are limited.

Partial Response

To make a query take only specified parameters, use optional query fields. Using a comma-delimited list of fields is common.
For example, let’s build a query that will only take the user’s name, status, and the avatar.

1
http://api.example.com/users?fields=name,status,avatar  

In this case, it only returns 3 parameters of the user’s collection.
However, in the absence of the fields, it may return more than 100 parameters, which is unnecessary for us if we are just going to list all the users.

Pagination

Imagine you want to fetch a collection resource which has more than a million entries. It is bad idea to fetch all at once, as the user can only see 10-20 entries on their devices. Pagination can solve this problem.
There are two parameters for this: offset and limit.

  • offset identifies from which entry the fetching should start
  • limit identifies how many entries should be returned, starting from the offset

So, to get 20 entries per request the query should be like:

1
2
http://api.example.com/users?offset=0&limit=20  
http://api.example.com/users?offset=20&limit=20  

It is good practice to set a default limit and offset for your collection URIs, so if the limit and offset are not specified, there will not be a heavy load put on the server.
For example, you can set the default limit to 10 and set the offset to 0.


URI Design for Search

If your API is providing a search, then you need to build a robust system to handle all types of search queries. In the case of simple searches, the query should simply be:

1
http://api.example.com/users/?q=male  

But in the case of complex searches, you need to consider global and scoped search URIs to handle the search properly.

If you want to do a global search across all your resources, then the following model is recommended, where the ?q indicates the query.

1
http://api.example.com/search?q=new+web+codes  

In the case of specific search on a resource, you should prepend it with the search query.

1
http://api.example.com/users?q=pending_lessions  

We’ve dropped the explicit “/search” in the URI and are relying on the parameter ?q to indicate the scoped query.