REST API Design - Core Concepts and Best Practices
RESTful APIs have become the industry standard due to their simplicity and flexibility. However, despite its simplicity, REST can also be misconstrued and is not always fully understood by those who work with it. While many developers are familiar with the basics, nuances and best practices often go overlooked.
This article aims to shed light on key principles for crafting exceptional RESTful APIs.
RESTful overview
REST(Representational State Transfer) is a popular architectural style for web services and APIs. First introduced by Roy Fielding in 2000, it provides a set of principles and guidelines for building APIs, such as client-server communication, statelessness, uniform interfaces, and resource-based URLs.
A RESTful API adheres to the REST principles and guidelines. When we refer to an API as being RESTful, it means the API uses HTTP methods appropriately, organises resources with meaningful URLs, and relies on standard HTTP status codes.
Main RESTful principles
RESTful APIs adhere to a set of principles that define their architectural style. These principles, often called the “REST constraints”, are essential for creating scalable, stateless, and well-structured web services.
Client-Server communication
RESTful APIs operate on a client-server model, employing HTTP methods (GET, POST, PUT, DELETE) to manipulate resources identified by unique URLs. This architectural style promotes simplicity, flexibility, and scalability.
Statelessness
RESTful APIs operate on a stateless principle. Each request from a client is treated as an independent transaction, carrying all necessary information for processing. This ensures scalability by eliminating server-side session management, allowing for horizontal scaling and load balancing.
Cacheability
This principle encourages the use of HTTP caching mechanisms to enable efficient and controlled caching of responses from the server. RESTful APIs can specify which data is cacheable and configure caching settings accordingly. While some data may be cached to improve performance and reduce server load, we can also mark specific data as non-cacheable when freshness and real-time updates are required.
Uniform interface
RESTful APIs adhere to a standardized client-server architecture. Clients interact with servers using HTTP methods (GET, POST, PUT, DELETE) to manipulate resources identified by URIs. This consistent approach fosters simplicity, scalability, and interoperability.
RESTful API Best Practices
Adhering to REST API best practices ensures seamless communication between clients and servers, promoting interoperability and scalability. Well-designed APIs enhance developer experience, improve performance, and reduce maintenance costs.
Choose the Right Granularity
API granularity defines the level of detail exposed through individual endpoints. Fine-grained APIs offer highly specific resources, requiring multiple requests to gather comprehensive data. Conversely, coarse-grained APIs provide broader datasets in a single request, potentially reducing call volume but increasing data transfer.
Here’s an example:
1 | # coarse-grained |
Choosing the correct granular size is important. A too-fine-grained API will lead to chatty API calls, performance issues, complex architecture, and increased maintenance costs. In the above example, we need to make three requests: /user/{userID}/posts
, /user/{userID}/comments
, and /user/{userID}/profile
to get information about the user blog. In contrast, a single call /user/{userID}/blog
will provide the same amount of information.
Conversely, an overly coarse-grained API will be inflexible, have scalability issues, and is hard to reuse.
Coarse-grained API is preferred when performance is a priority. For Get
request, using coarse-grained API make it possible to get all information required with one call. It means reduced load on a web server and also better bandwidth latency.
Typical use cases for fine-grained API include mobile apps and Post/Put
requests.
There is no one-size-fits-all rule for API Granularity. The design choice is a balance between several often conflicting factors, including the following:
- The size of the response payload
- The number of API calls
- Maintainability and reusability
- Scalability
- Business needs
In a real-world project, you will find the result is often a mix of fine-grained and coarse-grained API. The bottom line is that the decision is made to satisfy the business requirement and other technical considerations. A well-designed API will be granular enough to expose necessary details but not too granular to be too chatty to use.
Do NOT Build Your API Based on Database Tables
Restful API is designed around resources. A resource can be loosely linked to business entities instead of database tables.
It is easy to mirror the API endpoints based on internal database tables. For example, for the following data structure
1 | Orders |
It might be tempting to build API based on the tables, such as
1 | GET /orders |
But this approach has several issues. First, it exposes too much information about the underlying database structure. we create a tight coupling between the API interface and database implementation. It will only make the API hard to maintain. Thus we should not expose (leak)the internal database structure to the client.
Second, it requires clients to make multiple requests to retrieve all the information about an order (e.g. the order details and the order items). This can be inefficient and slow.
A better approach would be to design a more coarse-grained API that provides all the necessary information in a single request:
1 | GET /orders?customer_id={customer_id}&status={status} |
How can we separate the domain concept from the underlying data storage when designing a REST API?
The key is to break away from a database-driven mindset and resist the temptation of quickly jumping into implementation details. Here are some tips for food for thought:
- Think of the REST API from the business domain perspective instead of the database. Gather business requirements to establish the boundary context of the API. Ask lots of questions, like, why do we need the API? What are the new capabilities to be provided? Who are the users? What are the use cases?
- Isolate the API interface from underlying implementation details. A good API design is a stable API that can last long while the implementation changes. For example, the database schema will evolve or be reimplemented with a different database. Those internal changes won’t affect the API interface if the API is designed with proper abstraction.
- Single Responsibility Principle. A good REST API should focus on one business area and do it well. Creating an API that manages multiple responsibilities will make it unnecessarily complex and hard to maintain.
Use HATEOAS Data To Make Your Client Smart
HATEOAS (Hypermedia as the Engine of Application State) is an interface of REST API which provides navigable links to related endpoints in API response. It is a principle of REST that the API should be self-descriptive, and any consumer should be able to discover and communicate with the API using the links provided.
HATEOAS is an intuitive way of expressing relationships between current resources and others. Below is an example response for a user blog API endpoint /v1/user/1/blog
. As you can see, the related links for posts and comments are embedded in them.
1 | { |
With HATEOAS, the client can discover the next action it can carry forward. Compared with hard-coded URL links in the client, the HATEOAS links make the client code more dynamic and flexible.
From an architectural perspective, HATEOAS allows a loosely coupled system. When the client implements the dynamic links using HATEOAS, the server can change the URL without updating the client code.
In Martin Fowler’s article about the REST Richardson maturity model , REST API is considered level three when HATEOAS is implemented for discoverability. Unfortunately, although HATEOAS is considered to be an essential part of REST design, it is often forgotten in real-world projects.
Although not every REST API project will have a use case for HATEOAS, it can be a very powerful feature when used in the right place. Some good use cases I have worked on include the following:
- Workflow App: HATEOAS links returned can represent the current state, i.e., the collection of links represents all the possible actions available for the next step. The benefit is that the server manages all the business logic, and the client becomes reactive.
- Manage user permissions: HATEOAS links are a natural way to pass the permissions for a resource. For example, a client app will send a Get request in loading a new screen, and the links in the Get response represent the allowed actions for the current user. Thus, the client can react to the user permissions accordingly (show/hide the buttons, etc.).
Have a consistent Errors handling Pattern
Handling error is as important as handling successful responses in a REST API. With solid error handling, developers will find it easier to troubleshoot, and the app will also be easier to maintain.
A reliable error-handling framework must have a global error handler to capture and process errors. Depending on the nature of the error, we should use an appropriate HTTP status code and map the error to a readable and informative error message. A global error handler will ensure an error is handled and returned with a consistent and concise error message.
The key has a standard error response structure for the project. The error response should contain at least “code” and “message” key-value pairs and be able to hold multiple errors. Below is a simple example:
1 | { |
Please note that the code and message field should not be the technical error code and message directly from the server. Since the response from API is exposed to clients, they need to be translated into user-friendly and readable messages, and you don’t want to leak unnecessary technical details.
Of course, you can add more properties according to your needs. Some common properties include the following:
severity
: it can beerror
|warning
|info
suberrorcode
: a lower-level error codetraceId
: id for identifying the source of the errorcorreclationId
: an internal ID to trace in the log for a complex flow.stacktraces
: the active stack frames when an error occurs. It normally should be disabled in production. Be careful not to expose sensitive information when using this field.
A useful reference is RFC 7807 : problem details for HTTP APIs. It provides guidelines on how to carry machine-readable details of errors in an HTTP response.
The client can react to the error effectively with a consistent message structure of Error response. It can also make the life of developers easier.
Summary
While creating a basic REST API is straightforward, crafting a robust and scalable solution demands careful consideration. Balancing technical constraints, business requirements, and API design principles is essential to build APIs that are not only functional but also maintainable and efficient.
- Title: REST API Design - Core Concepts and Best Practices
- Author: Sunny Sun
- Created at : 2024-08-01 00:00:00
- Updated at : 2024-08-01 19:22:53
- Link: http://coffeethinkcode.com/2024/08/01/restful-api-core-concepts-best-pracices/
- License: This work is licensed under CC BY-NC-SA 4.0.