If you’ve been in the software game for a while, chances are you’ve worked with REST APIs. They’re everywhere, and many developers throw around the term “RESTful” when talking about them. But what does that really mean? The truth is, just slapping a few HTTP verbs on some endpoints doesn’t necessarily make your API RESTful. In fact, the term “REST API” itself can be a bit misleading. Let’s break down why your API might not be as RESTful as you think.
1. REST is an Architectural Pattern, Not an API
First off, let’s clear up a common misconception: REST (Representational State Transfer) is not an API. It’s an architectural pattern, much like factory or builder patterns you might use in design. REST provides a set of principles and constraints that, when followed, can make your API “RESTful.” However, just because you’re using HTTP and JSON doesn’t mean you’re actually following REST principles.
The term “REST API” is often used casually to describe web APIs that use HTTP, but not all of them are truly RESTful. It’s like calling any house a “smart home” just because it has Wi-Fi. If you don’t follow the core principles of REST, you’re not really getting the benefits that come with it.
2. REST: Definition and History
REST was introduced by Roy Fielding in his doctoral dissertation in 2000. Fielding, one of the principal authors of the HTTP specification, defined REST as a set of constraints that could be used to build scalable and reliable web systems.
“The Representational State Transfer (REST) style is an abstraction of the architectural elements within a distributed hypermedia system. REST ignores the details of component implementation and protocol syntax in order to focus on the roles of components, the constraints upon their interaction with other components, and their interpretation of significant data elements.” (Fielding, 2000, p. 76)
REST emerged as a response to the complexity of SOAP (Simple Object Access Protocol). SOAP is a protocol that focuses on operations and actions, often using XML for data transfer. While SOAP is powerful and comes with many built-in features like security and transaction support, it’s heavyweight and can be difficult to work with. REST, on the other hand, is resource-oriented and leverages HTTP directly, making it simpler and more lightweight.
Note that REST is not necessarily tied to HTTP. If you want to use your own protocol, that’s fine with REST principles.
3. REST in Simple Terms: Changing the Server’s State
At its core, REST is about the interaction between a client and a server. The client sends data to the server to change the server’s state. This is why it’s called “Representational State Transfer.”
To do this effectively, the client needs to include all the information required to transition the resource from its current state to the desired state. There’s no partial updates, no holding back — everything the server needs to know should be in that request.
“Stateless communication simplifies client-server interaction because each request can be understood in isolation. This removes the need for the server to remember the context of previous interactions, leading to improved scalability and fault tolerance.” (Fielding, 2000, p. 78)
HTTP Method: PUT /users/123
Scenario: Updating a user’s profile with new email and address information.
Invalid Payload (JSON):
{
"email": "[email protected]"
}
Valid Payload (JSON):
{
"user_id": 123,
"name": "John Doe",
"email": "[email protected]",
"address": {
"street": "123 Main St",
"city": "Anytown",
"state": "CA",
"zip": "12345"
},
"phone": "555-1234"
}
Explanation:
This payload is valid because it includes all the necessary information to represent the user’s complete state after the update. The server doesn’t need to retain any previous information about the user between requests. Everything the server needs to replace the user’s current state with the new one is present in the request.
In a truly RESTful API, the client must always send a complete representation of the resource’s state. This ensures that each request is self-contained, allowing the server to remain stateless and improving scalability.
4. Principles of REST
REST is built on a set of principles that guide how to structure and interact with APIs. Here’s a quick rundown:
Statelessness: Each request from the client must contain all the information the server needs to fulfill the request. The server doesn’t store any client context between requests.
Client-Server Separation: The client and server are separate entities, allowing them to evolve independently. This principle applies specifically to the traditional client-server model and does not extend to peer-to-peer networks or protocols like IPFS (InterPlanetary File System).
In peer-to-peer architectures, nodes act as both clients and servers, sharing resources directly with each other, which inherently blurs the distinction between client and server roles. REST’s client-server separation is not designed for such decentralized or distributed models; it works best when there is a clear, unidirectional flow of requests from clients to a centralized server.
Cacheability: Responses from the server should be explicitly marked as cacheable or non-cacheable to improve performance.
Layered System: The API should be designed in layers, with each layer interacting only with the adjacent layers, adding to the scalability and security of the system.
Uniform Interface: This is the core of REST. It includes concepts like one URI per resource, the use of standard HTTP methods (GET, POST, PUT, DELETE). Let’s dive a bit deeper into one URI per resource later. [DETAILS SEE BELOW]
HATEOAS is often overlooked but crucial. It means that your API should guide clients on what they can do next. For example, when fetching a user’s data, the response might include links to update or delete the user. This allows the client to discover available actions dynamically, making the API more flexible and decoupled from the client’s implementation. [DETAILS SEE BELOW]
Code on Demand, which allows the server to send executable code to the client, is rarely implemented due to security concerns. It’s interesting in theory, but in practice, most developers steer clear.
5. Uniform Interface and it’s details
The Uniform Interface is the cornerstone of REST. It simplifies and decouples the interaction between clients and servers by enforcing a standardized way of communication. This uniformity is what makes REST APIs predictable, consistent, and easy to use. Let’s break down its key components:
Identification of Resources via URIs:
Every resource in a RESTful API should have a unique URI (Uniform Resource Identifier). This is critical because it allows clients to interact with specific resources using consistent, straightforward paths. For example:
/users
represents a collection of users./users/123
represents a specific user with the ID 123./users/123/follower
represents a collection of followers of a specific user.
This clear mapping of URIs to resources ensures that each resource can be uniquely addressed, making the API more intuitive. Clients know exactly what to expect when they interact with a particular URI, leading to fewer surprises and easier maintenance.
Use of Standard HTTP Methods:
RESTful APIs combine the identification of resources by uris with standard HTTP methods to perform operations on resources:
GET: Retrieve a resource (safe and idempotent).
POST: Create a new resource.
PUT: Update an existing resource or create it if it doesn’t exist (idempotent).
DELETE: Remove a resource (idempotent).
As you know, each method has a specific purpose, reducing ambiguity. For instance, using POST to create resources and PUT to update them keeps the interface clean and consistent.
So in combination of HTTP Method and URIs it’s pretty straight forward how to use the API. That’s why in restful APIs actions aren’t part of the endpoints definition.
When for instance creating a new user the endpoint should be:
POST /users/
not POST /users/create_new_user/.
This design avoids confusion and duplication, ensuring that each resource has a single, clear point of access. Additionally, it aligns with the REST principle of statelessness, where the client interacts with the server via a specific URI, fully describing the desired operation or data.
Safe Operations (GET and HEAD):
In REST, certain operations like GET and HEAD are considered safe, meaning they do not modify the state of resources on the server. For example:
GET /users/123
: This retrieves the user’s data but makes no changes to it. You could repeatedly make this request without affecting the server’s state.
HEAD /products/456
: This retrieves metadata about a product without returning the actual content, still ensuring that no state is changed.
The principle that GET requests should have no side effects is critical. It allows these operations to be freely cached, retried, or redirected without unintended consequences. This makes GET requests idempotent, adding reliability and predictability to the client-server interaction.
Example of an Unsafe GET Request:
Scenario: Consider an e-commerce API where visiting a product page should track the number of views for analytics purposes. A poorly designed API might increment the view count every time a GET request is made to the product’s URI.
// Product represents a product with an ID and a view count
type Product struct { ID int `json:"id"` Name string `json:"name"` ViewCount int `json:"view_count"`
}
Server-side Action (Incorrect):
// GetProductHandler handles the GET /products/{id} request
func GetProductHandler(w http.ResponseWriter, r *http.Request) { ... // Fetch the product details product := getProductByID(id) if product == nil { http.Error(w, "Product not found", http.StatusNotFound) return } // Increment the view count (this is unsafe for a GET request) product.ViewCount++ saveProduct(product) // Return the product details as JSON w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(product)
}
Why This is Problematic
Side Effects: Every time a client makes a GET /products/456 request, the view_count is incremented. This means that just by fetching the product details, the state of the server changes, which directly violates the safe and idempotent nature of GET requests.
Unreliable Caching: Since GET requests are intended to be safe and have no side effects, they are often cached by clients, proxies, or CDNs. If a GET request changes server state, caches could cause inconsistent behavior, such as not counting all views or counting some views multiple times.
Idempotency: GET requests should be idempotent, meaning that making the same request multiple times should have the same effect as making it once. In this case, however, repeated requests would keep incrementing the view count, making the operation non-idempotent.
Correct Approach
To track product views, the correct approach would be to use a different method, such as POST, which is intended for operations that change the server’s state.
6. Understanding HATEOAS
Hypermedia as the Engine of Application State (HATEOAS) is a crucial but often overlooked aspect of RESTful architecture.
HATEOAS is a constraint of the REST architectural style that distinguishes it from other network-based architectures. When an API follows the HATEOAS principle, the client does not need to understand the full API upfront. Instead, the server provides links within the returned representations, guiding the client on the available actions. For example, a GET request to /users/123 might return the user’s data along with links to related actions such as updating the user (PUT /users/123
), deleting the user (DELETE /users/123
), or viewing the user’s orders (GET /users/123/orders
).
First introduced by Roy Fielding in his 2000 doctoral dissertation, HATEOAS is designed to ensure that RESTful systems remain decoupled and flexible. As Fielding notes:
“[A] REST API should be entered with no prior knowledge beyond the initial URI, and all interactions should be discoverable from that point forward.”
This means that the client can dynamically adjust to changes in the server’s API without requiring manual updates, as long as it follows the links and actions provided by the server. Concretely this means that the client would face no issues if the uri of a resource is changing as long as it can still be discovered from a defined entry point.
Benefits of HATEOAS for Decoupling and Avoiding Breaking Changes
HATEOAS can theoretically be a game-changer in terms of decoupling clients from servers. Here’s why:
Dynamic Navigation:
With HATEOAS, clients are not hard-coded to specific endpoints. Instead, they navigate the API based on the links provided by the server. This dynamic navigation means that even if the server changes the structure of its URIs or introduces new actions, the client can still function correctly as long as it follows the links.
Evolving APIs Without Breaking Clients:
A common challenge in API design is avoiding breaking changes when evolving the API. For example, if you change the endpoint /users/123/orders
to /accounts/123/purchases
, clients hard-coded to the old endpoint would break. However, with HATEOAS, the client doesn’t need to know the specific endpoint in advance. The server simply returns the updated link in its responses, and the client follows it without requiring any code changes. This flexibility is key to maintaining backward compatibility as the API evolves.
Guided State Transitions:
HATEOAS ensures that the client always knows what actions it can take at any given point, based on the current state of the resource. For instance, if a user’s account is locked, the client might receive a link to unlock the account, rather than being hard-coded to always display an “edit” link. This context-aware interaction prevents the client from making invalid requests and ensures a smoother user experience.
Challenges and Limitations:
Despite its theoretical advantages, HATEOAS is underutilized in many REST APIs. The complexity of implementing dynamic link resolution on the client side and the lack of tooling and standards can make it challenging. Moreover, in practice, many APIs rely on predefined endpoints and documentation, which can limit the perceived benefits of HATEOAS.
7. Real-World Challenges and Stretching REST Principles
As much as REST sounds neat in theory, reality can become messy. What happens when you need an endpoint to reset a user’s password by triggering an email? This isn’t a typical resource, and you might struggle to fit it into a RESTful design. You could treat it as a resource, something like /password-reset, but it feels a bit forced, doesn’t it?
Here are a few more common challenges:
Batch Operations: What if you need to update multiple resources at once? REST doesn’t handle this natively, and you might end up breaking statelessness or uniformity to make it work.
Complex Queries: Advanced filtering, sorting, and pagination can lead to complex URIs that stretch the “one URI per resource” principle.
Triggering Side Effects: Operations like sending emails or triggering workflows don’t always fit neatly into REST’s resource-centric model.
This is where alternatives like GraphQL and (g)RPC come into play. GraphQL, for instance, allows clients to query exactly the data they need, even across multiple resources, which can make it more efficient for certain use cases. On the other hand, gRPC offers strongly-typed contracts and high performance, making it suitable for inter-service communication in microservices architectures.
8. Conclusion — Take some rest for doing REST
Building a truly RESTful API isn’t just about using HTTP methods and JSON. It’s about embracing a set of principles that guide how clients and servers interact. As our systems evolve, it’s important to remain flexible and choose the right tool for the job — whether that’s REST, GraphQL, or gRPC.
So finally: If you need to design an API, take some rest ;-), and focus on understanding the trade-offs and knowing when to bend the rules.
Hint: if you want to discover a beautiful RESTful API, checkout github API.
Discover more from Coursity
Subscribe to get the latest posts sent to your email.