Introduction
REST is a set of "rules" or a style for building web services that allows different computer systems to talk to each other over the internet.
It stands for Representational State Transfer, meaning transferring a representation of a resource's state (data) over the network to synchronize the client's view with the server's data. Simply put, it's like sending a snapshot of current data from a server to a user so both sides show the same thing.
We often hear interchangeably these words HTTP and REST. But they are not the same thing. HTTP is a protocol. REST is an architectural style that typically uses HTTP. There are other non-RESTful styles too - gRPC for high-performance microservices, WebSockets for real-time bidirectional communication, and SOAP for structured enterprise messaging.
Why REST is BEST (in most cases)?
| Benefit | Why |
|---|
| Simple | Uses standard actions you already know (GET, POST, PUT, DELETE). |
| Scalable | It doesn't track your "session," making it easy to add more servers as you grow. |
| Flexible | It can send data in multiple formats, such as JSON (standard) or XML. |
| Fast | It can "remember" (cache) data, so the server doesn't have to work as hard. |
| Independent | You can change the backend code without breaking the frontend app. |
REST Constraints
Defined by Roy Fielding in his 2000 doctoral dissertation, REST has become the de facto standard for web APIs.To be considered truly
RESTful, a system must follow six architectural constraints.
Client-Server Separation
It means the user interface and the data storage are completely independent systems that talk to each other but don't rely on how the other is built. This independence allows you to completely rebuild a backend database or swap a frontend framework without affecting the other side, ensuring that both parts of the application can evolve and scale autonomously.
Statelessnes
Meaning every request from a client to a server be entirely self-contained. The server does not store any "memory" (session state) of the user between requests, meaning it doesn't remember who you are or what you did previously unless you send that information again.
// ❌ INCORRECT: Depends on server memory (Stateful)
app.MapPost("/checkout", (HttpContext context) =>
{
// The server is trying to "remember" the buyer from a hidden session
var buyerId = context.Session.GetString("CurrentBuyer");
});
// ✅ CORRECT: Request is self-contained (Stateless)
app.MapPost("/purchase", (PurchaseDetails cargo, ClaimsPrincipal pilot) =>
{
// All necessary info is right here in the request and the security token
var buyerId = pilot.FindFirst("MemberId")?.Value;
});
Caching
Its a rule that requires every server response to state whether it can be saved and reused. When a response is marked as "cacheable," it allows the user's browser or global delivery networks (CDNs) to display the data again later without asking the server for it twice.
// ✅ RESTful: Marking a response as reusable for 10 minutes
app.MapGet("/inventory/{sku}", async (string sku, WarehouseManager manager) =>
{
var item = await manager.FindItemBySkuAsync(sku);
return item is null
? Results.NotFound()
: Results.Ok(item);
})
.CacheOutput(p => p.Expire(TimeSpan.FromMinutes(10))); // Saves server resources for 10 mins
Uniform Interface
This is the most important REST constraint.
a. Resource Identification: Every piece of data must have a unique, permanent address (URI). You don't ask for "the data"; you ask for a specific "thing."
// Each book has its own unique, clear address
// GET /library/books/99
app.MapGet("/library/books/{isbn}", (string isbn) => $"Showing book: {isbn}");
b. Representation-based Manipulation: If you have the data (like a JSON object), you have everything you need to change or delete it. The "representation" you hold is your handle to the real data.
// The user sends a JSON "snapshot" of the book to update it
// PUT /library/books/99
app.MapPut("/library/books/{isbn}", (string isbn, Book update) =>
$"Used the provided data to update book {isbn}");
c. Self-descriptive Messages: Each request and response must explain itself. Using standard HTTP Status Codes and Content-Types tells the client exactly what happened and what kind of data they just received.
// The response explicitly says "I am JSON" and "I was found (200 OK)"
app.MapGet("/status", () => Results.Ok(new { status = "Active" }));
d. HATEOAS (Hypermedia as the Engine of Application State): The server doesn't just send data; it sends "navigational links." This tells the user exactly what they are allowed to do next (like "Edit" or "Delete") without them having to guess the URLs.
// The response includes the data AND links for what to do next
app.MapGet("/account/{id}", (int id) => new {
AccountId = id,
Balance = 500,
Links = new[] {
new { Rel = "withdraw", Href = $"/account/{id}/withdraw" },
new { Rel = "deposit", Href = $"/account/{id}/deposit" }
}
});
Layered System
The user's application does not know (or care) how the server infrastructure is structured. It doesn't matter if the request is handled by the final server, or routed through middle layers like security firewalls, load balancers, or caching services (CDNs).
Code on Demand
It remains the only optional rule in REST. It allows the server to send actual code (like a JavaScript snippet) for the user's application to run, temporarily adding new features or logic. While common in web browsers, it is rarely used for standard data APIs because most modern apps prefer to handle their own logic.
BEST Practices
Let's explore the practical patterns that distinguish high-quality, professional APIs from the messy implementations often seen in the industry.
Rule 1 : Use Nouns, Not Verbs
| ❌ Bad (Verbs in URI) | ✅ Good (Resource Nouns) |
|---|
| GET /api/fetchMembers | GET /api/members |
| POST /api/addNewMember | POST /api/members |
| PUT /api/modifyMember/7 | PUT /api/members/7 |
| DELETE /api/removeMember/7 | DELETE /api/members/7 |
| GET /api/getMemberLogs/7 | GET /api/members/7/logs |
C# example: In a clean API, the code maps the specific HTTP action to a simple, noun-based resource path:
// The HTTP Method defines the action, the URL defines the "thing"
app.MapGet("/api/members", GetMembers); // GET = Read all
app.MapPost("/api/members", AddMember); // POST = Create new
app.MapPut("/api/members/{id}", EditMember); // PUT = Update existing
app.MapDelete("/api/members/{id}", RemoveMember);// DELETE = Delete
Rule 2: Use Plural Nouns for Collections
In a clean API, the code maps the specific HTTP action to a simple, noun-based resource path:
C# Example:
// The HTTP Method defines the action, the URL defines the "thing"
app.MapGet("/api/members", GetMembers); // GET = Read all
app.MapPost("/api/members", AddMember); // POST = Create new
app.MapPut("/api/members/{id}", EditMember); // PUT = Update existing
app.MapDelete("/api/members/{id}", RemoveMember);// DELETE = Delete
Rule 3: Use Nesting to Show Relationships
When one resource belongs to another (a "parent-child" relationship), the URL should reflect that hierarchy to make the API intuitive.
| Relationship | Resource Path | Meaning |
|---|
| Driver → Deliveries | GET /api/drivers/88/deliveries | View every delivery assigned to driver 88 |
| Specific Delivery | GET /api/drivers/88/deliveries/5 | View only delivery #5 for driver 88 |
| Delivery → Packages | GET /api/deliveries/5/packages | View every package inside delivery #5 |
C# Example:
// Showing that deliveries are linked to a specific driver
app.MapGet("/api/drivers/{driverId}/deliveries", (int driverId) =>
$"Retrieving all shipments for driver {driverId}");
// Showing that packages are linked to a specific delivery
app.MapPost("/api/deliveries/{deliveryId}/packages", (int deliveryId, Parcel parcel) =>
$"Scanning a new package into delivery {deliveryId}");
But don't go too deep. Three levels is usually the maximum before URLs become wonky.
Rule 4: Path Parameters vs. Query Parameters
Path Parameters (Identification): Use these to point to a specific, unique resource. These are usually required parts of the URL.
GET /api/clients/99 → Client with ID 99
GET /api/inventory/laptop-pro-2026 → Specific product via its unique slug
Query Parameters (Refinement): Use these to filter, sort, or paginate a collection. These are typically optional modifiers.
GET /api/clients?status=active&order=joined → Filter by status and sort by date
GET /api/inventory?brand=nexus → Filter products by a specific brand
GET /api/shipments?page=3&limit=50 → Handle pagination
Rule of Thumb:
| Use Path Parameters When | Use Query Parameters When |
|---|
| Identifying a specific, unique item | Filtering or searching a list |
| The value is a required "key" | The value is an optional refinement |
| Expressing a clear hierarchy | Sorting or paginating data |
Avoid making long, confusing paths for filters. Keep your URLs clean.
C# Example:
// Path Parameter: Used to find the specific "Tenant"
app.MapGet("/api/tenants/{tenantId}", (int tenantId) =>
$"Displaying profile for Tenant #{tenantId}");
// Query Parameters: Used to filter a list of "Tenants"
app.MapGet("/api/tenants", (string? tier, int? limit) =>
$"Searching for {tier ?? "all"} tenants, showing top {limit ?? 10}");
Conclusion
Mastering REST is like learning the "universal language" of the internet. By following these rules—
using Nouns for your addresses, keeping your requests Stateless, and nesting your Routes logically—you aren't just building an API; you're building a scalable, professional system that any developer in the world can understand instantly.