Error Responses

One Shape, Useful Codes, Stable Messages

Error Responses

Pick one error response shape, give every error a stable code, never leak stack traces.

3 min read Level 1/5 #express#errors#api
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 versions
  • message — human-readable, may change
  • issues — 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.

Documenting With OpenAPI →