Understanding REST: Simplifying Complex Concepts for Beginners

Hleb SukrystsikHleb Sukrystsik
7 min read

Foreword

We programmers mostly work on already finished products. We come in, are given specific tasks, create a class, attach some logic to it, then apply this logic elsewhere, while also putting together the UI. That's it.

But sometimes, the task we face is to design an application from scratch when the client only has an idea in mind and needs implementation. Often, the task of starting a project is entrusted to the most experienced developers on the team. Why is that?

In my subjective opinion, the hardest part of any project is getting started. You need to decide on the services, the project structure, and the architectural patterns that will lay the foundation for other developers to build new features on.

Today, I want to talk about one of the oldest and most popular architectural patterns - REST. Let's figure out what it is, why it's still trendy, and also look at the 5 main principles that come together in this architectural pattern.

What is REST?

REST stands for REpresentational State Transfer. While the name might not immediately clarify its meaning, REST is essentially an architectural style used to create distributed hypermedia systems. It was first introduced by Roy Fielding in his well-known dissertation in 2000. Today, REST is a widely accepted method for building APIs in distributed systems, alongside GraphQL and gRPC.

Like any other architectural style, REST is defined by a set of constraints and guidelines that developers should follow to create a RESTful service. These constraints ensure that the service adheres to the principles of REST, making it efficient and scalable. Essentially, a REST API is a web API that conforms to the REST architectural style, which is characterized by these specific constraints. But what exactly are these constraints that define a REST system? Let's explore them in detail.

Uniform Interface

The 1st principle is the Uniform Interface, which means there is a clear way for the client and server to communicate. This keeps the system simple and lets each part change without affecting the other. This principle includes four smaller rules.

  1. Identifying Resources: In a RESTful system, it's crucial for both the client and server to have a clear method of identifying resources. A resource is essentially an entity that the API deals with. For instance, if an endpoint is designed to return a list of tasks, then each task is considered a resource. Similarly, if the endpoint provides information about customers, each customer is treated as a resource. This clear identification allows for precise interactions between the client and server.

  2. Manipulating Resources Through Representations: Once resources are identified, it's important that they have a consistent representation in the server's responses. This means that when the server sends data back to the client, it should always be in a format that the client can understand and use. The client can then use these representations to perform actions that change the state of the resource on the server, such as updating or deleting it.

  3. Self-Descriptive Messages: Each representation of a resource should be self-descriptive, meaning it should contain all the necessary information for the client to process the message. This includes metadata and any other details required to understand the resource's current state or to perform actions on it. By ensuring that messages are self-descriptive, the system becomes more robust and easier to work with, as clients do not need additional information to understand the server's responses.

  4. Hypermedia as the Engine of Application State (HATEOAS): In a RESTful architecture, the client only needs to know the initial URL to start interacting with the system. The server, through its responses, provides the client with the necessary links and instructions to navigate and interact with different parts of the system. This means that if a client needs to update, delete, or create a user, it simply follows the links and instructions provided in the server's responses, starting from the initial URL. This approach allows the server to control the flow of the application and guide the client through the available actions, making the system more flexible and easier to evolve over time.

Stateless

This constraint requires that every request sent by a client to a server must contain all the information necessary for the server to understand and process that request. The server is not allowed to rely on any stored state from previous interactions with the client. This principle is vital for the scalability of systems.

Consider a scenario where there are multiple instances of an API, lets say 4, managed by a load balancer. When a user sends a request, the load balancer can direct each request to a different API instance. For example, if a user's first request is handled by server 1, and the next request is directed to server 4, any state stored on server 1 would not be available on server 4. This could lead to inconsistencies if the server relied on previous state information.

As the system scales, potentially adding more servers, such as two or three more, the complexity increases. Therefore, it is essential that each request from the client is self-contained, providing all the necessary data for the server to process it independently. Consequently, any state that needs to be maintained must be stored on the client side. This approach ensures that the system remains robust and scalable, as the server does not need to manage or synchronize state across multiple instances.

Cacheable

This principle dictates that the server must clearly communicate to the client whether a response can be stored for future use, and if so, specify the duration for which it can be cached. This information can be conveyed either implicitly or explicitly, ensuring that the client understands the caching rules associated with each response.

Note: The server typically uses HTTP standardized headers to provide these caching instructions. Headers such as Cache-Control, Expires, and ETag are commonly used to define caching policies. Cache-Control might specify directives like max-age to indicate the maximum time a response should be considered fresh, or no-cache to require revalidation before reuse. The Expires header can set an absolute expiration date and time for the cached response, while ETag provides a unique identifier for a specific version of a resource, allowing the client to check if the cached version is still valid.

Client-Server

This concept emphasizes the separation of concerns between the client and the server. Imagine the server as one entity and the client as another. The key aspect they must agree upon is the contract, which is essentially the set of rules and protocols they use to communicate. This contract defines how the client and server interact, specifying the requests the client can make and the responses the server will provide.

The beauty of this architecture is that both the client and the server can evolve independently as long as they adhere to the established contract. This means that developers can update or modify the client-side application without needing to make changes to the server, and vice versa. For example, the server could undergo significant changes, such as being divided into multiple servers or microservices, to improve scalability or performance. As long as the contract remains unchanged, the client will continue to function seamlessly.

Layered System

A layered system architecture means that the client is unaware of whether it is directly connected to the end server or if there are intermediary components like load balancers in between. For instance, consider a scenario where you have a scaled-out API setup. In this setup, you might have multiple instances of Service One running in parallel to handle increased demand. Additionally, you could have Service Two operating alongside these instances. An API gateway might also be in place to manage and route requests efficiently.

The client interacts with this entire setup without needing to know the specifics of the underlying architecture. It doesn't matter to the client whether there are additional layers, such as caching servers, security layers, or additional microservices, behind the scenes. The client simply sends requests, and the system handles the rest, ensuring that the requests are directed to the appropriate services. This abstraction allows for greater flexibility and scalability, as the system can evolve and expand without impacting the client's ability to interact with it.

Summary

REST remains a foundational architectural style for building scalable and efficient APIs in distributed systems. Its principles, including the uniform interface, statelessness, cacheability, client-server separation, and layered system architecture, provide a robust framework for developers.

By adhering to these constraints, developers can create systems that are not only flexible and easy to maintain but also capable of evolving over time without disrupting client-server interactions.

0
Subscribe to my newsletter

Read articles from Hleb Sukrystsik directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Hleb Sukrystsik
Hleb Sukrystsik

I'm a .NET Software Engineer with a strong passion for technology and a beginner tech writer who loves to build projects and share valuable tips for new programmers on this blog at hlebwritescode.hashnode.dev. Small fact: speak 3 different languages (English, Polish, Russian). Feel free to reach out to me in any of these languages via Gmail or LinkedIn :) Right now, I’m heading straight toward my goal of becoming a recognized Microsoft MVP — so, will you join me on this amazing journey?