Skip to content

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.

Terminal window
bun create theoven my-app --db sqlite --auth basic

That gives you a working app with a database, signup, login and password reset before you have provisioned anything at all.

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
src/app.ts
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 })))
src/routes/users/[id]/avatar.post.ts
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.

src/routes/admin.ts
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 once
a transaction that rolls itself back
const 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.

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

0.6.1, on npm. Everything documented here is published and installable:

Terminal window
bun create theoven my-app --db sqlite --auth basic

Core, 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.

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.