Contracts
@rudderjs/contracts is the type-only foundation that every other framework package depends on. It defines the shape of an HTTP request, the response builder, the middleware signature, and the server adapter interface. It contains no runtime code beyond a small input-accessor helper, has zero dependencies, and changes infrequently.
You usually don't import from @rudderjs/contracts directly — most types re-export through @rudderjs/core or whichever package defines the surface. But knowing it exists matters for two reasons:
- Library authors who want to ship middleware, adapters, or routers that interoperate with the framework.
- Cross-package type safety — the same
AppRequesttype flows through the router, middleware, controllers, and your handlers, so refactoring at one edge propagates everywhere.
What's in it
| Export | Kind | Description |
|---|---|---|
AppRequest |
Interface | Normalized HTTP request with typed input accessors |
AppResponse |
Interface | Fluent response builder — json(), status(), header(), redirect() |
RouteHandler |
Type | (req, res) => unknown | Promise<unknown> |
MiddlewareHandler |
Type | (req, res, next) => unknown | Promise<unknown> |
RouteDefinition |
Interface | A registered route — method, path, handler, middleware |
ServerAdapter |
Interface | Implemented by server-adapter packages |
FetchHandler |
Type | WinterCG-compatible (request: Request, env?: unknown, ctx?: unknown) => Promise<Response> |
HttpMethod |
Union | 'GET' | 'POST' | ... | 'ALL' |
InputTypeError |
Class | Thrown by typed input accessors when coercion fails |
attachInputAccessors |
Function | Used by server adapters to attach typed input methods |
For the request and response APIs, see Requests and Responses.
When to consume contracts directly
Most app code reaches for the typed re-exports from the package you're already using:
// Most apps
import type { AppRequest, AppResponse } from '@rudderjs/contracts'That import is fine and safe — it doesn't pull any runtime code. Use it whenever you write a handler or middleware function outside of a route declaration:
import type { MiddlewareHandler, AppRequest, AppResponse } from '@rudderjs/contracts'
export const requireAdmin: MiddlewareHandler = async (req, res, next) => {
if (req.user?.role !== 'admin') return res.status(403).json({ message: 'Forbidden' })
await next()
}Implementing a server adapter
The ServerAdapter interface is what ports the framework to a new HTTP runtime — Bun's native server, Cloudflare Workers, Deno Deploy. Implementing it requires translating between the runtime's native request/response and the framework's normalized AppRequest / AppResponse.
import type { ServerAdapter, AppRequest, AppResponse, RouteDefinition, MiddlewareHandler } from '@rudderjs/contracts'
export class MyAdapter implements ServerAdapter {
registerRoute(route: RouteDefinition): void { /* ... */ }
applyMiddleware(middleware: MiddlewareHandler): void { /* ... */ }
listen(port: number, callback?: () => void): void { /* ... */ }
getNativeServer(): unknown { /* ... */ }
}The reference implementation is @rudderjs/server-hono. Read its source — it's the canonical way to plug a new runtime in. It is also the default: when Application.configure() receives no server: option, the framework auto-resolves @rudderjs/server-hono and constructs it with config('server'); a custom adapter is plugged in by passing server: explicitly.
Why a separate types package
Splitting types out of @rudderjs/core keeps the package graph clean:
- Zero runtime overhead.
@rudderjs/contractsis type-only; it adds nothing to your bundle. - No circular dependencies.
core,router,middleware, andserver-honoall depend oncontracts— butcontractsdepends on nothing. - Independent evolution. Type definitions stabilize before runtime APIs. Cosmetic refactors of
@rudderjs/coredon't ripple into every adapter.
For library authors, this means you can write a routing or middleware library that targets the framework's contracts without taking a dependency on the kernel package. Your library stays small; consumers slot it into any @rudderjs/core version that's compatible with the contracts version.
Versioning
@rudderjs/contracts follows semver strictly. Adding a field to AppRequest is a major bump; widening a method's return type is a minor bump. Treat any change to this package as load-bearing across the whole ecosystem.
Pitfalls
- Importing runtime code from
contracts. There isn't any (other thanattachInputAccessors). If you find yourself reaching for behavior, you probably want@rudderjs/coreor a domain package. - Casting
req.raw. It's the adapter's native request — useful for adapter-specific features (cookies via Hono context, file streams), but the cast bypasses type safety. Wrap it in a small helper module so the cast lives in one place. - Implementing
ServerAdapterwithout reading server-hono. The interface looks small but the integration with middleware groups, view rendering, and WebSocket upgrade has subtleties. Mirror the reference implementation.