Debugging & Diagnostics
ergo and ergo-router surface errors and warnings at three points in the request lifecycle: registration time (when routes are registered), factory time (when middleware is instantiated), and runtime (during request handling). This page documents each diagnostic mechanism and provides a reference for all warning codes.
Startup-Time Errors
Section titled “Startup-Time Errors”Registration-Time Validation (ergo-router)
Section titled “Registration-Time Validation (ergo-router)”When you register a route with router.get(), router.post(), etc.,
ergo-router validates the config object immediately — before any
requests arrive.
Unknown config keys are caught with Levenshtein-distance suggestions:
router.post('/users', { authorisation: {strategies: [bearerStrategy]}, execute: createUser,});Error: Unknown config key "authorisation" in route config for POST /users (did you mean "authorization"?)ergo-router validates in strict mode by default — unknown keys throw
an Error that prevents server startup. This catches typos before a
single request is processed.
Type validation ensures every config key has the correct value type
(object, boolean, function, or Array depending on the key):
router.post('/users', { authorization: 'admin', execute: createUser,});Error: Invalid "authorization" in route config for POST /users: expected object or boolean, got string.Missing execute function — every declarative route config must
include an execute function:
router.post('/users', { authorization: {strategies: [bearerStrategy]}, validate: {body: userSchema},});Error: Missing "execute" function in route config for POST /users. Declarative route configs must include an execute function.Semantic contradictions are detected across resolved config. For
example, body: false combined with validate.body is caught at
registration time:
router.post('/users', { body: false, validate: {body: userSchema}, execute: createUser,});Error: Route config for POST /users has body: false but validate.body is configured.Body parsing must be enabled for body validation to work.Factory-Time Validation (ergo)
Section titled “Factory-Time Validation (ergo)”Each ergo middleware factory validates its options object when called.
Unknown options produce a process.emitWarning() with a Levenshtein
suggestion (distance ≤ 3):
(node:12345) [ERGO_AUTHORIZATION_UNKNOWN_OPTION] ErgoWarning:authorization() received unknown option: "stratagies" (did you mean "strategies"?).Valid options are: authorizer, challenge, realm, strategies.Factory-time warnings use process.emitWarning() rather than throw
so that a typo in an optional field does not prevent startup. The
middleware still functions — only the unrecognized option is ignored.
Runtime Warnings
Section titled “Runtime Warnings”ergo uses Node.js process.emitWarning() with
{type: 'ErgoWarning', code} for runtime diagnostic messages. These
warnings fire during request handling when the pipeline encounters a
configuration issue that does not warrant a thrown error.
Listening for Warnings
Section titled “Listening for Warnings”process.on('warning', (warning) => { if (warning.type === 'ErgoWarning') { console.error(`[${warning.code}] ${warning.message}`); }});Stack Traces
Section titled “Stack Traces”To see where a warning originates, start Node.js with the
--trace-warnings flag:
node --trace-warnings server.jsThis prints a full stack trace for each process.emitWarning() call,
making it straightforward to trace the warning back to the specific
middleware factory or pipeline step.
Warning Code Reference
Section titled “Warning Code Reference”Fixed Codes (ergo)
Section titled “Fixed Codes (ergo)”| Code | Source | Condition | Action |
|---|---|---|---|
ERGO_VALIDATE_NO_BODY | validate() | validate.body schema is configured but acc.body is missing — body() middleware did not run before validate() | Ensure body is in the pipeline before validate, or let ergo-router auto-include it |
ERGO_VALIDATE_UNKNOWN_KEY | validate() | Schema object contains keys other than body, query, params | Check for typos; valid keys are body, query, params |
Dynamic Codes (ergo middleware factories)
Section titled “Dynamic Codes (ergo middleware factories)”Each middleware factory generates a warning code using the pattern
ERGO_{NAME}_UNKNOWN_OPTION where {NAME} is the uppercase factory
name with camelCase boundaries expanded:
| Code Pattern | Factory |
|---|---|
ERGO_ACCEPTS_UNKNOWN_OPTION | accepts() |
ERGO_AUTHORIZATION_UNKNOWN_OPTION | authorization() |
ERGO_BODY_UNKNOWN_OPTION | body() |
ERGO_CACHE_CONTROL_UNKNOWN_OPTION | cacheControl() |
ERGO_COMPRESS_UNKNOWN_OPTION | compress() |
ERGO_COOKIE_UNKNOWN_OPTION | cookie() |
ERGO_CORS_UNKNOWN_OPTION | cors() |
ERGO_CSRF_UNKNOWN_OPTION | csrf() |
ERGO_HANDLER_UNKNOWN_OPTION | handler() |
ERGO_IDEMPOTENCY_UNKNOWN_OPTION | idempotency() |
ERGO_LOGGER_UNKNOWN_OPTION | logger() |
ERGO_PAGINATE_UNKNOWN_OPTION | paginate() |
ERGO_PRECONDITION_UNKNOWN_OPTION | precondition() |
ERGO_RATE_LIMIT_UNKNOWN_OPTION | rateLimit() |
ERGO_SECURITY_HEADERS_UNKNOWN_OPTION | securityHeaders() |
ERGO_SEND_UNKNOWN_OPTION | send() |
ERGO_TIMEOUT_UNKNOWN_OPTION | timeout() |
ERGO_TRACING_UNKNOWN_OPTION | tracing() |
All dynamic codes include the unrecognized option name(s) and a “did you mean?” suggestion when a valid option is within Levenshtein distance ≤ 3.
ergo-router Validation
Section titled “ergo-router Validation”ergo-router does not use process.emitWarning(). Its config
validation uses:
- Strict mode (default):
throw new Error()for unknown config keys at registration time - Lenient mode (
strict: false):console.warn()for unknown config keys — prefixed with[ergo-router]
In lenient mode, unknown keys produce a warning instead of throwing:
const router = createRouter({strict: false});
router.get('/users', { rateLimmit: {max: 100}, execute: listUsers,});[ergo-router] Unknown config key "rateLimmit" in route config for GET /users (did you mean "rateLimit"?)The server starts normally and the unrecognized key is ignored. All
other validations (type checks, missing execute, semantic
contradictions) still throw regardless of the strict setting.
Pipeline Debug Mode
Section titled “Pipeline Debug Mode”For request-level debugging (identifying which middleware rejected a
request), enable pipeline debug mode with debug: true. This
populates _trace in error response bodies, showing the execution
path and break point.
See the Debug Tracing recipe for full
usage examples and the _trace shape reference.
onResponse Hook Debugging
Section titled “onResponse Hook Debugging”The onResponse lifecycle hook fires after send() completes.
Errors thrown by the hook are silently swallowed — they do not
appear in the HTTP response or cause unhandled rejections. If your
onResponse hook appears to not fire:
- Check application logs — hook errors are caught internally
- Verify that no
catchHandleris configured on the route —onResponsedoes not fire when acatchHandlerhandles the error via early return
See Lifecycle Hooks for
the execution model and
handler onResponse for the
standalone equivalent.
Runtime Error Responses
Section titled “Runtime Error Responses”Unlike registration-time errors (which prevent startup), runtime errors
are returned as HTTP responses using
RFC 9457 Problem Details format
(Content-Type: application/problem+json). Every error response includes
type, title, status, and detail.
401 Unauthorized — missing or invalid Bearer token:
{ "type": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401", "title": "Unauthorized", "status": 401, "detail": "Unauthorized"}422 Unprocessable Entity — JSON Schema validation failure with
per-field details:
{ "details": [ { "path": "/name", "message": "must NOT have fewer than 1 characters", "params": {"limit": 1} } ], "type": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422", "title": "Unprocessable Entity", "status": 422, "detail": "Validation failed"}The details extension member is an array of per-field errors produced
by the validate middleware.
Each entry includes the JSON Pointer path, the AJV error message,
and keyword-specific params.
For the complete catalog of every error status code, condition, and producing middleware, see the Error Response Reference.
Troubleshooting FAQ
Section titled “Troubleshooting FAQ””Startup error about body:false”
Section titled “”Startup error about body:false””Symptom: Server fails to start with an error like:
Error: Route config for POST /users has body: false but validate.body is configured.Cause: The route (or inherited defaults) sets body: false
while also configuring validate.body. Body parsing must run
before body validation.
Fix: Remove body: false from the route config, or remove the
validate.body schema if body validation is not needed.
”428 on PUT requests”
Section titled “”428 on PUT requests””Symptom: PUT or PATCH requests return 428 Precondition Required
even though the request body is correct.
Cause: The route has preconditionRequired: true (or inherits it
from defaults), which enforces that clients send an If-Match or
If-Unmodified-Since header. This is independent of
ETag generation — ETags enable conditional
evaluation, while preconditionRequired enforces header presence.
Fix: Send an If-Match header with the resource’s current ETag,
or set preconditionRequired: false on the route if enforcement is
not needed. See the
precondition middleware guide for the
full interaction.
”Middleware option typo”
Section titled “”Middleware option typo””Symptom: A warning appears in the console:
(node:12345) [ERGO_AUTHORIZATION_UNKNOWN_OPTION] ErgoWarning:authorization() received unknown option: "stratagies" (did you mean "strategies"?).Cause: A middleware option name is misspelled. The middleware still functions but ignores the unrecognized option.
Fix: Correct the option name as suggested. Run with
--trace-warnings to see the call site.
”Compression not working”
Section titled “”Compression not working””Symptom: Responses are not compressed even though compress is
configured.
Cause: The Accept-Encoding request header does not include a
supported encoding (gzip, deflate, br), or the response body is
below the minimum size threshold.
Fix: Verify the request includes Accept-Encoding: gzip (or
another supported encoding). Check the compress middleware’s
threshold option — bodies smaller than the threshold are not
compressed.
”onResponse hook not firing”
Section titled “”onResponse hook not firing””Symptom: The onResponse callback never executes.
Cause: A catchHandler is configured on the route or router.
When the pipeline throws, the catchHandler takes control via early
return — onResponse hooks do not fire in this path.
Fix: If you need post-response observation alongside error
handling, add logging inside the catchHandler itself. See
Lifecycle Hooks for the
execution model.
Further Reading
Section titled “Further Reading”- Debug Tracing recipe — request-level
pipeline tracing with
_trace - Error Response Reference — every HTTP error response produced by ergo middleware
- handler guide —
debug,onResponse, andredactErrorsoptions - Config Validation — unknown key detection, type enforcement, and semantic validation at registration time
- Route Config Key Reference — complete key listing with categories and accumulator paths