Skip to content

FAQ

They add a hidden layer — metadata, registration, resolution order — that the compiler cannot check and a reader cannot follow. Here a controller gets its dependencies as function arguments, and the wiring is ordinary code in one file. Decorators also cannot carry the inferred type of ctx into a method, so every handler would need a hand-written annotation.

It can: the framework reads routes from any object and names it after its class. But class fields are initialized before the constructor assigns its parameters, so a hook built from a constructor argument in a field is built from undefined (TypeScript reports TS2729). controller() avoids the problem. Services stay classes — see Controllers and dependencies.

There are none. A container needs them because it creates your objects in an order of its own; here you create them in main.ts, so the order is the order of the lines. Whatever must happen before the first request, such as connecting to the database or running migrations, goes before Bun.serve(). For stopping, @tetsujs/lifecycle handles SIGTERM: it drains the server and then runs the close functions you give it, in order. See Health checks and shutdown.

Tetsu uses Bun directly: its native router, CookieMap, Bun.serve and Bun.Server. Other runtimes would need a second router and a wrapper around everything else, and a second router is a second answer to which route a request matches.

No. Packages such as CORS or rate limiting are hooks, mounted in their slot like your own, so everything that runs for a route is visible where it is mounted. See Writing a hook package.

Why is ctx.user from a group hook missing from the handler’s type?

Section titled “Why is ctx.user from a group hook missing from the handler’s type?”

A controller is typed where it is written, not where it is mounted, so a group’s hooks do not reach the handler’s type. The hook still runs. To use its field in a handler, mount the hook on the route. See Context and its types.

A path segment is text. ctx.params.id is a string until a params schema converts it, for example with z.coerce.number(). The same holds for query and form fields.

Can I call the application without starting a server?

Section titled “Can I call the application without starting a server?”

Not through routing: routing is Bun’s, and only a socket reaches it. Call a handler directly with testCtx(), or start a real server on a free port with serve() from @tetsujs/core/testing. See Testing.

Is there a typed client, like Eden or Hono RPC?

Section titled “Is there a typed client, like Eden or Hono RPC?”

The typed boundary is the OpenAPI document. @tetsujs/openapi builds it from your routes, and any OpenAPI generator turns it into a client, in TypeScript or another language. See Typed client from OpenAPI.

No. Middleware wraps next(); a hook has a slot and no next(). Most middleware maps onto one hook in one slot, and the official packages cover the common cases: CORS, request ids, logs, rate limits, security headers.

Does the framework read environment variables?

Section titled “Does the framework read environment variables?”

No, neither the core nor any package. Read your configuration where the server starts and pass it in; in a test, configuration is then a plain argument.

Tetsu is at 0.x: usable, but the API may change between minor versions until 1.0. Every breaking change is listed in the release notes with how to move. See Stability and versioning.

How is it different from Nest, Hono or Elysia?

Section titled “How is it different from Nest, Hono or Elysia?”

Controllers and explicit wiring like Nest, without decorators or a container; fixed slots instead of Hono’s middleware; controllers instead of Elysia’s method chain. See Comparison.