One Shape, Useful Codes, Stable Messages
Error Responses
Pick one error response shape, give every error a stable code, never leak stack traces.
What you'll learn
- Define an error response shape
- Use stable error codes
- Map domain errors to HTTP responses
Errors are part of your API contract. Get the shape right once and clients can handle them programmatically.
Pick A Shape
{
"error": {
"code": "user_not_found",
"message": "No user with that id.",
"issues": [
{ "path": "email", "message": "Invalid email" }
]
}
} code— machine-readable, stable across versionsmessage— human-readable, may changeissues— for validation errors (array of field-level problems)
Whatever you pick, stay consistent. The worst APIs return
{ error: "..." } here, { message: "..." } there, and a raw
string elsewhere.
Centralize It
Map errors to responses in one place — the error middleware.
export function errorHandler(err, req, res, next) {
// Known domain errors
if (err instanceof HttpError) {
return res.status(err.status).json({
error: { code: err.code, message: err.message, issues: err.issues },
});
}
// Validation (already handled in the validate mw, but just in case)
if (err?.name === "ZodError") {
return res.status(400).json({
error: {
code: "validation_failed",
message: "validation failed",
issues: err.issues,
},
});
}
// Unknown — log loudly, respond generically
console.error("UNEXPECTED ERROR", err);
res.status(500).json({
error: { code: "internal_error", message: "internal server error" },
});
} Stable Codes
Codes are part of your contract. Don’t change user_not_found to
USER_NOT_FOUND after clients depend on the old name.
Pick a style and stick with it (snake_case is common).
Never Leak Stacks
In production, the response should never include err.stack. Log
it server-side.
console.error(err); // ✓ logged
res.json({ error: { stack: err.stack } }); // ✗ never Stack traces reveal file paths, library versions, internal API shapes — gifts to attackers.
RFC 7807 — A Standard
There’s a real standard for error responses: Problem Details for HTTP APIs (RFC 7807):
{
"type": "https://example.com/errors/not-found",
"title": "User not found",
"status": 404,
"detail": "No user with id 42.",
"instance": "/api/users/42"
} It’s the canonical shape for new APIs in 2026. Adopting it makes your API immediately familiar to anyone who’s seen it elsewhere.
The shape isn’t magic — code + message + issues works just as
well. Pick one and stay consistent.