hectoday
DocsCoursesChangelog GitHub
DocsCoursesChangelog GitHub

Access Required

Enter your access code to view courses.

Invalid code

← All courses REST API Design with @hectoday/http

What Makes an API RESTful

  • APIs are contracts
  • Project setup
  • Resources, not actions

HTTP Methods

  • GET, POST, PUT, PATCH, DELETE
  • Idempotency
  • Method safety and side effects

Status Codes

  • The status codes that matter
  • Error responses

Resource Design

  • Modeling resources
  • Partial responses and field selection
  • Pagination
  • Filtering, sorting, and searching

API Lifecycle

  • Versioning
  • Content negotiation
  • Rate limiting and quotas

Advanced Patterns

  • Bulk operations
  • Long-running operations
  • HATEOAS and discoverability

Putting It All Together

  • API design checklist
  • Summary

The status codes that matter

You don’t need all of them

HTTP defines over 70 status codes. That’s a lot. But in practice, a REST API uses about a dozen. The trick is knowing which ones to use and when.

Right now, our bookstore API uses 200 for successes, 201 for creates, 204 for deletes, and 400 and 404 for errors. That’s a good start. But there are a few more we need, and getting them right makes a real difference for the developers consuming your API. A meaningful status code tells the client exactly what happened before they even look at the response body.

Success codes (2xx)

200 OK is your workhorse. The request succeeded and here’s the result. Use it for: GET requests that return data, PUT and PATCH requests that return the updated resource, and any successful operation that has a response body.

201 Created means a new resource was created. Use it whenever a POST successfully creates something. You should also include a Location header that points to the new resource:

return Response.json(book, {
  status: 201,
  headers: { location: `/books/${book.id}` },
});

The Location header tells the client: “Here’s where you can find the thing I just created.” It’s a small detail that well-designed APIs include consistently.

204 No Content means the request succeeded but there’s nothing to send back. Use it for DELETE (the resource is gone, there’s nothing to return) and sometimes for PUT or PATCH when you choose not to return the updated resource:

return new Response(null, { status: 204 });

Client error codes (4xx)

These tell the client they did something wrong.

400 Bad Request means the request itself is broken. Missing required fields, wrong data types, invalid JSON. The server can’t even parse what the client sent.

401 Unauthorized means the client isn’t authenticated. No API key, no session, expired token. The client needs to log in or provide credentials before they can make this request.

403 Forbidden means the client is authenticated but doesn’t have permission. They’re logged in, but they’re not allowed to do this specific thing. The difference from 401: 401 means “who are you?”, 403 means “I know who you are, but you can’t do that.”

404 Not Found means the resource doesn’t exist. GET /books/book-999 when there’s no book with that ID. This is also sometimes used when a resource exists but the client shouldn’t know about it, to prevent users from guessing valid IDs.

409 Conflict means the request conflicts with the current state of the server. The most common case: duplicate records. A book with that ISBN already exists. A user with that email is already registered.

const existing = books.find((b) => b.isbn === isbn);
if (existing) {
  return Response.json({ error: "A book with this ISBN already exists" }, { status: 409 });
}

422 Unprocessable Entity means the request is well-formed JSON, but the data fails validation. The email format is wrong. The rating is 6 when it must be between 1 and 5. The difference from 400: 400 means “I can’t even parse this,” 422 means “I understand what you sent, but the data isn’t valid.”

[!TIP] The distinction between 400 and 422 is subtle. Many APIs use 400 for both syntax and validation errors. If you want to be precise, use 400 for malformed requests (invalid JSON) and 422 for valid JSON with invalid data. If you only want to use one, use 400.

429 Too Many Requests means the client is being rate limited. They’ve sent too many requests in a given time window. Always include a Retry-After header so the client knows how long to wait.

Server error codes (5xx)

500 Internal Server Error means something went wrong that the server didn’t expect. An unhandled exception, a database connection failure, a bug. You should never return 500 intentionally. If your code returns 500, it means something broke that you didn’t plan for.

The quick reference

Here’s a mapping of which status codes to use for each operation:

OperationSuccessNot foundValidation errorDuplicate
GET (single)200404
GET (list)200
POST201400/422409
PUT200404400/422409
PATCH200404400/422409
DELETE204404

Keep this table handy. It covers the vast majority of cases you’ll encounter.

What’s next

Status codes tell the client what happened. But when something goes wrong, the client needs more than just a number. They need to know why it failed and what to fix. That’s where error response formatting comes in. Right now, our API returns errors in different shapes from different endpoints. Let’s fix that.

Exercises

Exercise 1: Update your book routes to use the correct status codes. Make sure POST returns 201 and DELETE returns 204.

Exercise 2: Add ISBN uniqueness checking. Return 409 if a book with the same ISBN already exists.

Exercise 3: What status code would you return for “the author you’re trying to delete has books, delete the books first”? Answer: 409 Conflict, because the current state of the server prevents the operation.

When should you use 204 No Content instead of 200 OK?

← Method safety and side effects Error responses →

© 2026 hectoday. All rights reserved.