DEV Community

Alex Chen
Alex Chen

Posted on

HTTP Status Codes: The Complete Developer Reference

HTTP Status Codes: The Complete Developer Reference

Stop guessing what status code to return. Use this guide.

2xx — Success

Code Name When to Use
200 OK Successful GET, PUT, DELETE, or PATCH
201 Created Resource successfully created. Include Location header
202 Accepted Request accepted but not yet processed (async operations)
204 No Content Successful DELETE or PUT where no body is needed
// 200 — Standard success
res.status(200).json({ data: user });

// 201 — Resource created
res.status(201).json({ data: newUser }).location('/api/users/123');

// 202 — Async task started
res.status(202).json({ 
  jobId: 'abc123', 
  status: 'pending',
  checkUrl: '/api/jobs/abc123' 
});

// 204 — Deleted successfully
res.status(204).send(); // No body!
Enter fullscreen mode Exit fullscreen mode

3xx — Redirection

Code Name When to Use
301 Moved Permanently Resource permanently moved to new URL
302 Found Temporary redirect (old, use 303 or 307 instead)
304 Not Modified Client's cached version is still valid
// 301 — SEO-friendly permanent redirect
res.redirect(301, '/new-url');

// 304 — Let browser use cache (handled by ETag/Last-Modified)
app.get('/api/data', (req, res) => {
  const etag = computeEtag(data);
  if (req.headers['if-none-match'] === etag) {
    return res.status(304).send(); // No body needed
  }
  res.set('ETag', etag).json({ data });
});
Enter fullscreen mode Exit fullscreen mode

4xx — Client Errors

Code Name When to Use
400 Bad Request Malformed request syntax
401 Unauthorized Missing or invalid authentication
403 Forbidden Authenticated but not authorized for this resource
404 Not Found Resource doesn't exist
405 Method Not Allowed Route exists but wrong HTTP method
409 Conflict Request conflicts with current state
415 Unsupported Media Type Wrong Content-Type header
422 Unprocessable Entity Valid syntax but semantic errors (validation failed)
429 Too Many Requests Rate limit exceeded
// 400 — Bad request body
if (!req.body.email) {
  return res.status(400).json({ error: 'Missing required field: email' });
}

// 401 — Not authenticated
if (!req.headers.authorization) {
  return res.status(401).json({ error: 'Authentication required' });
}

// 403 — Authenticated but forbidden
if (req.user.role !== 'admin') {
  return res.status(403).json({ error: 'Admin access required' });
}

// 404 — Resource not found
const user = await User.findById(id);
if (!user) return res.status(404).json({ error: 'User not found' });

// 405 — Wrong method
app.all('/api/users', (req, res) => {
  res.set('Allow', 'GET, POST').status(405).json({ error: 'Method not allowed' });
});

// 409 — Duplicate resource
const existing = await User.findByEmail(email);
if (existing) return res.status(409).json({ error: 'Email already registered' });

// 422 — Validation failed
const errors = validateUser(req.body);
if (errors) {
  return res.status(422).json({ error: 'Validation failed', details: errors });
}

// 429 — Rate limited
res.set('Retry-After', '60').status(429).json({ error: 'Too many requests' });
Enter fullscreen mode Exit fullscreen mode

5xx — Server Errors

Code Name When to Use
500 Internal Server Error Unhandled server error
502 Bad Gateway Upstream server returned invalid response
503 Service Unavailable Server is overloaded or in maintenance
504 Gateway Timeout Upstream server took too long
// 500 — Catch-all (don't expose details in production!)
app.use((err, req, res, _next) => {
  const isDev = process.env.NODE_ENV !== 'production';
  res.status(500).json({
    error: {
      message: isDev ? err.message : 'Internal server error',
      ...(isDev && { stack: err.stack }),
    }
  });
});

// 503 — Maintenance mode
app.use((req, res, _next) => {
  if (isMaintenanceMode) {
    return res.status(503).json({ 
      error: 'Service temporarily unavailable',
      retryAfter: 300 
    });
  }
  next();
});
Enter fullscreen mode Exit fullscreen mode

The Decision Tree

Did the request succeed?
├── YES → 2xx
│   ├── Created something? → 201
│   ├── Processing async? → 202
│   ├── Deleted something? → 204
│   └── Otherwise → 200
│
└── NO → Why?
    ├── Client's fault → 4xx
    │   ├── No auth? → 401
    │   ├── Not allowed? → 403
    │   ├── Not found? → 404
    │   ├── Bad input? → 400 or 422
    │   ├── Duplicate? → 409
    │   ├── Too many? → 429
    │   └── Other → 400
    │
    └── Server's fault → 5xx
        ├── Upstream down? → 502
        ├── Upstream slow? → 504
        ├── Overloaded? → 503
        └── Unknown → 500
Enter fullscreen mode Exit fullscreen mode

Common Mistakes

// ❌ Using 200 for errors
res.status(200).json({ error: 'User not found' });
// Client can't distinguish success from failure by status code!

// ❌ Using 404 for everything
res.status(404).json({ error: 'Method not allowed' });
// Use 405 for wrong methods!

// ❌ Using 500 for client errors
res.status(500).json({ error: 'Missing email' });
// Use 400!

// ❌ Returning errors with 200
res.json({ success: false, error: 'Something' });
// Always set the correct status code!

// ✅ Correct approach
if (!user) return res.status(404).json({ error: { code: 'NOT_FOUND', message: 'User not found' } });
Enter fullscreen mode Exit fullscreen mode

Quick Reference

Category Codes
Success 200, 201, 202, 204
Redirect 301, 302, 304
Client Error 400, 401, 403, 404, 405, 409, 415, 422, 429
Server Error 500, 502, 503, 504

What status code do you see misused most often?

Follow @armorbreak for more API development content.

Top comments (0)