← Node & Express

REST APIs

What families of HTTP status codes exist, and which code would you use for each CRUD operation?

Show answer — try answering out loud first

Status codes are grouped into families based on the first digit: 2xx (success), 3xx (redirection), 4xx (client error) and 5xx (server error). In CRUD the typical choices are: 200 when reading, 201 when creating, 204 when deleting without returning content, 400/422 when the client sends invalid data, 404 when the resource does not exist, and 500 when something fails on the server.

The status code tells the client how its request ended. They are grouped into families:

  • 2xx – Success. The request was processed correctly.
    • 200 OK: everything is fine, and there is usually a response body (a successful read or update).
    • 201 Created: a new resource was created (typical of a POST).
    • 204 No Content: everything is fine, but there is no body to return (typical of a DELETE).
  • 3xx – Redirection. The resource is somewhere else or has not changed.
    • 301 Moved Permanently: the resource was permanently moved to another URL.
    • 304 Not Modified: the resource has not changed since last time (used with caching).
  • 4xx – Client error. The request is malformed; the problem is on the caller's side.
    • 400 Bad Request: malformed request (broken JSON, a required field is missing).
    • 401 Unauthorized: authentication is missing or the token is invalid (I don't know who you are).
    • 403 Forbidden: you are authenticated but you don't have permission (I know who you are, but you can't).
    • 404 Not Found: the resource does not exist.
    • 422 Unprocessable Entity: the syntax is correct but the data is semantically invalid (for example, an email with an impossible format).
  • 5xx – Server error. The request was fine, but the server failed.
    • 500 Internal Server Error: an unexpected error on the server.
    • 503 Service Unavailable: the server is not available right now (overload or maintenance).

Quick mental rule: 4xx = the client's fault, 5xx = the server's fault.

Which code to use for each CRUD operation

Operation HTTP verb Success Common errors
Create POST 201 Created 400 / 422 (invalid data)
Read GET 200 OK 404 (does not exist)
Update PUT / PATCH 200 OK 400 / 422, 404
Delete DELETE 204 No Content 404 (does not exist)
const express = require('express');
const app = express();
app.use(express.json());

let tareas = [{ id: 1, titulo: 'Estudiar HTTP' }];

// READ -> 200 if it exists, 404 if not
app.get('/tareas/:id', (req, res) => {
  const tarea = tareas.find((t) => t.id === Number(req.params.id));
  if (!tarea) {
    return res.status(404).json({ error: 'Tarea no encontrada' });
  }
  res.status(200).json(tarea);
});

// CREATE -> 400 if the title is missing, 201 if it is created
app.post('/tareas', (req, res) => {
  if (!req.body.titulo) {
    return res.status(400).json({ error: 'El campo "titulo" es obligatorio' });
  }
  const nueva = { id: tareas.length + 1, titulo: req.body.titulo };
  tareas.push(nueva);
  res.status(201).json(nueva);
});

// DELETE -> 404 if it does not exist, 204 if it is deleted (no body)
app.delete('/tareas/:id', (req, res) => {
  const indice = tareas.findIndex((t) => t.id === Number(req.params.id));
  if (indice === -1) {
    return res.status(404).json({ error: 'Tarea no encontrada' });
  }
  tareas.splice(indice, 1);
  res.status(204).send(); // 204 carries no body
});

app.listen(3000);
# Create a task (we expect 201)
curl -i -X POST http://localhost:3000/tareas \
  -H "Content-Type: application/json" \
  -d '{"titulo":"Repasar códigos HTTP"}'

# Request a task that does not exist (we expect 404)
curl -i http://localhost:3000/tareas/999

Returning 200 for everything, even when there are errors, and stuffing the real status inside the JSON body ({ "ok": false }). This forces the client to "guess" by reading the body and breaks tools and clients that rely on the HTTP code. Another classic mistake is confusing 401 (you are not authenticated) with 403 (you are authenticated but don't have permission), and using 200 on a creation instead of 201.

"HTTP codes are grouped by the first digit: 2xx is success, 3xx redirection, 4xx client error and 5xx server error. The mental rule I use is that 4xx is the caller's fault and 5xx is the server's fault. In a normal CRUD I return 200 when reading or updating, 201 when creating because a new resource was born, and 204 when deleting when there is nothing to return. For client errors I use 400 when the request is malformed, 422 when the structure is valid but the data doesn't make sense, 401 when authentication is missing, 403 when the user is authenticated but has no permission, and 404 when the resource doesn't exist. And I reserve 500 for unexpected server failures. The important thing is not to always return 200 and hide the error inside the JSON."

Quick challenge

A client makes POST /pedidos with a valid JSON body but the total field comes in negative, something your business does not allow. What status code would you return and why?

See answer

422 Unprocessable Entity. The JSON is syntactically well-formed (that's why it isn't strictly a 400), but the content is semantically invalid: a negative total makes no sense according to the business rules. The 422 communicates exactly that: "I understood you, but I can't process this data".

(Many APIs also accept a 400 here; the important thing is to explain the difference between "malformed" and "semantically invalid".)