ContentForger

Guides

REST API Design Basics: Methods, Status Codes and Naming

REST API design basics for your first web app: which HTTP method to use, which status code to return, and how to name URLs consistently.

By Thomas Lovas Lokoy — 2026-10-17, last updated 2026-10-17

An old telephone switchboard with cables and lights
Photo: Vintage telephone switchboard by M McBey · CC BY 2.0 · cropped
On this page (8)
  1. What REST API design actually decides
  2. Methods: say what the request does
  3. Status codes: say what happened
  4. Naming: make URLs guessable
  5. A worked example
  6. Errors need a consistent body too
  7. Test the contract, not just the happy path
  8. Sources

Most first web apps grow an API by accident. A form needs to save something, so a route appears. A list needs data, so another appears. Six weeks later there are twenty endpoints with twenty slightly different conventions. Good REST API design is mostly about deciding a few rules early and applying them everywhere: which HTTP method means what, which status code answers which outcome, and how URLs are named. This guide covers those three decisions, using the HTTP specification itself as the reference rather than opinion.

The reference is RFC 9110, "HTTP Semantics", published in June 2022. It defines what methods and status codes mean. Everything below either comes from that document (and the MDN pages that explain it) or is labelled as a convention you are free to choose differently.

What REST API design actually decides

An HTTP API has three moving parts that a client can see: the URL it calls, the method it uses, and the status code and body it gets back. A well-designed API makes all three predictable. If someone knows how your API creates a project, they should be able to guess how it creates a task, without reading the code.

Predictability makes the front end simpler and the API easier to test. It also matters when a model writes part of your code: it will follow a consistent pattern, and copy an inconsistent one.

Methods: say what the request does

RFC 9110 defines a set of request methods. The ones you will use in almost every app are below, with MDN's one-line summaries.

MethodWhat it meansSafeIdempotent
GETRequests a representation of the specified resourceYesYes
POSTSubmits an entity to the specified resource, often causing a change in stateNoNo
PUTReplaces all current representations of the target resource with the request contentNoYes
PATCHApplies partial modifications to a resourceNoNo
DELETEDeletes the specified resourceNoYes

Safe methods

RFC 9110 says request methods are considered safe "if their defined semantics are essentially read-only", and states that "the GET, HEAD, OPTIONS, and TRACE methods are defined to be safe." In practice, a GET must never change data. Browsers prefetch links and crawlers follow them, so a GET that deletes things will eventually delete something nobody meant to.

Idempotent methods

MDN defines it plainly:

An HTTP method is idempotent if the intended effect on the server of making a single request is the same as the effect of making several identical requests.

RFC 9110 lists PUT, DELETE and the safe methods as idempotent. POST and PATCH are not guaranteed to be. MDN's example is a request that adds a row: send it three times and you get three rows.

MDN notes that a client can safely retry a request that uses an idempotent method. Retrying a PUT after a dropped connection is harmless; retrying a POST that creates an order may create two. When you design a create endpoint, decide how you will stop duplicates: a disabled submit button, a unique constraint, or a client-generated key the server checks.

PUT or PATCH?

PATCH was added by RFC 5789 in March 2010, whose abstract notes that "the existing HTTP PUT method only allows a complete replacement of a document." The difference is the size of the change. PUT sends the whole resource and replaces what is there. PATCH sends only the fields that change.

For most first apps, PATCH is the more natural fit for edit forms, because the form rarely contains every field the record has. If you use PUT, treat any field the client leaves out as something to clear, because that is what "replace" means. Mixing the two meanings in one API is how records lose data.

Status codes: say what happened

RFC 9110 groups status codes into five classes:

  • 1xx (Informational): the request was received, continuing process
  • 2xx (Successful): the request was successfully received, understood, and accepted
  • 3xx (Redirection): further action needs to be taken in order to complete the request
  • 4xx (Client Error): the request contains bad syntax or cannot be fulfilled
  • 5xx (Server Error): the server failed to fulfil an apparently valid request

The class matters most: a client that only checks for 2xx, 4xx or 5xx will still behave correctly.

The short list worth using

This set covers nearly every endpoint in a typical CRUD app.

