HTTP Status Codes
Every HTTP response carries a three-digit status code that tells the client how the request went — succeeded, redirected, client error, or server error. Using the right code is not pedantry: clients, browsers, caches, search engines, and monitoring tools all make decisions based on it. Returning 200 OK for an error (a depressingly common mistake) breaks every one of them.
The five classes
Range | Class | Meaning |
|---|---|---|
| Informational | Request received, continuing (rare) |
| Success | The request succeeded |
| Redirection | Further action needed (go elsewhere) |
| Client error | The client did something wrong |
| Server error | The server failed |
Setting the status in Node
// Two equivalent ways:
res.statusCode = 201
res.end()
res.writeHead(201, { 'Content-Type': 'application/json' })
res.end(JSON.stringify(created))The codes you'll actually use
Code | Name | When |
|---|---|---|
| OK | Standard success (GET, PUT, PATCH) |
| Created | Resource created (POST) — include |
| No Content | Success, but nothing to return (DELETE, some PUTs) |
| Moved Permanently | Permanent redirect — caches/SEO update |
| Found / Temp Redirect | Temporary redirect |
| Not Modified | Cached copy is still valid (conditional GET) |
| Bad Request | Malformed/invalid request |
| Unauthorized | Not authenticated (no/invalid credentials) |
| Forbidden | Authenticated but not allowed |
| Not Found | Resource does not exist |
| Conflict | State conflict (e.g. duplicate, version mismatch) |
| Unprocessable Entity | Well-formed but semantically invalid (validation) |
| Too Many Requests | Rate limit exceeded |
| Internal Server Error | Unhandled server-side failure |
| Bad Gateway / Unavailable / Timeout | Upstream/overload/timeout |
401 vs 403 — authentication vs authorization
The cardinal sin: 200 for errors
// ✗ Wrong — masks the failure as success
res.writeHead(200).end(JSON.stringify({ error: 'user not found' }))
// ✓ Right — the code tells the truth
res.writeHead(404, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ error: 'user not found' }))201 and 204 — success beyond 200
201 Createdfor a successfulPOSTthat made a resource — include aLocationheader pointing to the new resource's URL.204 No Contentwhen there is genuinely nothing to send back (aDELETE, or aPUTthat returns no representation). With 204 you must not write a body.
4xx vs 5xx and retries
The client/server split drives retry behavior. A 4xx means the request itself is wrong — retrying it unchanged will fail again, so don't. A 5xx means a transient server problem — the same request might succeed on retry (ideally with backoff). Clients and proxies bake this in, so classify correctly:
try {
const result = await handle(req)
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify(result))
} catch (err) {
if (err.name === 'ValidationError') {
res.writeHead(422).end(JSON.stringify({ error: err.message })) // client's fault
} else {
console.error(err) // log server faults
res.writeHead(500).end(JSON.stringify({ error: 'Internal Server Error' }))
}
}