REST API design: resource modeling, status code, versioning
1. Resource modeling
- A URL represents a resource, named using nouns rather than verbs:
GET /orders/5is more standard thanGET /getOrder?id=5. - The action to perform on a resource is determined by the HTTP method, not the URL name. The same URL is reused across different actions depending on the method.
- 5 standard methods for CRUD operations on a resource:
| Method + URL | Action | Request body? | Idempotent? | Safe? |
|---|---|---|---|---|
GET /orders | Retrieve a list | No | Yes | Yes |
GET /orders/5 | Retrieve 1 resource | No | Yes | Yes |
POST /orders | Create new | Yes | No | No |
PUT /orders/5 | Replace entire resource, requires sending all fields | Yes | Yes | No |
PATCH /orders/5 | Partial update, only requires fields to change | Yes | Depends on implementation | No |
DELETE /orders/5 | Delete | No (typically) | Yes | No |
-
Safe: invoking the method does not alter resource state on the server (read-only).
GETis the only safe method in the table above. -
Idempotent: calling the endpoint 1 time or N times results in the exact same final resource state on the server (excluding metadata fields like
updated_at, and excluding HTTP response differences across calls).
| Method | Idempotent |
|---|---|
| GET | Yes |
| PUT | Yes |
| DELETE | Yes (from the 2nd call onwards, the "non-existent" state does not change further, even if the response status varies between 200/404) |
| POST | No (unless an idempotency key is explicitly implemented) |
| PATCH | Depends on implementation, typically not guaranteed |
2. Status code
Success group (2xx):
| Action | Status code |
|---|---|
GET (read, with data) | 200 OK |
POST (create) | 201 Created, accompanied by a Location header pointing to the newly created resource URL |
PUT/PATCH (update) | 200 OK if returning the updated data |
DELETE (delete) | 204 No Content, no body returned because the resource no longer exists |
Client error group (4xx):
| Code | Meaning |
|---|---|
400 Bad Request | Malformed syntax/missing required payload, validation error, occurs before identity/permission verification |
401 Unauthorized | Identity unverified (not logged in, invalid/expired/missing token) |
403 Forbidden | Identity verified, but lacks sufficient permissions to perform the action |
404 Not Found | Resource does not exist |
- Distinguishing
401vs.403enables proper frontend handling: receiving401triggers a redirect to the login page, while receiving403displays an unauthorized access message without forcing re-authentication.
3. API versioning
The core problem to solve: changing the response schema (a breaking change) can break legacy clients (e.g. published mobile apps that have not updated) consuming the same API.
3 common approaches:
- URL versioning:
GET /v1/orders/5vs.GET /v2/orders/5. Simple, easy to debug (version is visible in the URL), and most widely used in practice. Theoretical downside:/v1/...and/v2/...are treated as distinct URLs for the same underlying resource, slightly deviating from strict REST philosophy. - Header versioning: maintains clean URLs by sending headers such as
Accept: application/vnd.myapp.v2+jsonorX-API-Version: 2. Keeps URLs clean and adheres closer to REST principles, but makes debugging harder since the version cannot be inspected directly from the URL. - Query parameter:
GET /orders/5?version=2. Less common, typically used for temporary versions or feature experiments.
URL versioning remains the most popular real-world choice due to its simplicity, maintainability, and ease of documentation.
