Oven documentation
Oven is a backend framework for Bun. It replaces Express, takes its cues from FastAPI, and is TypeScript-native end to end.
The promise: you should never wire infrastructure by hand again. Auth, a database, file storage, email and background jobs are configuration, not a week of plumbing.
bun create theoven my-app --db sqlite --auth basicThat gives you a working app with a database, signup, login and password reset before you have provisioned anything at all.
Where to start
Section titled “Where to start”Pick the one that matches why you are here.
| New to Oven | Installation → Tutorial — five chapters, from one route to a database and auth |
| Learning by building | Build a todo API — one project, end to end: routes, database, auth, tests, deploy |
| Adding one feature | Recipes — sign in with Google, image uploads, share links, real-time |
| Building something real | Projects — a chat backend, a Trello clone, a video platform |
| Coming from Express | The translation guide — what to delete, what to rewrite, in what order |
| Deciding whether to use it | Batteries included · Philosophy · Benchmarks |
| Adding a capability | The brick catalogue — database, auth, storage, mail, queues |
| Looking something up | Context · App · CLI |
The shape of an app
Section titled “The shape of an app”export const app = createApp() .use(db(drizzleSqlite({ url: './data.db', schema }))) .use(storage(s3Storage({ bucket: 'uploads' }))) .use(image({ format: 'webp' })) .use(queue(redisQueue(), { jobs: [buildVariants] })) .use(auth(basicAuth({ db: client, secret })))export default route( { auth: true, body: z.object({ file: z.file().max(5_000_000) }), response: { 200: z.object({ id: z.uuid(), avatar: z.string() }) }, }, async (ctx) => { const avatar = await ctx.image.transform(ctx.body.file, { width: 256, height: 256 }) const { key } = await ctx.storage.upload(`avatars/${ctx.params.id}.webp`, avatar.bytes) await ctx.queue.dispatch(buildVariants, { key }) return ctx.db.update(users).set({ avatar: key }).returning() },)That route is validated, authenticated, typed and documented at /docs — and nothing in it was
wired by hand. Leave a brick out of app.ts and touching it is a compile error rather than a
crash at 3am.
The response schema is not only documentation: it filters the body, so the row’s
passwordHash cannot be sent even though the handler returned the whole record.
Grouping and per-request values
Section titled “Grouping and per-request values”const admin = routerFor<typeof app>({ prefix: '/admin', tags: ['admin'], auth: 'staff' })
admin.get('/users', (ctx) => ctx.db.select().from(users))admin.delete('/users/:id', { params: idParam }, (ctx) => remove(ctx.params.id))
app.use(admin) // prefix, tag and guard declared onceconst tx = dependency('tx', async function* (ctx) { const handle = await begin(ctx.db) try { yield handle await handle.commit() } catch (error) { await handle.rollback() throw error }})
export default route({ deps: { tx } }, async (ctx) => { await ctx.deps.tx.insert(orders).values(order) return { ok: true }})Routers group routes; dependencies are per-request values that compose, cache within a request, and clean up after it.
Reference
Section titled “Reference”| Context | everything on ctx, the argument every handler receives |
| App | createApp, routing, configuration, lifecycle |
| File-based routing | the filesystem is the route table |
| Routers | mountable route groups with shared prefix, tags and auth |
| Dependencies | per-request values, composed, with teardown |
| Validation | Standard Schema, uploads, response schemas |
| Errors | the classes, and the RFC 9457 shape they become |
| Middleware & hooks | the onion model, five hooks, built-ins |
| WebSockets & SSE | real-time, upgraded from a guarded route |
| Always-on batteries | body, cookies, uploads, tokens, query |
| Bricks | the contract, and how to write your own |
| Environment variables | readers that throw instead of guessing |
| CLI | create, dev, db, worker, doctor |
| The public API | what each package exports, and what is deliberately internal |
Status
Section titled “Status”0.6.1, on npm. Everything documented here is published and installable:
bun create theoven my-app --db sqlite --auth basicCore, the CLI, and twenty-two bricks — database (Drizzle, Mongoose), auth (email/password on SQL or Mongo, Clerk, better-auth), storage (S3, disk, Bunny, ImageKit), mail, queues, cache, OpenTelemetry, webhooks, rate limiting, vector search (embedded, pgvector, Qdrant), AI and images. See the brick catalogue for what each one does, and the roadmap for what is next, and the changelog for what each release changed.
Pre-1.0, so APIs can still change between minor versions — that freedom is deliberate while the contracts settle. Every brick page lists what it cannot do under Limitations, because a page that only lists capabilities is an advertisement.
Reading this with a coding agent
Section titled “Reading this with a coding agent”The whole documentation set is published in the llms.txt format, generated from these pages at build time so it cannot drift from them:
| File | What it is |
|---|---|
/llms.txt |
an index — every page, its one-line description, its URL |
/llms-full.txt |
the full text of every page, so no further fetches are needed |
oven create also writes an AGENTS.md into every project, describing the conventions that
matter and the Express habits that do not apply here.