CodeUse it when
200 OKA read or update succeeded and you are returning data
201 CreatedA new resource was created, typically after a POST
204 No ContentIt worked and there is nothing to return, for example after a DELETE
400 Bad RequestThe request is malformed, such as a body that is not valid JSON
401 UnauthorizedThe caller is not signed in
403 ForbiddenThe caller is signed in but not allowed to do this
404 Not FoundThe resource does not exist
409 ConflictThe request clashes with current state, such as a duplicate unique value
422 Unprocessable ContentThe body parsed fine but the values fail validation
500 Internal Server ErrorSomething broke on the server that the client cannot fix

401 versus 403. MDN points out that although the standard says "unauthorized", 401 semantically means "unauthenticated": the client must authenticate itself to get the response. For 403, the client's identity is known to the server, and it is still refused. Signed out is 401. Signed in as the wrong person is 403.

404 instead of 403. MDN also notes that servers may send 404 instead of 403 to hide the existence of a resource from an unauthorized client. For user-owned records this is often the better choice: telling a stranger "this invoice exists but you cannot see it" leaks information.

400 versus 422. MDN describes 422 as meaning the server understood the content type and the syntax was correct, but it was unable to process the contained instructions. A reasonable rule: if the body cannot be parsed, answer 400; if it parses but a field is invalid (an empty title, a date in the wrong range), answer 422. Pick one approach and use it consistently.

201 and the Location header. MDN says the newly created resource must be locatable either by the URL of the initiating request or by the URL in the Location header of the response. Returning the new record's URL there, and the record itself in the body, saves the client a second request.

Never 200 for an error. Returning 200 with a body that says "error" defeats every tool that reads status codes, from your own fetch wrapper to monitoring. If it failed, the status code should say so.

Naming: make URLs guessable

The HTTP specification does not tell you how to name paths. This part is convention, so the goal is consistency rather than correctness. These conventions are widely used and work well together:

  • Use nouns for resources, and let the method be the verb. A path like /projects with GET lists projects and with POST creates one. A path like /createProject duplicates what the method already says.
  • Use plural collection names. /projects for the collection, /projects/42 for one item. Choosing plural everywhere removes a decision you would otherwise make per resource.
  • Nest only one level for ownership. /projects/42/tasks reads well. Three or four levels of nesting becomes hard to maintain; past one level, a filter is usually clearer.
  • Use query strings for filtering, sorting and paging. /tasks?status=open&page=2 keeps the resource path stable while the view changes.
  • Pick one case and keep it. Lowercase with hyphens in paths, and one style (camelCase or snake_case) for JSON fields. Mixing styles is the most common inconsistency in hand-grown APIs.
  • Version once you have outside users. A prefix such as /v1 costs nothing early.

A worked example

Here is a small task app designed with these rules.

RequestMeaningSuccessCommon errors
GET /projectsList my projects200401
POST /projectsCreate a project201 with Location401, 422
GET /projects/42Read one project200401, 404
PATCH /projects/42Rename a project200401, 404, 422
DELETE /projects/42Delete a project204401, 404
GET /projects/42/tasks?status=openList open tasks200401, 404
POST /projects/42/tasksAdd a task201 with Location401, 404, 422

The table makes gaps visible. Every route that touches user data can return 401, every route with an ID can return 404, and every route that accepts a body can return 422. A route that does not fit is either a genuine exception or a mistake.

Errors need a consistent body too

Status codes say what kind of failure happened; the body should say which field and why. Choose one error shape, such as a message plus a list of field errors, and return it from every endpoint. Never include stack traces or database errors: a 500 response should say only that something went wrong, ideally with a request ID you can find in your logs.

Test the contract, not just the happy path

Call each endpoint signed out, signed in as a second user, and with a deliberately broken body, and compare the codes you get with the table you designed. The pre-launch checklist for AI-generated code includes exactly this test for 401 and 403, because it is the one most often skipped. Keep server-only keys out of these routes' responses as well; environment variables in Next.js explains how to keep secrets on the server side.

The ContentForger API reference shows the versioned-path convention from this guide on a single public endpoint.

Sources

ShareXLinkedInFacebook

Was this article helpful?

New guides in your inbox

One short email when we publish something useful — tutorials, comparisons and launch checklists. Unsubscribe in one click.

Related articles