A REST API is a contract with every app that calls it, and a predictable one is far easier to use than a clever one. A handful of conventions do most of the work: nouns in the URL, verbs in the HTTP method, accurate status codes, self-contained requests, versions, validation and documentation.
Resources are nouns
REST is built around resources: the things your API knows about, such as cats, orders or invoices. Each resource gets a URL, and the URL names the thing, not what you want to do with it.
/cats the collection of cats
/cats/123 one cat, with id 123
/cats/123/toys the toys that belong to cat 123Compare that with /getAllCatsNowPlease or /deleteCat?id=123. Those URLs bake an action into the path, so every new operation needs a new endpoint with its own name to learn. With noun URLs, a developer who has seen /cats can guess /cats/123 and /owners/7 without reading anything.
A few habits keep resource URLs tidy:
- Use plural nouns for collections (
/cats, not/cat), and add the id to reach one item. - Nest only one level deep where it shows ownership (
/cats/123/toys). Deeper paths like/owners/7/cats/123/toys/9get brittle; the toy can usually live at/toys/9. - Keep them lowercase, with hyphens between words (
/vet-visits). - Put filtering, sorting and paging in the query string:
/cats?colour=ginger&sort=age&page=2. The resource is still/cats; the query narrows it.
Some operations don't map neatly onto a noun, like 'send a reminder' or 'cancel an order'. Often there's a resource hiding in them: cancelling is a change to the order's status, and a reminder can be a new item in /reminders. When there isn't one, a sub-resource such as POST /orders/42/cancellation keeps the pattern intact.
HTTP methods are the verbs
If the URL is the noun, the HTTP method says what you're doing to it. Here are the five you'll use most, on the same tuna resource:
| Method and URL | What it does |
|---|---|
GET /tuna/123 | Fetch tuna 123 |
POST /tuna | Create a new tuna |
PUT /tuna/123 | Replace tuna 123 completely |
PATCH /tuna/123 | Change some fields of tuna 123 |
DELETE /tuna/123 | Remove tuna 123 |
The URL /tuna/123 stays the same across four of those rows. Only the method changes, and the intent is clear from it alone.
PUT or PATCH
Both update, but differently. PUT sends the whole resource and replaces what's stored, so a field you leave out is gone. PATCH sends only the fields that change and leaves the rest alone. If a client only wants to rename tuna 123, PATCH with {"name": "Bluefin"} is the safer choice; a PUT with only that field would wipe out everything else.
Safe and idempotent
Two properties from the HTTP specification explain why the choice of method matters:
- Safe methods don't change anything on the server.
GETis safe, so browsers, caches and crawlers can call it freely. Never put a delete behind aGETlink. - Idempotent methods give the same end result whether you send them once or five times.
GET,PUTandDELETEare idempotent: deleting tuna 123 twice still leaves it deleted.POSTis not, because each call creates another tuna.
That matters when a network drops a request. A client can safely retry a PUT, but retrying a POST might create a duplicate order.
Status codes say what happened
Every response carries a three-digit status code, and clients rely on it before they read the body. The first digit gives the family: 2xx is success, 4xx means the client got something wrong, and 5xx means the server failed.
The video's four cover most responses:
200 OK: the request worked, and the body has the result.201 Created: aPOSTmade something new. Send aLocationheader with the new resource's URL.400 Bad Request: the request itself is wrong, such as a missing field or a malformed date.404 Not Found: there's no resource at that URL.
You'll meet a few more often. 204 No Content suits a successful DELETE with nothing to return. 401 Unauthorized means the caller isn't signed in, and 403 Forbidden means they are but aren't allowed (the difference between authentication and authorisation). 409 Conflict covers clashes such as creating a user whose email already exists, and 500 Internal Server Error means the bug is on your side.
The biggest mistake here is returning 200 with an error hidden in the body, like {"success": false}. Monitoring, retry logic and HTTP libraries all look at the status code, so they treat that failure as a success. Pick the code that matches what happened, then give a clear error body as well:
{
"type": "validation-error",
"title": "The request has invalid fields",
"status": 400,
"errors": {
"weightKg": "Must be greater than 0"
}
}That shape follows Problem Details (RFC 9457), a standard format for HTTP API errors. Using the same error shape on every endpoint means clients write one error handler, not twenty.
Every request stands alone
REST APIs are stateless: the server keeps no memory of a client between requests. Each request carries everything needed to handle it, including who is asking (usually a token in the Authorization header), which resource and any data.
GET /cats/123
Authorization: Bearer eyJhbGciOi...The server checks the token, does the work and forgets the conversation. The next request brings its token again.
Statelessness pays off when you run more than one server. With no per-client memory in the server, a load balancer can send request one to server A and request two to server B, and both can answer. Restart a server and nobody gets logged out mid-task. Compare that with a server-side session, where a cookie points at data held on one particular machine.
The API still has data: cats, orders and accounts live in a database. What the server drops is the conversation, so a request never depends on what the previous request did.
Version your API
Once other apps depend on your API, changing it can break them. Versioning lets you make those changes without breaking the clients you already have. The most common way is a version in the path:
/v1/cats
/v2/catsOld clients keep calling /v1, new ones move to /v2, and both run side by side until /v1 is retired. Some APIs put the version in a header instead, which keeps URLs clean but is harder to test from a browser.
You only need a new version for a breaking change: removing or renaming a field, changing its type, making an optional parameter required, or changing what a status code means. Adding a new optional field or a new endpoint doesn't break anyone, so it can ship in the current version. When you do retire an old version, announce a date, tell the clients still calling it, and give them time to move.
Validate and sanitise input
Treat every request as untrusted, even from your own front end. Anyone can send any JSON to a public URL, whether by mistake or on purpose.
Validation checks that the input is what you expect before you act on it:
- required fields are present;
- types are right (a number is a number, a date parses);
- values are in range (a weight above zero, a name under 100 characters);
- choices are from a known list (
statusis one ofavailable,adopted).
When a check fails, stop and return 400 with an error body that names the field and the problem, so the client can fix it.
Sanitising makes sure data can't be turned against you later. Build database queries with parameters, never by gluing input into a SQL string, to stop SQL injection. Escape text before showing it in HTML. And only accept the fields a client is allowed to set: if your update handler copies every incoming field onto the record, someone can send "isAdmin": true and promote themselves.
Client-side checks in a form are a convenience for the user. The server's checks are the ones that protect your data.
Document everything
Other developers can only call your API correctly if they can find out how it works. Good documentation says which endpoints exist, what each one accepts and returns, which status codes and errors to expect, and how to authenticate, with an example request and response for each.
The OpenAPI Specification is the standard way to describe a REST API in a YAML or JSON file. Tools then turn that file into interactive documentation, client libraries and request validation. Many frameworks can generate it from your code, so the docs change when the code does. Whichever way you produce it, update the documentation in the same change as the endpoint.
Common mistakes
- Verbs in URLs, such as
/createCator/getCats. The method already says it. 200for everything, with the failure buried in the body.- Changing fields in place without a new version, breaking clients overnight.
- A
GETthat changes data, which a crawler or cache can trigger by accident. - Trusting client-side validation and skipping the server's own checks.
- Inconsistent naming, like
/catsnext to/Dogand/owner_list, so every endpoint has to be looked up.
Key takeaways
- URLs name resources with nouns; HTTP methods (
GET,POST,PUT,PATCH,DELETE) are the verbs. - Return the status code that matches what happened, with a consistent error body.
- Each request carries everything the server needs, so any server can answer it.
- Version the API and save new versions for breaking changes.
- Validate and sanitise every input on the server, and document every endpoint, error and example.