Skip to content

Getting Started

  • Node.js 22 or later (pure ESM)
  • npm (ships with Node.js)
Terminal window
npm install @centralping/ergo @centralping/ergo-router
Use casePackagesWhy
Full REST API with routing, transport security, and declarative pipeline assembly@centralping/ergo + @centralping/ergo-routerergo-router provides createRouter(), automatic handler()/send() wrapping, and transport-level concerns (request IDs, CORS, rate limiting, security headers)
Custom routing or framework integration — you bring your own router (Express, Fastify, Koa, etc.)@centralping/ergo onlyUse compose() and handler() directly to build pipelines without ergo-router’s opinions

Create a server with a single route that demonstrates all four Fast Fail stages:

import createRouter, {graceful} from '@centralping/ergo-router';
const router = createRouter({
defaults: {
accepts: {types: ['application/json']},
},
});
router.get('/users/:id', {
authorization: {
strategies: [
{
type: 'Bearer',
authorizer: async (attributes, token) => {
if (!token) return {authorized: false};
return {authorized: true, info: {userId: token}};
},
},
],
},
validate: {
params: {
type: 'object',
properties: {id: {type: 'string', minLength: 1}},
required: ['id'],
},
},
execute: (req, res, acc) => ({
response: {
body: {
id: acc.route.params.id,
name: 'Jane Doe',
requestedBy: acc.auth.userId,
},
},
}),
});
router.post('/users', {
authorization: {
strategies: [
{
type: 'Bearer',
authorizer: async (attributes, token) => {
if (!token) return {authorized: false};
return {authorized: true, info: {userId: token}};
},
},
],
},
validate: {
body: {
type: 'object',
properties: {name: {type: 'string', minLength: 1}},
required: ['name'],
},
},
execute: (req, res, acc) => ({
response: {
statusCode: 201,
body: {name: acc.body.parsed.name, createdBy: acc.auth.userId},
},
}),
});
await graceful(router.handle(), {port: 3000});
RequestResult
GET /users/42 with Accept: text/xml406 Not Acceptable — negotiation fails
GET /users/42 without Authorization401 Unauthorized — authorization fails
GET /users/42 with valid Bearer token200 OK — all stages pass
POST /users without Content-Type415 Unsupported Media Type — missing content type
POST /users with invalid JSON body400 Bad Request — body parsing fails (malformed JSON)
POST /users with valid JSON + Bearer token201 Created — resource created

Every error response is a RFC 9457 Problem Details JSON object.

If you chose the ergo-only path, you compose middleware manually using compose() and wrap the pipeline with handler():

import http from 'node:http';
import {compose, handler, logger, url, accepts} from '@centralping/ergo';
const pipeline = compose(
logger(),
accepts({types: ['application/json']}),
url(),
(req, res, acc) => ({
response: {
body: {path: acc.url.pathname, query: acc.url.query},
statusCode: 200,
},
}),
);
const server = http.createServer(handler(pipeline));
server.listen(3000, () => console.log('Listening on :3000'));

Built-in middleware carries its own accumulator path — compose it as a bare function call. Custom middleware uses {fn, setPath} config objects to declare the key under which its result is stored on the domain accumulator. The final function in the pipeline is the execute step: it reads accumulated values and returns a {response} object.

handler() creates both accumulators, runs the pipeline, and calls send() exactly once to serialize the HTTP response. See the handler guide for all available options.