REST API — Best Practices and Design

.NET

Photo by Kevin Ku on Unsplash

Roy Fielding introduced the REST model in 2000, revolutionizing the design of web services. This model has since become the industry standard for building modern web applications and services. RESTful web services are now the go-to solution for creating web-based APIs, and they are used by organizations of all sizes and across various industries. As a software developer, understanding how to properly design a REST API is a crucial skill.

In this article, we delve into the core principles that underlie effective REST API design. We explore the significance of a stateless API, the art of organizing the API around resources, handling exceptions gracefully, and more...

The Maturity Levels of REST API Design

When it comes to designing REST APIs, there are different stages of maturity. These levels help us gauge the quality and adherence to REST principles.

In 2008, Leonard Richardson proposed the following maturity model for web APIs:

  • Level 0: Define one URI, and all operations are POST requests to this URI.
  • Level 1: Create separate URIs for individual resources.
  • Level 2: Use HTTP methods to define operations on resources.
  • Level 3: Use hypermedia (HATEOAS, described below).

Level 3 corresponds to a truly RESTful API according to Fielding’s definition. In practice, many published web APIs fall somewhere around level 2.

The Importance of a Stateless API

REST APIs should be stateless, which means that each client request should contain all the required information to process the request, and the server does not maintain any session state or context information between requests. This statelessness is essential for several reasons:

  • Scalability: Stateless APIs allow requests to be processed by any available server, without relying on specific state from the server, making the API more scalable.
  • Availability: If a web server fails, incoming requests can be routed to another instance, with no adverse effects on client applications.
  • Caching and Optimization: Stateless APIs are more easily cached and optimized because the same response can be returned for identical requests without needing to store state information on the server.
  • Modularity and Testing: Stateless APIs are more modular and easier to maintain or test because there’s no need to set up complex session state or context information.

Making Stateful Apps Stateless

While a stateless design is advantageous, some applications require state to make progress with requests. For example, an e-commerce website needs to maintain state information for a shopping cart. To make the app stateless, follow these steps:

  1. Identify the state of the app (e.g., items, quantities, and prices).
  2. Instead of storing the state within the app, store it externally, such as in a database or cache, to ensure the app can operate independently.
  3. Include a session ID or cookie in subsequent requests to ensure that the server can access the correct cart, making interactions with the app identifiable and state retrieval possible from any server.

Organizing the API Around Resources

A well-designed API should revolve around resources, such as customers or orders, rather than actions or verbs. This approach provides a more intuitive structure and aligns with the principles of REST.

For example, consider these URI design choices:

This design philosophy leverages the HTTP protocol’s methods (GET, POST, PUT, PATCH, DELETE) to handle actions, promoting consistency across endpoints and enabling clients to make assumptions about API behavior based on their knowledge of HTTP.

The common HTTP methods used by most RESTful web APIs are:

  • GET: Retrieves a representation of the resource.
  • POST: Creates a new resource.
  • PUT: Either creates or replaces the resource..
  • DELETE: Removes the resource.

The following table summarizes the common conventions adopted by most RESTful implementations using the e-commerce example.

Source: https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design

Real-World Example: Hierarchical Resource Design

In real-world scenarios, API design can become more complex. Consider modeling an endpoint that returns customer orders with the ability to sort by an attribute and paginate the results. This involves a one-to-many relationship and can be represented with path parameters like this:

GET /customer/orders

However, there are ways to improve this API:

Tip 1: Group Entities into Collections

Entities are often grouped into collections, such as orders and customers, making the API more intuitive. Using plural nouns for URIs referencing collections provides a consistent naming convention:

GET /customers/customer/orders

Tip 2: Use Parameterized URIs for Identity

To identify a specific user, use parameterized URIs. Path parameters are recommended when specifying the identity or key of a specific resource being accessed or modified:

GET /customers/{customer_id}/orders

Tip 3: Avoid Complex Resource URIs

Avoid resource URIs that are more complex than two levels deep. For instance, instead of having customers/orders/products (which has three levels), opt for simpler URIs that serve the same purpose:

  • ❎Avoid: /customers/1/orders/99/products
  • ✅Good: /customers/1/orders
  • ✅Good: /orders/99/products

This simplifies maintenance and allows for greater flexibility in the future.

Avoid requiring resource URIs more complex than collection/item/collection.

Tip 4: Use Query Parameters for Additional Options or Metadata

To sort the collection, use query parameters to provide additional options or metadata. For example, to sort by price:

GET /customers/{customer_id}/orders?sort=price&limit=10

Query parameters are recommended for filtering, sorting, pagination, or when additional properties or options need to be passed to an operation.

Returning Structured Data

When designing a REST API, avoid returning plain text. Instead, use structured media types such as JSON, XML, or YAML. These formats provide a structured way of representing data, making it easy for client applications to parse and understand the returned data. JSON is the preferred option for REST APIs due to its wide support and ease of use in modern programming languages and frameworks.

Versioning a RESTful Web API

API versioning is crucial to manage changes without breaking clients. Various methods, such as URI versioning, query string versioning, headers, custom headers, and media types, can be used to specify the API version. The choice of versioning method depends on factors like performance, RESTful principles, and development team preferences.

Example API Versioning Methods:

  • URI Versioning: domain.com/v2/customers
  • Query Params: domain.com/customers?version=2
  • Headers: domain.com/customers with Custom-Header: api-version=2
  • Media Type: domain.com/customers with Accept: application/e-commerce.v1+json

Choosing the appropriate versioning method depends on your project’s specific needs and requirements.

Handling Exceptions

Exception handling is crucial in a REST API to prevent uncaught exceptions from propagating to the client. It’s important to catch exceptions, wrap them with descriptive messages, and use appropriate HTTP status codes. Distinguishing between client-side errors and server-side errors helps improve the user experience and allows for better error management.

Source:https://www.youtube.com/watch?v=LtNSd_4txVc

HATEOAS — Hypermedia as the Engine of Application State

HATEOAS (Hypermedia as the Engine of Application State) is the highest level of REST API maturity. It involves including links in the representation of a resource, identifying the available operations on that resource. While HATEOAS enables a discoverable and self-descriptive API, it has some disadvantages, including potential performance impact, lack of standardization, and low adoption due to limited client support and complexity.

HTTP/1.1 200 OK

{
    "account": {
        "account_number": 12345,
        "balance": {
            "currency": "usd",
            "value": 100.00
        },
        "links": {
            "deposits": "/accounts/12345/deposits",
            "withdrawals": "/accounts/12345/withdrawals",
            "transfers": "/accounts/12345/transfers",
            "close-requests": "/accounts/12345/close-requests"
        }
    }
}
Currently there are no general-purpose standards that define how to model the HATEOAS principle. The examples shown in this section illustrate one possible, proprietary solution.

Conclusion

Designing a RESTful API involves adhering to key principles such as statelessness, resource-centric design, structured data representation, versioning, and effective exception handling. By following these best practices and considering the maturity levels of REST API design, you can create APIs that are efficient, scalable, and maintainable. Remember, a well-designed API forms the foundation for successful communication between software systems, ensuring seamless integration and a positive user experience.

Thanks for reading!

Resources:

https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design

https://en.wikipedia.org/wiki/HATEOAS

https://1levelup.dev/blog/rest-api-best-practices-design