Skip to content

@tetsujs/core

@tetsujs/core is the framework: controllers, routes, lifecycle hooks, validation, cookies, WebSockets and the request pipeline. It has no runtime dependencies. This page lists what the package exports and where each part is explained. The model itself is in Key concepts.

Terminal window
bun add @tetsujs/core

It needs Bun 1.4 or later, TypeScript 5.7 or later with strict on, @types/bun, and moduleResolution set to bundler, node16 or nodenext. Installation explains each.

The package has two entry points: @tetsujs/core for the framework and @tetsujs/core/testing for test helpers.

Export What it is Explained in
createApp Builds the application from routes, hooks and options: the object you spread into Bun.serve. createApp
route Declares one route: method, path, schema, hooks and handler. Routes and handlers
controller Declares a controller: a named function from its dependencies to its routes. Controllers and dependencies
group Puts routes and groups under a path prefix, with hooks of their own. Groups and mounting
hook One factory per slot: hook.beforeParse, hook.beforeHandle, hook.onError and the rest. Lifecycle hooks
ws Declares a WebSocket endpoint. WebSockets
onMount A symbol. A controller method under this name receives the built application, once, before createApp returns. Controllers and dependencies
Export What it is Explained in
HttpError An error with a status and a body. The pipeline answers with both. Errors
httpError Builds an HttpError with the standard envelope and an error code of your own. Errors
errorBody Builds the envelope { status, message, error }, for a hook that returns its own Response. Errors
ValidationError The HttpError thrown when a request part fails its schema. It carries the issues. Validation
ResponseContractError Reported when a handler returns a status or body its response map does not allow. The client gets a 500. Responses
reportFailure Passes a failure to the application’s reportError, the way the framework does. Errors
Export What it is Explained in
toJsonSchema Returns the JSON Schema of a Standard Schema value, or undefined if it has no JSON Schema support. Used by documentation tools. Validation
signedCookie Reads a signed cookie from the request before it is parsed, when ctx.cookies is not filled yet. Cookies

isRoute, isGroup and isWs tell whether a value was declared by route(), group() or ws(). They are for code that walks a route tree, such as a documentation generator.

Everything else is a type: the application, the context at each stage and Requires, hooks and slots, routes, errors and cookies. The createApp reference lists them by area.

@tetsujs/core/testing imports bun:test, so use it in test files only. Testing shows each helper in use.

Helper What it does
serve(app, options?) Starts the application on a free port and returns a request function for it, with the server’s address as request.url. Stopped when the tests around the call finish, so not to be called in beforeAll; { stop: false } leaves the stop to request.stop().
request.client(options?) A client with default headers and a cookie jar, for a test that signs in and acts as that user.
request.stop() Stops the server now. A request to it afterwards throws, saying what stopped it.
testCtx(parts, options?) Builds a context for calling a handler directly, with no server.
captureErrors() Collects what the framework logs on console.error, so a test can assert on it. Call it in a describe body, not inside a test.
stopServers() Stops every server serve started that is still running. Needed only outside bun test.
const users = controller("Users", () => ({
list: route({
method: "GET"

The method this route answers.

Kept as a literal rather than widened to

Method

: the method is half of a route's identity, and an application that remembers its routes — for a generated client, for tooling — needs to know which one this is.

method
: "GET",
path: "/users"

Route path with :param segments, e.g. "/orders/:id/cancel".

Must start with /, contain no empty segments and no trailing slash; a malformed literal is a compile error.

path
: "/users",
handler: (ctx: {
readonly req: Request & {
readonly cookies?: Bun.CookieMap;
};
readonly server: Bun.Server<unknown>;
readonly out: Outgoing;
readonly route: RouteInfo;
readonly startedAt: number;
readonly params: {};
}) => never[]

The endpoint logic; ctx is fully inferred, never annotate it.

The return type is inferred rather than demanded, and checked twice over. Against the route's own contract, by the

HandlerResult

bound on R: answering with something response never declared is a compile error that says so, instead of a structural diff against Response. And against what the framework can serialize at all, by the intersected

ValidateResult

: a stream handed over bare is refused whether or not the route declared anything, because that is the case no contract covers — without a response schema HandlerResult is unknown and accepts every value there is.

handler
: () => [] }),
}));
const
const request: RequestFn
request
= serve(createApp({ routes: users() }));
test("lists users", async () => {
const
const res: Response
res
= await
const request: RequestFn
(path: string, init?: RequestInit) => Promise<Response>
request
("/users");
expect(
const res: Response
res
.
status: number

The status read-only property of the Response interface contains the HTTP status codes of the response.

MDN Reference

status
).toBe(200);
});