TypeScript
Both @centralping/ergo and @centralping/ergo-router ship with TypeScript
declarations generated from JSDoc. No separate @types/ packages are needed —
but your project’s tsconfig.json does require a small configuration step.
Prerequisites
Section titled “Prerequisites”- Node.js 22 or later
- TypeScript 5.x (any recent 5.x release)
- Both packages installed:
npm install @centralping/ergo @centralping/ergo-routerInstall @types/node
Section titled “Install @types/node”Both packages’ type declarations reference Node.js built-in types
(import('node:http').IncomingMessage, import('node:http').ServerResponse).
TypeScript needs @types/node to resolve these:
npm install -D @types/nodetsconfig Configuration
Section titled “tsconfig Configuration”Add 'types': ['node'] to your compilerOptions. This tells TypeScript to
load @types/node for node: protocol imports used in the package
declarations:
{ "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "strict": true, "types": ["node"] }}Without 'types': ['node'], TypeScript produces TS2591 errors like
Cannot find name 'node:http' when compiling with skipLibCheck: false.
Importing Types
Section titled “Importing Types”@centralping/ergo exports consumer-facing type interfaces from a ./types
subpath. @centralping/ergo-router exports typed route helpers (defineGet,
definePost, defineRoute, definePut, definePatch, defineDelete) and
utility types from its main entry point.
ergo type interfaces
Section titled “ergo type interfaces”import type { UrlResult, BodyResult, AcceptsResult, AuthorizationResult, CookieJar, LogEntry, PaginateResult, PreferResult, TracingResult, IdempotencyResult, ResponseAccumulator,} from '@centralping/ergo/types';These interfaces describe the middleware result shapes stored on the domain
accumulator (e.g., acc.url is UrlResult, acc.body is BodyResult).
Typed Routes with Route Helpers
Section titled “Typed Routes with Route Helpers”ergo-router provides six typed route helpers that infer the domain accumulator
type from enabled middleware config keys — providing fully typed acc in
execute callbacks without manual annotation:
| Helper | Auto-includes | Use for |
|---|---|---|
defineGet | {url: UrlResult} | GET routes |
defineDelete | {url: UrlResult} | DELETE routes |
definePost | {body: BodyResult} | POST routes |
definePut | {body: BodyResult} | PUT routes |
definePatch | {body: BodyResult} | PATCH routes |
defineRoute | — | Method-agnostic |
definePut and definePatch are type-identical to definePost; defineDelete
is type-identical to defineGet. The aliases exist for IDE discoverability — use
whichever matches your HTTP method.
import createRouter, {defineGet, definePost, definePut, definePatch, defineDelete} from '@centralping/ergo-router';
const router = createRouter({ defaults: { accepts: {types: ['application/json']}, },});
router.get( '/users/:id', defineGet( {authorization: true, url: true}, (req, res, acc) => { acc.auth; // AuthorizationResult — typed acc.url.query; // Record<string, string | string[]> — typed acc.route.params; // Record<string, string> — always present return {response: {body: {id: acc.route.params.id}}}; }, ),);
router.post( '/users', definePost( {authorization: true}, (req, res, acc) => { acc.auth; // AuthorizationResult — typed acc.body.parsed; // unknown — typed return {response: {statusCode: 201, body: acc.body.parsed}}; }, ),);
router.put( '/users/:id', definePut( {authorization: true, body: {limit: 2048}}, (req, res, acc) => { acc.auth; // AuthorizationResult — typed acc.body.parsed; // unknown — typed return {response: {body: acc.body.parsed}}; }, ),);
router.patch( '/users/:id', definePatch( {authorization: true, body: true}, (req, res, acc) => { acc.auth; // AuthorizationResult — typed acc.body.parsed; // unknown — typed return {response: {body: acc.body.parsed}}; }, ),);
router.delete( '/users/:id', defineDelete( {authorization: true, url: true}, (req, res, acc) => { acc.auth; // AuthorizationResult — typed acc.url.query; // Record<string, string | string[]> — typed return {response: {statusCode: 204}}; }, ),);See the Getting Started TypeScript tab for a complete working example.
Standalone compose with Manual Annotations
Section titled “Standalone compose with Manual Annotations”When using compose() directly (without ergo-router), TypeScript cannot fully
infer accumulator types across {fn, setPath} config objects. Add explicit
type annotations to the execute function:
import {compose, handler, logger, accepts, url} from '@centralping/ergo';import type {UrlResult, AcceptsResult, LogEntry} from '@centralping/ergo/types';
type Acc = { log: LogEntry; accepts: AcceptsResult; url: UrlResult;};
const pipeline = compose( logger(), accepts({types: ['application/json']}), url(), (req, res, acc: Acc) => ({ response: { body: {path: acc.url.pathname, query: acc.url.query}, }, }),);See the Custom Middleware recipe for TypeScript examples of the middleware contract and domain contribution patterns.
Next Steps
Section titled “Next Steps”- Getting Started — install both packages and build your first API
- Custom Middleware — write middleware that fits the accumulator contract (includes TypeScript examples)
- ergo-router — router API, config resolution, and pipeline builder reference
- ergo Type Exports — auto-generated API reference