v0.6.1 is on npm

The batteries-included framework for Bun

Auth, a database, file storage, email and background jobs — configured, not wired. Every one of them typed, on the request context, documented at /docs.

108k
req/s on Bun
5.9×
faster than Express
1540
tests, all green
18
packages on npm
src/routes/users/[id]/avatar.post.ts
import { z } from 'zod'
import { route } from '../../../route'

export default route(
  {
    auth: true,
    params: z.object({ id: z.uuid() }),
    body:   z.object({ file: z.file().max(5_000_000) }),
  },
  async (ctx) => {
    // every one of these is typed, validated and guarded
    const { key } = await ctx.storage.upload(`avatars/${ctx.params.id}`, ctx.body.file)

    await ctx.queue.dispatch(resizeAvatar, { key })

    return ctx.db.update(users).set({ avatar: key }).where(eq(users.id, ctx.user.id))
  },
)

Validated, authenticated, typed and documented at /docs — with nothing wired by hand. Leave a brick out and touching it is a compile error, not a crash at 3am.

Always on

What you never install again

Express made you assemble a working server from parts. That was never a real choice — it was homework. If more than 80% of apps need it, Oven ships it in core; the rest ship in the box, configured rather than installed.

Body parsing

Content-type aware, size-limited, and lazy — a route that never reads ctx.body never parses it.

replaces body-parser

File uploads

Uploads arrive as web File objects, streamed rather than buffered, with size and MIME limits.

replaces multer

Cookies

ctx.cookies with secure defaults and signing built in. No secret to remember to set.

replaces cookie-parser

Token capture

ctx.token from the Authorization header, a cookie, or the query string — even with no auth brick.

replaces the snippet everyone rewrites

Async errors

A throw becomes an RFC 9457 response, whether it happened synchronously or three awaits deep.

replaces express-async-errors

Environment variables

env.port(), env.bool(), env.duration() — parsing that throws instead of guessing, with secrets redacted from errors.

replaces dotenv + Boolean('false') === true

Graceful shutdown

SIGTERM drains in-flight requests. Mid-drain arrivals get 503 with Retry-After, not a dead socket.

replaces a bug you find in production

CORS & security headers

Correct preflights, Vary: Origin, and the headers every app should send. Configured, not installed.

replaces cors + helmet

Request IDs & logging

Structured, request-scoped ctx.log with an id on every line — and on every error response.

replaces morgan + uuid

All of it lazy: a route that returns a string parses no body, splits no cookie header and generates no request id. That is asserted by tests, not promised. See what is always on →

Add a brick, get a feature

Sixteen bricks, one contract

Every brick contributes a capability to the request context, fully typed — and disappears from the type when you remove it. Each page lists what it cannot do as plainly as what it can.

Database

Drizzle over bun:sqlite, Postgres or MySQL, and Mongoose. ctx.db is the native client — your queries stay yours.

db, db-drizzle, db-mongoose →

Auth

Email and password, Google and GitHub, or Clerk and better-auth. Eight endpoints, argon2id, revocable sessions, rate limited by default.

Sign in with Google in 20 min →

Storage

S3, R2, MinIO, Bunny and ImageKit, with presigned direct uploads — and a local directory so uploads work before you have a bucket.

five drivers →

Queue

Typed jobs, retries with backoff, a dead letter and cron. In memory, in Redis, or in the Postgres you already run.

queue →

Mail

Console driver by default, so password reset works the moment you create an app. Resend, SES or SMTP when you are ready.

mail →

Real-time

WebSockets upgraded from an ordinary guarded route, so auth: true is checked before a socket exists. Server-sent events for the one-way case.

WebSockets & SSE →

Cache & telemetry

Tag invalidation and stampede protection, in memory or Redis. OpenTelemetry spans named by route pattern, not by path.

cache, telemetry →

OpenAPI & docs UI

The same schemas that validate generate an OpenAPI 3.1 document and a browsable reference — and filter the response, so a column you forgot to omit cannot be sent.

openapi →

Bricks are not middleware. CORS, rate limiting and compression wrap a request; a brick puts something new on the context. Write your own in about fifty lines. Browse the catalogue →

Three ways in

Docs you can build from

Not a reference and a hello-world. Every snippet on the site is executed before it is published — 458 of them, checked on every commit, so a sample that drifts from the framework fails the build rather than your afternoon.

Tutorial

5 chapters · one idea each

A route, then validation, errors, a database, auth. Start here if you have not written an Oven app before.

Your first route →

Recipes

4 builds · one sitting each

A finished feature you drop into an app you already have. Sign in with Google, image uploads, expiring share links, real-time notifications.

Browse recipes →

Projects

3 backends · 16 chapters

A whole backend in order — a chat app, a Trello clone, a video platform — including the parts that are actually hard.

Build something real →

The hard parts get their own chapters. Cursor pagination, because offset shows a reader the same message twice. Card ordering, measured to the exact drag where floating-point positions collapse — 42. An upload pipeline with a video that is honestly not watchable yet. Read the ordering chapter →

Measured, not claimed

Fast, and honest about it

Real sockets, 50 connections, identical routes, each framework on the runtime it is actually deployed on. Every number here comes from a script in the repo.

Hono bun
108,845
Oven bun
108,250
Fastify node
80,896
Express node
18,389

requests/second — parameterised JSON route, higher is better

Where it sits

How Oven compares

 ExpressFastifyNestJSFastAPIOven
RuntimeNodeNodeNodePythonBun
Auth / DB / queue / mail / S3partialpartial
Automatic OpenAPIpluginplugin
Types inferred from schemaspartialpartial
File-based routing
Body / cookies / uploads always onpartial
Per-request dependency injection
Google / GitHub sign-in in the boxmodule

We are not competing on router microseconds — Bun makes everything fast. We compete on the time between bun create and an app shaped like production.

Where FastAPI is still ahead: OAuth2 scopes, static file serving, and per-exception handlers. Those are on the list rather than in the box, and the brick pages say so.

A deliberate break

Every framework that shipped an Express compatibility layer ended up shaped by Express — inheriting its mistakes along with its users.

So Oven ships none. No (req, res, next), no Connect middleware, no CommonJS build, no callbacks. Web standards, ESM and async only. If you want Express, Express exists, and it is very good at being Express.

Read the philosophy →

Bake something.

Scaffold a project and you have a database, working auth and email before you have provisioned anything at all.