REST API introduction
REST API introduction — resources and verbs
When writing an API on top of HTTP, REST is the word that comes up most often. It stands for Representational State Transfer, organized in Roy Fielding's 2000 doctoral dissertation.
1. About REST
| Event | Time |
|---|---|
| Roy Fielding "Architectural Styles" doctoral dissertation | 2000 |
| RFC 7230 ~ 7235 (HTTP/1.1 reorganized) | 2014 |
| OpenAPI 3.0 | 2017 |
| HATEOAS resurfaces in discussion | 2010s |
The 6 constraints Fielding presented in the dissertation:
① Client-Server — separation of concerns
② Stateless — each request is independent
③ Cacheable — responses declare cacheability
④ Uniform Interface — consistent interface
⑤ Layered System — intermediaries are transparent
⑥ Code on Demand (optional) — rarely used
The most violated constraint is HATEOAS — including links to next possible actions in the response. It is usually omitted in real-world JSON APIs.
Over time the meaning has loosened, often used to mean "a JSON API that uses HTTP verbs."
2. Resource-centric design
The core of REST is "URLs point to resources, HTTP verbs point to actions." Verbs do not go into the URL.
Good shape:
GET /users (list)
GET /users/123 (one)
POST /users (create)
PATCH /users/123 (partial update)
DELETE /users/123 (delete)
Common antipatterns:
GET /getUsers
POST /createUser
POST /users/123/delete
Resource names are usually plural nouns. Nested resources only when the parent-child relationship is clear.
GET /users/123/posts (posts of user 123)
POST /users/123/posts (create a post for user 123)
3. HTTP verbs and CRUD
| Verb | Meaning | Idempotent | Safe |
|---|---|---|---|
| GET | Read | O | O |
| POST | Create / arbitrary action | X | X |
| PUT | Replace whole | O | X |
| PATCH | Partial update | Usually X (depends on impl) | X |
| DELETE | Delete | O | X |
- Safe — does not change resource state.
- Idempotent — repeated requests yield the same result.
The difference between PUT and PATCH is meaning — PUT replaces the resource with the body, while PATCH modifies parts. In practice, PATCH is used more often.
4. Response codes
| Code | Use |
|---|---|
| 200 OK | Success (with body) |
| 201 Created | Creation succeeded. Location header points to the new resource. |
| 204 No Content | Success, no body (DELETE etc.) |
| 400 Bad Request | Input error |
| 401 Unauthorized | Not authenticated |
| 403 Forbidden | Authenticated but no permission |
| 404 Not Found | Resource not found |
| 409 Conflict | Concurrency / duplicate |
| 422 Unprocessable Entity | Validation failure (semantic error) |
| 429 Too Many Requests | Rate limit |
| 500 Internal Server Error | Server error |
| 503 Service Unavailable | Transient outage |
The boundary between 400 and 422 is a matter of opinion. A common flow is 400 when JSON itself is broken, 422 when the format is fine but the meaning does not fit.
5. Pagination
Offset-based:
GET /posts?page=3&size=20
GET /posts?offset=40&limit=20
The strength is arbitrary page jumps and UI friendliness. The downsides are that large offsets get slower (the DB has to skip OFFSET rows) and that concurrent inserts and deletes cause omissions or duplicates.
Cursor-based:
GET /posts?cursor=eyJpZCI6MTIzfQ&limit=20
The response includes nextCursor. The client sends it in the next request. The strengths are O(1) paging and concurrency safety. The downside is that arbitrary page jumps are hard.
There are many opinions that cursors are recommended for large datasets and real-time feeds. Places where arbitrary jumps are needed, like admin UIs, use offsets.
6. Filtering, sorting, searching
Conventions exist but are not standards.
GET /posts?status=published&author=123&sort=-createdAt&search=react
sort=fieldorsort=-field(negative prefix for descending).qorsearchfor free-text search.- Filters as simple parameters (
status=published).
For complex filters, separate standards (JSON:API · OData · GraphQL) sometimes take over.
Reducing response size (sparse fieldset):
GET /users/123?fields=id,name,email
JSON:API and GraphQL standardize this. In REST it remains a convention.
7. Comparison with GraphQL · gRPC · tRPC
| Candidate | Place |
|---|---|
| REST | Standard, general purpose. The majority of public APIs. |
| GraphQL | Mobile and varied clients, places where the response shape changes often. |
| gRPC | Internal service-to-service, where performance and schema matter. |
| tRPC | Fast development inside TS monorepos. |
GraphQL (Facebook, 2015) — the client defines the response shape with a query. Strengths are solving over-fetching and under-fetching, single endpoint, and a strong schema. Downsides are caching difficulty, learning curve, and N+1 risk.
gRPC (Google, 2015) — Protobuf serialization + HTTP/2 + code generation. Strengths are very high speed, a strong schema, and automatic multi-language SDKs. Downsides are hard to use directly from a browser and complex debugging (binary).
tRPC — clients and servers share the same types in a TypeScript monorepo. Strengths are automatic type sharing without code generation and fast development. Downsides are TypeScript dependence and difficulty with non-TS clients.
8. Idempotency-Key · ETag
A convention to backstop idempotency on POST. Stripe popularized it.
POST /payments
Idempotency-Key: 2025-04-25-abc123
The server treats the same key on retries as the same response. Useful for payments and other critical side effects.
ETag with If-Match · If-None-Match:
- ETag — a resource version identifier in the response.
- If-None-Match — same ETag returns 304 Not Modified.
- If-Match — PUT/DELETE allowed only when the ETag matches (optimistic concurrency).
9. Common pitfalls
Doing everything via POST — that is not REST. The flow of putting verbs in URLs follows. Separate into GET/POST/PATCH/DELETE wherever possible.
Non-standard error responses — somewhere it is {"error": "..."}, somewhere else it is {"message": "..."}. Consider adopting RFC 7807 (application/problem+json).
Missing status codes — every response is 200 with success: false in the body. Clients struggle to distinguish ordinary flows from errors.
Missing versioning — URL-based like /v1/users or header-based versioning. The place where compatibility-breaking changes happen later.
N+1 queries — fetching a collection plus each item's relations one by one explodes. Eager loading or joins.
Heavy responses — returning 1000 rows at once. Enforce pagination.
CORS — preflight when the browser calls a different-origin API. Check Access-Control-Allow-Origin and -Allow-Methods.
Closing thoughts
REST is more a collection of conventions than a standard. There is no single right answer, but keeping just one or two promises like "no verbs in URLs" makes consistency visible quickly. For externally exposed APIs, pairing RFC 7807 with an OpenAPI spec is operationally safer.
Next
- websocket-sse
See Roy Fielding dissertation · RFC 9110 — HTTP Semantics · RFC 7807 — Problem Details · OpenAPI Spec · GraphQL · gRPC.