REST APIs
Resource modelling, URLs, verbs, idempotency, versioning.
REST APIs
REST (Representational State Transfer) is a set of constraints for designing HTTP APIs. It is not a standard or a protocol — it is a style. An API is "RESTful" when it treats resources as nouns, HTTP methods as verbs, and URLs as stable addresses for those resources.
Analogy
A REST API is like a well-organised filing cabinet in a busy office. Every folder (resource) has a permanent label and a fixed location — the "Customers" drawer, folder 42, subfolder "Orders" — so anyone who knows the filing system can find the same paper again tomorrow. You do not invent new verbs for every task: you slide a paper in (POST), pull one out to read (GET), replace a whole sheet (PUT), cross out one line with a pen (PATCH), or shred the folder (DELETE). The cabinet itself never renames its drawers to match what you plan to do — it just holds nouns, and staff do the verbs.
Resources and URLs
A resource is any thing your API manages: a user, an order, a file. Each resource has a URL. URLs should be stable, lowercase, and noun-based — they identify things, not actions.
/users ← the collection of all users
/users/42 ← one specific user
/users/42/posts ← posts belonging to user 42
/posts ← the collection of all posts
/posts/7 ← one specific post
Avoid verbs in URLs. /users/42/deactivate is not RESTful. Instead: PATCH /users/42 with { "active": false } in the body.
Verbs on resources
Map HTTP methods to CRUD operations:
| Method | URL | What it does |
|---|---|---|
| GET | /users | List all users |
| POST | /users | Create a new user |
| GET | /users/42 | Fetch user 42 |
| PUT | /users/42 | Replace user 42 entirely |
| PATCH | /users/42 | Partially update user 42 |
| DELETE | /users/42 | Delete user 42 |
Idempotent vs safe
| Safe | Idempotent | |
|---|---|---|
| GET | Yes | Yes |
| HEAD | Yes | Yes |
| POST | No | No |
| PUT | No | Yes |
| PATCH | No | No* |
| DELETE | No | Yes |
*PATCH can be made idempotent if you design the body carefully (e.g., { "set": { "name": "Sam" } } rather than { "increment": { "loginCount": 1 } }).
Response shape conventions
Collections return arrays:
{
"data": [
{ "id": 1, "name": "Alice" },
{ "id": 2, "name": "Bob" }
],
"meta": { "total": 2, "page": 1 }
}
Single items return objects:
{
"id": 42,
"name": "Sam",
"email": "sam@example.com",
"createdAt": "2025-01-15T10:30:00Z"
}
Errors should be consistent:
{
"error": "not_found",
"message": "User 42 does not exist",
"status": 404
}
Versioning
APIs break clients when they change. Version your API from day one.
URL versioning (most common, most explicit):
/v1/users
/v2/users
Header versioning:
Accept: application/vnd.example.v2+json
URL versioning is simpler to route, cache, and debug. Run both versions in parallel while clients migrate. When v1 traffic hits zero, decommission it.
Pagination
Never return unbounded collections. Limit with:
GET /users?page=1&per_page=25
GET /users?cursor=eyJpZCI6MjV9&limit=25
Include navigation links or cursor in the response so clients don't need to guess:
{
"data": [...],
"meta": {
"page": 1,
"per_page": 25,
"total": 312,
"next": "/v1/users?page=2&per_page=25"
}
}
What REST is not
REST does not require JSON. It works with any content type. REST does not require HTTPS, though you should always use it. REST is not RPC — do not model procedures (/sendEmail) as REST endpoints. REST also does not prescribe authentication; that is a separate concern.