What Happens When You Type a URL and Press Enter
DNS lookups, TCP and TLS handshakes, HTTP requests, servers, and the browser's rendering pipeline. The whole journey of a web page, explained so it finally clicks.
Read →Changing an API after clients depend on it is painful. The design decisions worth getting right early: resource naming, status codes, error format, pagination, versioning and idempotency.
An API is a promise. Once a mobile app, a partner or even your own frontend depends on it, every change becomes a negotiation. That's why a little care upfront pays off for years. These are the REST API design practices that age well.
URLs identify things. HTTP methods say what to do with them.
| Do | Avoid |
|---|---|
GET /users |
GET /getUsers |
POST /users |
POST /createUser |
GET /users/42 |
GET /user?id=42 |
PATCH /users/42 |
POST /updateUser |
DELETE /users/42 |
GET /deleteUser/42 |
GET /users/42/orders |
GET /getOrdersForUser?userId=42 |
Guidelines:
/users, /orders./users/42/orders is fine; /users/42/orders/7/items/3/notes is a sign you need flatter resources./order-items, not /OrderItems.POST /orders/7/cancel. Just be consistent.GET reads and never changes data. Crawlers, prefetchers and caches assume this.POST creates, or triggers a process.PUT replaces a resource entirely.PATCH updates part of a resource.DELETE removes it.GET, PUT and DELETE should be idempotent: calling them twice has the same effect as calling them once. That property makes retries safe.
You don't need all of them. You need these, used consistently:
| Code | When |
|---|---|
200 OK |
Successful read or update |
201 Created |
Resource created (include its location or the resource) |
204 No Content |
Success with nothing to return, e.g. delete |
400 Bad Request |
Malformed request or validation failure |
401 Unauthorized |
Not authenticated |
403 Forbidden |
Authenticated, but not allowed |
404 Not Found |
Resource doesn't exist (or the user shouldn't know it exists) |
409 Conflict |
State conflict, like a duplicate email |
422 Unprocessable Content |
Valid syntax, failed business rules (some teams use 400 for this) |
429 Too Many Requests |
Rate limit hit (rate limiting guide) |
500 Internal Server Error |
Something broke on your side |
Never return 200 with { "error": "..." } in the body. Clients, monitoring tools and caches all rely on status codes.
Pick a shape and never deviate. Clients can then handle errors with one piece of code. There's even a standard for this, Problem Details for HTTP APIs (RFC 9457):
{
"type": "https://api.example.com/errors/validation",
"title": "Validation failed",
"status": 400,
"detail": "One or more fields are invalid.",
"errors": [
{ "field": "email", "message": "Must be a valid email address" }
]
}
Include field-level details for validation errors so frontends can show messages next to the right inputs. Never include stack traces or SQL errors in production responses.
An endpoint that returns "all orders" works until there are 200,000 orders. Adding pagination later breaks clients, so add it now.
Offset pagination (?page=3&limit=20) is simple and supports jumping to page 30. It gets slow on huge tables and can skip or repeat items when data changes between requests.
Cursor pagination (?limit=20&after=eyJpZCI6MTAwfQ) returns an opaque cursor pointing to the last item. It's stable and fast at any depth. Ideal for feeds and infinite scroll.
Either way, set a sensible default and a maximum limit, and return what clients need to fetch the next page.
Keep query parameters predictable:
GET /orders?status=paid&created_after=2026-01-01&sort=-created_at&limit=20
sort with a - prefix for descending.fields=id,total,status to keep responses small.Only allow sorting and filtering on indexed columns, or you've built a very convenient way to make your database slow (indexing explained).
Even if you never expect breaking changes, leave room for them:
/v1/users) is explicit and easy to route and cache.More important than the mechanism: avoid breaking changes. Adding fields is safe. Removing or renaming fields, changing types or changing meaning is breaking. When you must, run both versions for a while and announce a deprecation timeline.
Networks fail. A client sends "create payment," the response is lost, and it retries. Without protection, you charge twice.
Accept an Idempotency-Key header on important POST requests. Store the key with the result; if the same key arrives again, return the stored result instead of repeating the action. Payment providers use this pattern for exactly this reason.
2026-03-14T09:30:00Z. (Why dates go wrong.)camelCase or snake_case and stick with it.Use nouns for resources and HTTP methods for actions, return meaningful status codes, use one consistent error format, paginate lists, version your API, and document it with OpenAPI.
Use PATCH for partial updates, which is what most edit forms do. Use PUT when the client sends the complete resource to replace it.
URL versioning like /v1/ is the simplest and most common. Whatever you choose, prefer additive, non-breaking changes so new versions are rarely needed.
DNS lookups, TCP and TLS handshakes, HTTP requests, servers, and the browser's rendering pipeline. The whole journey of a web page, explained so it finally clicks.
Read →There are dozens of HTTP status codes, but about fifteen cover nearly everything. What each one means, when to return it, and the mix-ups (401 vs 403, 301 vs 302) that confuse everyone.
Read →A, AAAA, CNAME, MX, TXT and NS records in plain English, why DNS changes seem slow, and the troubleshooting commands that tell you what is really going on.
Read →