Getting Started
Zebra is a Bun-first TypeScript web framework. This guide covers installation, configuration, and your first app.
Install
bun add @zebra-web/zebra reflect-metadata@zebra-web/zebra is the public facade — it re-exports @zebra-web/core, @zebra-web/session, @zebra-web/cors, and (aliased) @zebra-web/rate-limit. You can also install individual sub-packages directly:
bun add @zebra-web/contract @zebra-web/client @zebra-web/testing
bun add @zebra-web/session @zebra-web/cors @zebra-web/rate-limit
bun add @zebra-web/observability @zebra-web/redisRuntime requirements
- Bun ≥ 1.4.0 (runtime). The repo is pinned to
packageManager bun@1.4.0; tests and CI run on the same Bun. - Typecheck via
tsgo— the native TypeScript compiler (@typescript/native-preview). reflect-metadataimported once at the entry point, and decorators enabled intsconfig.json:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}Import reflect-metadata once, before anything else:
import "reflect-metadata";
import { Zebra } from "@zebra-web/zebra";First app
import "reflect-metadata";
import { Zebra } from "@zebra-web/zebra";
const z = new Zebra();
z.get("/hello/:name", async (req) => new Response(`hello, ${req.params.name}`));
await z.listen({ port: 3000 });bun run src/main.ts
curl http://localhost:3000/hello/world
# hello, worldz.listen() performs, in order:
- runs all
boothooks; - validates the whole dependency graph (every DI binding plus the deps declared by routes and middleware) — unbound tokens, circular dependencies, and scope violations fail fast with an error;
- precompiles a per-route execution plan (middleware chain, dep indices, scope requirement) so dispatch does zero per-request inspection.
Once validation passes, the app is ready (the ready hooks run after) and starts accepting connections.
App with dependencies
Declare dependencies with the @injectable() decorator, register them on the Zebra instance, and pull them into routes by name:
import "reflect-metadata";
import { Zebra, injectable } from "@zebra-web/zebra";
@injectable()
class Greeter {
greet(n: string) {
return `hi, ${n}`;
}
}
const z = new Zebra();
z.injectSingleton(Greeter);
z.get("/hi/:name", { g: Greeter }, async (req, { g }) => g.greet(req.params.name));
await z.listen({ port: 3000 });{ g: Greeter } is named-object route DI: the second argument declares the route's dependencies, and the third (the handler) receives a second argument with exactly those dependencies resolved. Both req.params and the deps are fully type-inferred.
Value encoding rules
A handler's return value is encoded by Zebra.toResponse:
| Return value | Result |
|---|---|
Response | passed through unchanged (never wrapped or modified) |
undefined | empty 204 response |
anything else (objects, strings, numbers, null) | JSON.stringify encoded, content-type: application/json; charset=utf-8, status 200 |
Note: plain strings are also JSON-encoded (a
"hi"comes back quoted). Use thetext()response helper or construct aResponsewhen you need the raw string. Use the response helpers when you want explicit control.
Bring your own Container
For tests that mock specific bindings, or apps that share a container, construct one explicitly:
import { Container, Zebra } from "@zebra-web/zebra";
const container = new Container();
container.bind(IRepo).to(MockRepo);
const z = new Zebra({ container });z.inject* methods write to whichever container the Zebra instance owns.
Constructor options
new Zebra(opts) supports:
| Option | Description |
|---|---|
container | custom Container (a fresh one is created by default) |
body | request body size limit overrides (see HTTP) |
errors.exposeStack | include stack in Problem+Json responses (default false) |
session / sessionResolver / sessionTtl | session-scoped DI resolver and TTL (see Session scope) |
gracePeriod | graceful shutdown wait (ms, default 10_000) |
requestTimeout | per-request deadline (ms); a timeout answers 504 request_timeout (see HTTP) |
trustProxy | app-level statement that x-forwarded-for may be trusted (default false) |
Next steps
The repo ships a minimal example at examples/hello:
bun --filter example-hello start