How should a REST API return error responses consistently?
Use one JSON shape for every error with the right HTTP status, a stable machine-readable code, a human message, and optional field details. Never leak stack traces or database errors to clients.
Explain it simply
When a UPI payment fails, the app shows a clear reason and a code you can quote to support. A good API does the same. If one endpoint returns { "msg": "bad" }, another returns plain text, and a third an HTML crash page, the frontend developer needs special code for each. One format everywhere removes that mess.
Example
A shape many teams use:
{
"error": {
"code": "VALIDATION_FAILED",
"message": "Some fields are invalid",
"details": [
{ "field": "email", "message": "must be a valid email" },
{ "field": "age", "message": "must be at least 18" }
]
}
}Build it once, in a custom error class and one central handler:
class AppError extends Error {
constructor(status, code, message, details) {
super(message);
this.status = status;
this.code = code;
this.details = details;
}
}
app.use((err, req, res, next) => {
if (err instanceof AppError) {
return res.status(err.status).json({
error: { code: err.code, message: err.message, details: err.details },
});
}
console.error(err); // log the real cause on the server
res.status(500).json({
error: { code: "INTERNAL", message: "Something went wrong" },
});
});The code is for programs to branch on, the message is for humans. Log a request ID on the server and return it, so support can find the log entry.
What to say in the interview
"I define an AppError class, throw it from handlers, and have one central middleware turn it into a consistent JSON shape with the right status. Unknown errors become a generic 500 with details only in the server logs."
Common mistakes
- Sending
err.stackor raw SQL errors to the client. - Using 200 for failures.