Skip to content

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.

  • Node.js 22 or later
  • TypeScript 5.x (any recent 5.x release)
  • Both packages installed:
Terminal window
npm install @centralping/ergo @centralping/ergo-router

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:

Terminal window
npm install -D @types/node

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.

@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.

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).

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:

HelperAuto-includesUse 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
defineRouteMethod-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.

  • 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