Skip to content

Why ergo?

ergo is a play on “error or go” — every middleware either rejects the request with a proper error or passes it forward. There is no ambiguity, no silent swallowing, no deferred validation. The name also carries its Latin meaning: therefore — as in, “the request is valid; therefore, proceed.”

ergo’s public API names are deliberate architectural choices, not arbitrary labels. Each was evaluated against ecosystem alternatives and retained for accuracy. The full audit is documented in ergo’s internal decision record (#139).

A factory that returns the post-pipeline response writer — called once by handler() after the pipeline completes. It serializes the accumulated response (JSON, ETags, conditionals, RFC 9457 errors, pagination).

Unlike Express’s res.send() or Fastify’s reply.send(), ergo’s send is not a method called inside a route handler. It operates at a different architectural level — a factory wired by the framework, not an imperative call in user code.

Rejected: respond (semantically vague), reply (Fastify’s term with different invocation semantics), complete (too generic).

A pipeline middleware for content negotiation — validates the client’s Accept, Accept-Language, Accept-Charset, and Accept-Encoding headers per RFC 9110 §12.5.

The name is a verb form of the HTTP header name. It maps directly to the protocol concept rather than inventing a framework-specific abstraction.

Rejected: negotiate (overloaded — could mean auth or protocol negotiation), contentType (covers only one of the four Accept headers).

Left-to-right two-accumulator pipeline composition. compose is the standard term for combining functions into a pipeline. ergo’s left-to-right direction matches Node.js stream.pipeline(), RxJS pipe(), and lodash _.flow() — all established left-to-right composition patterns.

The FP convention where compose means right-to-left (Ramda, Redux) is one convention, not the only one. ergo’s pipeline model — data flows through ordered stages — is inherently left-to-right.

Rejected: pipeline (ambiguous with the architectural concept — ~300 occurrences as a concept word), chain (name collision with internal utilities), flow (lodash-specific semantics without the two-accumulator model).

Most Node.js frameworks treat middleware as a flat chain where anything can happen in any order. This leads to common failure modes:

  • Wasted work: body parsing and database queries execute before discovering the request is unauthorized.
  • Inconsistent errors: each handler invents its own error format, making client-side error handling fragile.
  • Silent failures: middleware swallows errors or returns ambiguous status codes, making debugging harder.
  • RFC drift: teams start with good intentions but gradually diverge from HTTP semantics because the framework doesn’t enforce them.

ergo organizes every request through four ordered stages:

Request → Negotiation → Authorization → Validation → Execution → Response
↓ ↓ ↓
406/415 401/403 400/422

Each stage either succeeds (passes the request forward) or fails (returns an RFC 9457 Problem Details error response). Failures are immediate — no downstream middleware executes.

Fail Fast is a well-established principle in systems design. The concept originates from Jim Shore’s influential essay “Fail Fast” (IEEE Software, 2004), which argues that bugs are easier to find when software fails immediately and visibly rather than producing incorrect results silently.

In the HTTP context, this principle maps directly to RFC 9110’s status code semantics: a 401 Unauthorized should be returned before attempting to parse a request body, and a 406 Not Acceptable should be returned before executing business logic. ergo makes this the only possible execution path.

ergo doesn’t just support RFCs — it enforces them structurally:

ConcernStandardergo Behavior
Error responsesRFC 9457All errors are Problem Details JSON
Content negotiationRFC 9110 §12.5Automatic 406 for unacceptable media types
AuthenticationRFC 6750, RFC 7617Bearer/Basic with proper WWW-Authenticate
CachingRFC 9111Cache-Control and conditional request support
CORSFetch StandardPreflight and simple request handling
CSRFOWASP GuidelinesDouble-submit cookie pattern
Rate limitingRFC 6585 §4429 Too Many Requests with Retry-After
Security headersRFC 6797HSTS, CSP, and security header defaults

ergo doesn’t treat security as an opt-in feature — it’s a consequence of the pipeline design and conservative defaults. You would have to deliberately override these protections to weaken them:

  • Security headers ship with restrictive defaults: Content-Security-Policy: default-src 'none', X-Frame-Options: DENY, X-Content-Type-Options: nosniff, X-XSS-Protection: 0 (disables flawed browser XSS filters).
  • Null-prototype objects for all user-input-derived data (query params, cookies, Prefer headers) prevent prototype pollution attacks.
  • Bounded input parsing limits query string length, key-value pair count, cookie count, request body size, and decompressed body size out of the box.
  • Decompression bomb protection enforces a separate limit on inflated body size after decompression, preventing small compressed payloads from expanding to arbitrary sizes in memory.
  • Header injection prevention via sanitizeQuotedString for all values interpolated into HTTP header quoted-strings (WWW-Authenticate, Link, Set-Cookie).
  • Timing-safe CSRF verification uses crypto.timingSafeEqual() to prevent timing attacks on token comparison.
  • Sensitive header redaction in the structured logger (authorization, cookie, proxy-authorization, set-cookie).
  • 5xx error detail redaction ensures internal error messages are never leaked to API consumers.

These protections satisfy the OWASP REST Security Cheat Sheet recommendations and address multiple risks in the OWASP API Security Top 10. See the Security concepts page for a detailed mapping.