Node.js › Error Handling

What is the difference between a 4xx and 5xx HTTP status code from an error-handling perspective, and how should a Node.js API decide which to return?

Advanced

Answer

4xx status codes indicate the client made a mistake (invalid input, missing authentication, requesting a nonexistent resource) -- these are typically operational errors that should include a clear, actionable message helping the client fix their request. 5xx status codes indicate a server-side failure (an unexpected exception, a database outage) -- the specific internal details generally should NOT be exposed to the client (to avoid leaking implementation details or security-sensitive information), typically returning a generic 'Internal Server Error' message while logging full details server-side for debugging.
app.use((err, req, res, next) => {
  if (err.isOperational) {
    return res.status(err.statusCode || 400).json({ error: err.message }); // safe to show details
  }
  logger.error('Unexpected error:', err); // log full details internally
  res.status(500).json({ error: 'Internal server error' }); // generic message to the client
});
Real-world example An API returns a specific 400 error with 'Email is already registered' for a validation failure (safe, helpful to show), but for an unexpected database connection error, returns only a generic 500 'Internal server error' to the client while logging the full stack trace and connection details internally for the engineering team.

Common follow-ups: What specific security risk does leaking a stack trace or internal error message to an API client actually pose?;How would you handle an error that's genuinely ambiguous between being the client's fault and the server's fault?