REST APIs are nowadays the de-facto standard for Web applications. However, as more systems and services adapt REST architectural style, many problems arise regularly. To avoid these repetitive problems, developers could follow good practices and avoid bad practices. Thus, research on best and bad practices and how to design a simple but effective REST API is important. Yet, to the best of our knowledge, there are only a few concrete designs to recurring REST API practices, like ``API Versioning''. There are works on defining or detecting some practices, but not on designs to the practices. We present the most up-to-date list of REST API practices and propose designs, in the form of anti-patterns, to make a REST API compliant.


1. REST API (Anti)-Patterns' Best Practices

1.1. Entity Linking

Problem

{
  "post": {
    "title": "Lorem ipsum",
    "content": "Lorem ipsum",
    "links": [
      {
        "rel": "comment",
        "method": "post",
        "uri": "/post/123/comment"
      },
      {
        "rel": "like",
        "method": "get",
        "uri": "/post/123/like"
      }
    ]
  }
}

In the above sample response, the server returns the link to related resources. This pattern is Entity Linking.

Best Practice

The method that handles the current request will have a list of related classes containing the related resources. To loop through the list, all these classes should be an implementation of an interface. Accessing methods implemented the same interfaces in different classes, based on the class type (i.e., dynamic biding), is called double-dispatch.

To implement double-dispatch in a high-level programming language, we could use Visitor Pattern.

entity linking best practice

All the controllers that are intended for populating links for the related resources will be the implementation of the LinkedResource interface. This interface has a method accept() that accepts a visitor instance of type ResourceVisitor. For each logic to select the resource to be included, a separated concrete Visitor will be created. These visitors could share the common logic in the abstract class CommonResourceVisitor. Developers can put hide the complexity of language refection logic here.

The sample implementation with reflection is available for Java Spring and ASP.NET Core (see https://git.io/JGRWW and https://git.io/JGRW8).

1.2. API Versioning

Problem

The server should support API Versioning to allows the coexistence of multiple versions of the API.

Best Practice

Use Proxy design pattern to redirect/convert the request to the old version of the API. For each version of the exposed API, there should be a corresponding interface. The interface does not serve a purpose in the current version but will be the contract for the next version. The class will contain a private instance of each old API class that supports this next version. Thus, all the API versions will have some common logic that did not change over time. This logic can be separated into an abstract class. The API controllers inherit from it.

api versioning

1.3. Server Timeout

Problem

When there is a long-running operation on the REST API server, the client needs to wait to receive a response. In a traditional system, the communication between the running operation and the object that consumes the operation result is permanent. However, in a REST API system, the servers communicate with the consumers (the client applications) through the network. This introduces some potential problems: if the client and the server disconnected, the operation continues to run on the server, but the result was no longer needed. The same if the client cancels or abandons the request. These problems will waste the server's resources.

Best Practice

A combination of Timeout and Asynchronous Request-Reply pattern (HTTP Polling) by William Eastbury

The design will require some extra work from both server and client developers. Developers need to implement an operation status checker and a cache system to store the operation result on the server. On the client side, the developers need to implement a polling mechanism to periodically get the operation status and finally get the response from the Resource Endpoint.

server timeout

Detailed steps:

  • When the client sends the request that triggers a long-running operation, the server will respond as quickly as possible with 202 Accepted HTTP code.
  • The server continues its long-running operation while the client is periodically polling for the result.
  • When the result available, the polling will return with 302 Found HTTP code with the URI for the result.
  • The client sends a GET request to get the result.

In case the client is disconnected, the server wait for a timeout then terminates the operation.

1.4. POST-PUT-PATCH return

Problem

When the client application sends a request to modify the database (Create, Update, Delete), the server usually returns a simple result indicating a successful request like HTTP status code 200 OK. However, there is no way for the client application to know the added/modified object was correctly committed to the database unless the client makes a new request for the object.

Best Practice

To separate the database manipulation logic from business logic, we could use the Repository pattern. To ensure a database transaction was successfully executed before returning the result to the user, we could use the Unit of Work pattern.

postputpatch return

The Unit of Work with ASP.NET framework was supported in a separate library called Entity Framework that works like an Object Relational Mapping (ORM) framework.

Java Spring has a Repository pattern built-in with the basic CRUD operations. ASP.NET Core does not have this function built-in. Still, there is an instruction on how to implement them. In addition, both frameworks support Dependency Injection and Unit of Work.

Sample implementations exist for Java Spring and ASP.NET Core.

1.5. Content Negotiation

1.5.1. Problem

The client can only process and manipulate the resources in some formats.

1.5.2. Best Practice

We could use factory design patterns to initiate the object in the required format. For each set of required formats, there should be a corresponding concrete Factory (i.e., XML and JSON will have a concrete factory. PNG, JPG and Base64 will have another concrete factory).

content negotiation

1.5.3. Existing Framework Supports

Both Java Spring and ASP.NET core support content negotiation with the default set to JSON format. The table below compares content negotiation implementation in popular frameworks.

Java Spring ASP.NET core Our approach
Common media types
Yes Yes Yes
Customizable serializer
No Yes Yes
Do not require data annotation on model No Yes Yes
Built-in support ignorable
Yes Yes N/A

1.6. List Pagination

Problem

In the REST API system, any API endpoint that returns data type as a list should support pagination, even if the list is typically small. Developers do not know if a list will grow in the future. Adding pagination support for a list after the API was published is not a good idea since the clients already assumed they have all items, while they only have the first page.

Common Design Patterns - https://cloud.google.com/apis/design/design_patterns

Frameworks support

Fortunately, the popular frameworks already support pagination in some ways.

For example, Java Spring has a PagingAndSortingRepository interface that helps developers quickly implement pagination with the help of the Pageable interface.

For ASP.NET, the pagination capability was built-in with Entity Framework Core, the open-source ORM by Microsoft, using Skip and Take methods on a collection of type IQuerytable. The use of these method require some conversion of the parameters: Skip(itemPerPage * pageNumber) and Take(itemPerPage). Then developers can use it as follow:

var result = _dbContext.transaction
                       .Skip(itemPerPage * pageNumber)
                       .Take(itemPerPage);

For other frameworks that do not have ORM libraries, the developers could use the LIMIT and OFFSET clauses of SQL, depending on the database technology. For example, for SQL Server:

SELECT *
FROM your_table
ORDER BY column_name ASC
OFFSET number_of_skipped_row ROWS FETCH NEXT max_number_of_row ROWS ONLY;

2. Evaluation

To evaluate our best practice, we surveyed experienced developers.

The survey link can be found here:

The initial survey result showed that most of the answers agree with our design. We are interviewing the participants with exciting responses to understand their thought.