1. Library
  2. Http and the Web
  3. Apis

Updated 10 hours ago

Every web page you've ever visited worked the same way: you typed a URL, your browser asked for it, and a server sent back a representation. Links took you to other URLs. Bookmarks remembered where things were. The back button worked because each page was a distinct thing at a distinct address.

REST is the recognition that APIs should work the same way.

The Philosophy

REST (Representational State Transfer) isn't a protocol. It's not a standard you implement. It's an architectural philosophy that says: the patterns that made the web successful should also make your API successful.

Roy Fielding, who helped design HTTP itself, articulated REST in his 2000 doctoral dissertation. But he wasn't inventing something new—he was describing what already worked. The web scaled to billions of pages because of specific architectural choices. REST says: make those same choices for your API.

The name tells you everything. When you request a resource, you get a representation of its current state, transferred to you. That's it. That's REST.

Resources: Things With Addresses

A resource is anything you can name. A user. A document. A shopping cart. A search result. If you can talk about it, it can be a resource.

Every resource gets an address—a URI:

/users/123           # A specific user
/posts/456/comments  # Comments on a specific post
/products            # The collection of all products

This seems obvious. Of course things have addresses. But watch how quickly developers abandon this when building APIs. They create endpoints like /getUserProfile or /fetchOrderDetails—verbs pretending to be addresses. REST says no. A user profile lives at /users/123. You don't fetch it—you request it, the same way your browser requests any URL.

Resources are nouns. Always nouns.

HTTP Methods: The Verbs You Already Have

HTTP already has verbs. Use them.

GET retrieves a resource. It never changes anything. You can call it a thousand times and nothing happens except you get the same thing back. This is called being safe and idempotent.

GET /users/123      # Retrieve user 123
GET /posts          # Retrieve all posts

POST creates something new. Call it twice, you might get two new things. It's neither safe nor idempotent—it changes the world.

POST /users         # Create a new user
POST /comments      # Create a new comment

PUT replaces a resource entirely. Send the complete new version. Call it five times with the same data, you get the same result—idempotent.

PUT /users/123      # Replace user 123 with this data

PATCH updates part of a resource. Just the fields you're changing.

PATCH /users/123    # Update specific fields

DELETE removes a resource. Delete something twice? It's gone either way—idempotent.

DELETE /users/123   # Remove user 123

The web already knew how to do this. REST just said: do it on purpose.

Status Codes: The Response's First Word

Before you read the body, the status code tells you what happened.

2xx—it worked:

  • 200 OK: Here's what you asked for
  • 201 Created: Made the new thing you wanted
  • 204 No Content: Done, nothing to show you

4xx—you did something wrong:

  • 400 Bad Request: I don't understand what you sent
  • 401 Unauthorized: Who are you?
  • 403 Forbidden: I know who you are. No.
  • 404 Not Found: Nothing at this address
  • 409 Conflict: Can't do that—it contradicts current state
  • 429 Too Many Requests: Slow down

5xx—the server broke:

  • 500 Internal Server Error: Something went wrong on our end
  • 502 Bad Gateway: Something behind us broke
  • 503 Service Unavailable: We're overwhelmed or down for maintenance

Using the right status code isn't pedantry. It's communication. A client that gets 401 knows to re-authenticate. One that gets 429 knows to back off. One that gets 404 knows not to retry.

The Constraints That Make It Work

REST isn't just "use HTTP correctly." It's a set of architectural constraints that enable scale, reliability, and evolution.

Statelessness: Every request contains everything the server needs to process it. No "session" on the server remembering who you are between calls. This sounds inconvenient—you have to send your credentials every time. But it's what lets any server handle any request. No sticky sessions. No synchronization nightmares. Scale horizontally forever.

Client-Server Separation: The client and server evolve independently. As long as the interface stays stable, you can rewrite either side completely. Your iOS app and Android app and web app all talk to the same API. The server doesn't care what kind of client you are.

Cacheability: Responses say whether they can be cached. When they can, intermediaries (CDNs, browser caches, proxy servers) can serve them without hitting your server. This is how the web handles billions of requests—most of them never reach the origin.

Layered System: Clients don't know whether they're talking to the final server or an intermediary. This lets you add load balancers, caches, security layers, and API gateways without changing client code.

Uniform Interface: Everyone interacts with resources the same way. You don't need special knowledge to use a new REST API—you already know the verbs, you understand URIs, you can read status codes. The patterns transfer.

Collections and Individuals

URIs distinguish between collections (groups of resources) and individual resources.

GET /users          # The collection of users
GET /users/123      # A specific user

POST /users         # Add to the collection (create a user)
DELETE /users/123   # Remove from the collection (delete a user)

Nested resources show relationships:

GET /users/123/posts      # Posts by user 123
GET /posts/456/comments   # Comments on post 456

But don't nest too deep. /users/123/posts/456/comments/789/likes is unwieldy. If a resource can stand alone, give it a top-level address.

Query Parameters: Refining Collections

Query parameters filter, sort, and paginate collections without creating endless specialized endpoints.

GET /users?role=admin              # Filter by role
GET /posts?sort=-created_at        # Sort descending by date
GET /users?page=2&per_page=20      # Pagination
GET /users?fields=id,name,email    # Select specific fields

The collection resource stays the same. The query parameters refine what you're asking for.

Request and Response Anatomy

A typical REST exchange:

Request:

POST /users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer token123

{
  "name": "Jane Smith",
  "email": "jane@example.com"
}

Response:

HTTP/1.1 201 Created
Content-Type: application/json
Location: /users/789

{
  "id": "789",
  "name": "Jane Smith",
  "email": "jane@example.com",
  "created_at": "2024-01-15T10:30:00Z"
}

Notice: 201 Created (not 200). Location header tells you where the new resource lives. The response includes the created resource with its server-assigned ID. Everything communicates.

Why Idempotency Matters

In distributed systems, requests fail. Networks drop packets. Clients retry. What happens when a retry succeeds but the original also succeeded?

With idempotent operations, nothing bad:

  • GET the same resource twice? You get the same thing.
  • DELETE the same resource twice? It's deleted (or already was).
  • PUT the same data twice? Same result.

With POST, you might create duplicates. That's why careful API design uses idempotency keys—a unique identifier the client sends so the server can recognize retries.

Understanding idempotency isn't academic. It's how you build reliable systems.

What Goes Wrong

GET with side effects. Never. GET must be safe. If someone's browser prefetches links, if a crawler indexes your API, if a cache serves a stale response—GET must not change anything.

200 OK with error in body. The status code is for machines. If you return 200 with {"error": "User not found"}, clients that check status codes think everything worked.

Verbs in URLs. /createUser, /deletePost, /updateOrder—you've recreated RPC with extra steps. Let HTTP methods be your verbs.

Inconsistent conventions. snake_case here, camelCase there. /user singular, /products plural. Pick conventions and hold them.

Breaking changes without versioning. When you change what an endpoint returns, you break every client. Version your API from day one.

The Simplicity Trap

REST looks simple. Resources, verbs, status codes—anyone can understand it in an afternoon.

The trap is thinking that understanding means following. Developers constantly reinvent RPC, creating endpoints like /api/performAction because it feels more direct than modeling resources. They ignore status codes because checking them is extra work. They skip versioning because the API is "internal."

REST's constraints are easy to understand and hard to follow. The discipline is the point. The web scaled to billions because these constraints create systems that compose, cache, and evolve. Your API can inherit that heritage—if you actually follow the constraints.

Frequently Asked Questions About REST APIs

Was this page helpful?

😔
🤨
😃