Custom Headers
Problem
Section titled “Problem”You need to add custom response headers — timing headers, correlation
IDs, cache directives, or API-specific metadata — from middleware or
execute handlers without calling res.setHeader() directly.
Solution
Section titled “Solution”From an Execute Handler
Section titled “From an Execute Handler”Return headers as [name, value] tuples in the response.headers
array. send() applies them to the response before writing the body.
import { compose } from "@centralping/ergo";
const pipeline = compose( (req, res, acc) => ({ response: { body: { ok: true }, headers: [ ["X-Custom-Header", "my-value"], ["X-Request-Start", String(Date.now())], ], }, }),);router.get("/health", { execute: (req, res, acc) => ({ response: { body: { status: "ok" }, headers: [ ["X-Custom-Header", "my-value"], ["X-Request-Start", String(Date.now())], ], }, }),});Response Time
Section titled “Response Time”For accurate response time measurement, use the
logger middleware’s built-in duration field. Logger
attaches to res.on('finish') and records total time from middleware entry
to response completion:
import { compose, logger } from "@centralping/ergo";
const pipeline = compose( [logger({ log: (info) => console.log(`${info.method} ${info.url} ${info.duration}ms`), }), "log"], // ... remaining middleware);If you need response time logging without the logger middleware, attach a
finish listener before calling the handler:
import http from "node:http";import { handler, compose } from "@centralping/ergo";
const pipeline = compose(/* ... */);const app = handler(pipeline);
http.createServer((req, res) => { const start = performance.now(); res.on("finish", () => { console.log(`${req.method} ${req.url} ${(performance.now() - start).toFixed(2)}ms`); }); app(req, res);}).listen(3000);Removing a Header
Section titled “Removing a Header”Set a header value to undefined to clear it from the response:
(req, res, acc) => ({ response: { headers: [["X-Powered-By", undefined]], },});Explanation
Section titled “Explanation”How Headers Flow Through the Pipeline
Section titled “How Headers Flow Through the Pipeline”Headers from the response accumulator are applied by send() after the
pipeline completes. send() iterates the headers array and calls
res.setHeader(name, value) for each tuple, or res.clearHeader(name)
when the value is undefined.
This design means:
- Multiple middleware can contribute headers — each returns its own
headersarray, and the pipeline merges them into the response accumulator. - Later middleware can override earlier headers — if two middleware set the same header name, the last one wins (standard HTTP semantics).
- No
resmutation in middleware — middleware never callsres.setHeader()directly. All header contributions flow through the accumulator, keeping the pipeline pure and testable.
Built-In Header Middleware
Section titled “Built-In Header Middleware”ergo includes middleware that manages common headers automatically:
| Middleware | Headers Set |
|---|---|
| security-headers | Content-Security-Policy, X-Frame-Options, etc. |
| cache-control | Cache-Control |
| cors | Access-Control-* headers |
| compress | Content-Encoding, Vary |
Use custom headers for application-specific metadata that these middleware do not cover.