Getting started
baguette is a blazingly fast, zero-config API framework for Bun and Hono. You drop a file into a directory and get back a fully typed, validated, and documented HTTP endpoint. One zod declaration is the single source of truth for runtime validation, TypeScript types, the OpenAPI spec, and the error funnel.
This page takes you from an empty directory to a running, documented, type-safe route in about 60 seconds.
Install
bun add @prehoy/baguette
baguette re-exports z and createRoute so your app shares the exact zod version the framework validates with. Never install zod separately.
Your first typed route in 60 seconds
1. Create a route
The file's location is the URL. Create api/hello.ts:
// api/hello.ts -> GET /api/hello
import { defineRoute, z } from "@prehoy/baguette";
export default defineRoute({
method: "get",
request: { query: z.object({ name: z.string().default("world") }) },
response: z.object({ greeting: z.string() }),
handler: (c, { query }) => c.json({ greeting: `Hello, ${query.name}!` }),
});
query arrives already validated and typed as { name: string }. There is no manual parsing, and no way to read an unvalidated value by accident.
2. Start the server
Point serve() at your routes directory. Create server.ts:
// server.ts
import { serve } from "@prehoy/baguette";
serve({ routesDir: "./api" });
bun run server.ts
Routes are imported in parallel at boot and mounted by convention. Nothing to register.
3. Call it
curl 'http://localhost:3000/api/hello?name=Ada'
# { "greeting": "Hello, Ada!" }
curl 'http://localhost:3000/api/hello?name='
# 400 — validation failed, before your handler ever ran
4. Read the docs it wrote for itself
Open http://localhost:3000/api/docs for a live Scalar UI, or /api/doc for the raw OpenAPI spec. Both are generated from your zod schemas — there is nothing to hand-maintain and nothing to keep in sync.
What you get from that one declaration
- Runtime validation — invalid
params,query, orbodyreturn400before your handler runs. - Inferred types — the handler's second argument is typed from your schemas. No casts.
- OpenAPI + Scalar docs — served automatically at
/api/docsand/api/doc. - Error funnel — an unhandled throw becomes a clean
500in one predictable place.
Where things live
baguette apps follow one directory layout:
api/— HTTP routes. OnedefineRouteper file.cron/— optional scheduled jobs.automations/— optional event-driven jobs.methods/(orlib/) — shared helpers and business logic.
Each layer is independently optional. If a directory doesn't exist, its code is never imported.
Next steps
- defineRoute — the full route contract: params, query, body, multi-status responses, tags, plus declarative
authandrateLimit(brute-force + email-bomb protection, built in). - serve — configuring the server: security headers, the CORS credentials recipe, body limits, SPA serving,
onBoot/onShutdown, typeddefineEnv, and auto-wiring the optional layers. - CLI — scaffold routes, cron, and automations with
baguette new. - Clean-code contract — the conventions that keep an AI (or a human) from making a mess.