Find why invalid input becomes a 500 response

A client submits a task with an invalid organization identifier and receives a 500 response. The server log shows a database conversion exception. Another client sends broken JSON and gets the same response. Monitoring reports a service failure even though these requests should have been rejected at the boundary. The endpoint's broad catch block erased the difference between caller mistakes and application failures.
This example assumes a Node JSON API with MongoDB-backed data. It focuses on failure classification rather than a specific validation library. TypeScript declarations describe expected values but do not make an incoming HTTP body conform to them. Treat network input as unknown until parsing and validation succeed.
Reproduce each failure independently
Save a valid synthetic request first. Then create separate cases for malformed JSON, wrong field type, missing required title, invalid identifier syntax, unknown organization, denied access, duplicate value, and unavailable database. Keep the endpoint, headers, and authentication state constant when testing each difference.
Record status, safe response body, and server-side error category. An error message alone cannot tell whether parsing, authorization, or storage failed. A request identifier helps correlate the response with logs without including the submitted private data in every log entry.
Do not immediately change all 500 responses to 400. A database outage and a programming error still need an operational failure response. The diagnosis is that known client errors lack explicit handling, not that an endpoint should never return a server error.
Draw the failure stages
The endpoint first reads and parses the body. Next it validates the schema and converts accepted identifiers. It then verifies identity and authorization, evaluates business rules, persists data, and maps the public response. Each stage has a small set of expected outcomes.
A malformed JSON body generally belongs to a bad-request response. A syntactically valid body that violates the chosen schema may use 400 or 422 according to the API's documented convention. Unauthorized identity and denied permission have their own semantics. A missing resource can use 404, with privacy considerations about what existence information is revealed.
A uniqueness conflict should not be reported as an arbitrary programming failure when the application intentionally enforces that business constraint. Conversely, an unknown exception must not be relabeled as validation just because a catch block sits near the input code. Keep the translation narrow and explicit.
Validate before calling the driver
Check body size and content expectations before expensive work where the server framework allows it. Parse JSON with a deliberate error mapping. Validate field types, limits, and optional-value semantics. Convert database identifiers only after checking the accepted representation for the installed driver or ORM.
Build an allowed input object instead of forwarding the decoded body. The request may include an owner, role, or internal status field that the API never intended callers to set. Reject or ignore unknown fields consistently, and test the policy.
For dates, do not accept any string that JavaScript happens to parse. Define an accepted timestamp format and validate the actual calendar value. For numbers, distinguish a number from a numeric-looking string and decide whether zero is valid. These choices belong in the contract described in small API contracts.
Translate only known errors
Use a bounded error mapper for expected categories. Keep the external response safe and stable while recording an internal category and request identifier. A field-level error can name the invalid field without echoing a secret or full payload.
try {
const input = await parseAndValidateCreateTask(request)
const actor = await requireActor(request)
const task = await createAuthorizedTask(actor, input)
return jsonResponse(publicTask(task), 201)
} catch (error) {
if (error instanceof InvalidRequest)
return jsonResponse({ code: 'INVALID_REQUEST', fields: error.fields }, 400)
if (error instanceof TaskConflict)
return jsonResponse({ code: 'TASK_CONFLICT' }, 409)
throw error
}
All helpers and error classes in this outline are application code. Authentication and authorization mappings are omitted for brevity and must be implemented intentionally. The framework's outer error boundary can produce the safe generic server response for unclassified failures. Do not expose raw database errors through a convenient error.message field.
Avoid classifying errors solely by matching arbitrary text. Prefer the library's documented error codes or a domain error created by a controlled adapter. Review driver-version details before relying on a code shape suggested by AI.
Preserve useful operational signals
Logs should say which stage failed and whether the failure was expected. Repeated invalid requests may be an abuse signal, while a spike in database timeouts is an availability signal. Mixing both under generic 500 makes diagnosis harder and can trigger the wrong response.
Do not swallow a storage exception and return an empty success object. That can make a form display success when no record exists. A fallback is acceptable only if the endpoint contract defines it and the response makes its state clear.
For third-party operations, separate local persistence from remote completion. A request may have created the task but failed to send a notification. That partial result is not necessarily a validation error or a failed create. Third-party failure states examines that distinction.
Verify the matrix and the repair
Run each saved request against the actual handler in an isolated environment. Assert status and stable error code, not merely that something throws. Verify invalid requests do not write records or call an expensive provider. Check that the valid request still succeeds and returns only allowed fields.
Add a deliberate programming exception to a fake domain adapter and verify that it remains an operational failure. This guards against an overly broad mapper that calls every exception bad input. Add a driver conflict case through an isolated database when uniqueness handling is part of the change.
Ask an AI reviewer to trace all catch blocks and identify any path that converts an unknown error into success, absence, or validation. Require file evidence. Give it sanitized request fixtures rather than production payloads. A useful review can propose missing cases, but the response matrix supplies the reproducible proof.
Keep status policy consistent
Teams can reasonably choose different conventions for schema violations, provided clients know them and implementations agree. The goal is not to win an abstract argument over 400 versus 422. The goal is a predictable distinction between caller correction, access denial, resource conflict, and service recovery.
Document that policy beside the endpoint contract and centralize only the parts genuinely shared. A universal error framework with dozens of speculative subclasses may be harder to review than three explicit categories for one operation. Start with actual failure paths and expand when new operations need consistent behavior.
Authorization tracing and concurrent uniqueness extend this classification to two failures that often masquerade as generic server errors.
Sources
About the Author

Kamruz
Full Stack Developer
A passionate developer who loves creating modern web applications and sharing knowledge with the community.
More About Me