Migrating to 0.5
This guide covers changes introduced in ergo 0.5.0 and
ergo-router 0.5.0. Both packages should be upgraded together —
ergo-router 0.5.x requires @centralping/ergo >=0.5.0 <0.6.0.
Prerequisites
Section titled “Prerequisites”npm install @centralping/ergo@^0.5.0 @centralping/ergo-router@^0.5.0Verify versions after install:
npm ls @centralping/ergo @centralping/ergo-routerPotentially Breaking: Registration-Time Config Validation (ergo-router)
Section titled “Potentially Breaking: Registration-Time Config Validation (ergo-router)”Routes that combine body: false with validate: {body: schema} now
throw at registration time instead of producing a guaranteed 500
error on every request. The check uses resolved values — contradictions
originating from defaults are also caught.
Before (0.4.x)
Section titled “Before (0.4.x)”import createRouter from '@centralping/ergo-router';
const router = createRouter();
// ⚠️ This route silently produced a 500 on every requestrouter.post('/items', { body: false, validate: {body: itemSchema}, execute: createItem,});
// Server starts without error — the bug surfaces at request timeAfter (0.5.x)
Section titled “After (0.5.x)”import createRouter from '@centralping/ergo-router';
const router = createRouter();
// ✓ Now throws immediately at registration time:// "Route config for POST /items has body: false but validate.body// is configured. Body parsing must be enabled for body validation// to work."router.post('/items', { body: false, validate: {body: itemSchema}, execute: createItem,});Migration Steps
Section titled “Migration Steps”- Start your application — if any routes have this contradiction, you will see an immediate error with the route method and path.
- Fix the contradiction — either remove
body: false(to allow body parsing) or removevalidate: {body: schema}(if body validation is not needed). - Check defaults — the validation resolves config against
defaults, so adefaults: {validate: {body: schema}}combined with a per-routebody: falsewill also throw.
Notable Additions
Section titled “Notable Additions”These are non-breaking additions in 0.5.0. No migration is required, but you may want to adopt them.
Validate Shorthand Form (ergo)
Section titled “Validate Shorthand Form (ergo)”Pass a raw JSON Schema directly to validate() instead of wrapping
it in {body: schema}:
router.post('/users', { validate: {body: userSchema}, execute: createUser,});router.post('/users', { validate: userSchema, execute: createUser,});The shorthand is detected when the argument contains JSON Schema
keywords (type, properties, required, etc.) and none of the
targeted keys (body, query, params). The targeted form is
unchanged and takes precedence when any targeted key is present.
See the validate middleware guide for the full detection algorithm and precedence rules.
Response Schema Projection (ergo)
Section titled “Response Schema Projection (ergo)”responseSchema strips undeclared properties from response bodies
before serialization — schema-based output projection:
router.get('/users/:id', { send: { responseSchema: { 200: { type: 'object', properties: { id: {type: 'string'}, name: {type: 'string'}, }, }, }, }, execute: async (req, res, acc) => { const user = await db.findById(acc.route.params.id); // Even if `user` has extra fields (email, passwordHash), // only `id` and `name` are sent to the client return {response: {body: user}}; },});Resolution order: exact status code match → range key ('2xx') →
'default'. Only applies to Object bodies with statusCode < 400.
Projectors are compiled at factory time for zero per-request overhead.
See the send middleware guide for the full resolution table and interaction with the response envelope.
onResponse Lifecycle Hook (ergo + ergo-router)
Section titled “onResponse Lifecycle Hook (ergo + ergo-router)”Post-send observation hook for audit logging, metrics, and monitoring:
const router = createRouter({ onResponse: (req, res, responseInfo, domainAcc) => { auditLog.write({ method: responseInfo.method, url: responseInfo.url, status: responseInfo.statusCode, duration: responseInfo.duration, user: domainAcc.auth?.identity, }); },});Configurable at router level and per-route. Route-level hooks fire first, then router-level. Hook errors are silently swallowed — they cannot affect the response.
See the handler guide for standalone usage and the ergo-router package page for the dual-level execution model.
catchHandler 4th Argument (ergo-router)
Section titled “catchHandler 4th Argument (ergo-router)”Custom error handlers now receive the domain accumulator as a fourth argument:
router.post('/orders', { catchHandler: (req, res, err, domainAcc) => { // domainAcc contains route params, parsed body, auth identity, // and other pipeline data available at the time of the error logger.error({ error: err.message, user: domainAcc.auth?.identity, route: domainAcc.route?.params, }); res.writeHead(500, {'Content-Type': 'application/json'}); res.end(JSON.stringify({error: 'Internal Server Error'})); }, authorization: {strategies: [bearerStrategy]}, validate: {body: orderSchema}, execute: processOrder,});Backwards compatible — existing 3-argument handlers continue to work.
New Presets (ergo-router)
Section titled “New Presets (ergo-router)”Three new presets join presets.jsonApi:
presets.sse— Server-Sent Events: disables compression and timeout, restricts totext/event-streampresets.webhooks— Webhook receiver: requiresIdempotency-Keyheader, restricts toapplication/jsonpresets.public— Public read-only API: enables transport-level rate limiting (100 req/60s), setsCache-Control: public, max-age=300
import createRouter, {presets} from '@centralping/ergo-router';
const router = createRouter({ ...presets.sse, defaults: { ...presets.sse.defaults, authorization: {strategies: [bearerStrategy]}, },});See the ergo-router package page for per-preset inventory tables and override patterns.
Informational
Section titled “Informational”These changes require no action but are worth noting.
Multi-Runtime CI (ergo)
Section titled “Multi-Runtime CI (ergo)”The ergo test suite now runs on Deno 2.x and Bun 1.x alongside Node.js 22/24. Both runtimes pass all contract tests (100%) and nearly all unit tests via their Node.js compatibility layers. These are informational CI checks — Node.js remains the primary runtime.
@types/node Optional Peer Dependency (ergo + ergo-router)
Section titled “@types/node Optional Peer Dependency (ergo + ergo-router)”Both packages now declare @types/node (>= 22) as an optional
peer dependency. TypeScript consumers compiling with
skipLibCheck: false need @types/node installed for .d.ts files
that reference import('node:http'). JavaScript-only consumers are
unaffected — npm prints an informational warning but does not fail.
Cumulative Changes from Earlier Versions
Section titled “Cumulative Changes from Earlier Versions”If you are upgrading from a version earlier than 0.4.x, the following breaking changes also apply. See the 0.3-to-0.4 migration guide for the composition format change (the primary 0.4.0 breaking change) and its earlier cumulative entries.
From 0.1.x: Validate Body Prerequisite (ergo 0.2.0)
Section titled “From 0.1.x: Validate Body Prerequisite (ergo 0.2.0)”validate({body: schema}) now returns a 500 response when
body() middleware is not in the pipeline ahead of it. A one-time
process.emitWarning diagnostic is emitted with code
ERGO_VALIDATE_NO_BODY. Previously, body validation was silently
skipped, allowing invalid request data to reach the execute handler.
Correctly ordered pipelines are unaffected — this only surfaces
when validate() is placed before body() (or when body() is
missing entirely).
From 0.1.x: Idempotency Malformed Header (ergo 0.2.0)
Section titled “From 0.1.x: Idempotency Malformed Header (ergo 0.2.0)”idempotency() now distinguishes missing from malformed
Idempotency-Key headers. A malformed header (present but not a valid
RFC 8941 sf-string) always returns 400 with format guidance,
regardless of the required option.
Previously, a malformed header with required: false silently passed
through as if the header were absent.
Verification
Section titled “Verification”After migrating, confirm everything works:
- Application starts — no registration-time errors from the
body: false+validate.bodysemantic check. - Build passes —
npm run build(or your project’s build command) exits without errors. - Tests pass —
npm testexits without failures. - No runtime warnings — check for
process.emitWarningdiagnostics during startup (ergo validates option keys at factory time). - Review changelog — see the full ergo changelog and ergo-router changelog for additional non-breaking additions you may want to adopt.