CLI
oven create my-app # scaffold a projectoven dev # run with a watcheroven build # bundle and write a route manifestoven start # run the buildoven db migrate # run schema and migration commandsoven worker # run background jobsoven routes # print the route tableoven openapi # emit the OpenAPI documentoven doctor # check the project for common problemsoven create
Section titled “oven create”oven create my-app --db sqlite --auth basic| Flag | Values | Default |
|---|---|---|
--template |
minimal, api |
api |
--db |
sqlite, postgres, none |
asked, or none |
--auth |
basic, none |
asked, or none |
--openapi |
--openapi / --no-openapi |
on |
--yes |
skip every prompt | — |
--db sqlite scaffolds Drizzle over bun:sqlite — no server to run, no native module. The
switch to Postgres is one import and one line in src/db.ts; every query you have written stays
the same.
--auth basic adds working signup, login, refresh, logout, password change and reset, with the
auth-basic tables re-exported from src/schema.ts so
oven db generate picks them up. Mail defaults to the console driver, so the reset link is
printed to your terminal and the whole flow works before you have configured a provider. Auth
implies a database; asking for auth with --db none scaffolds SQLite and says so.
Every project also gets an AGENTS.md describing the conventions — route naming, return
values instead of res.json(), defineRoute, native ORM queries — and the things never to do,
because a model that has not read the docs otherwise reaches for Express habits.
oven create my-app --template apiInteractive on a TTY, flag-driven otherwise — a scaffolder that prompts inside CI hangs forever,
which is a bad way to find out a pipeline is misconfigured. Pass --yes to skip prompts.
| Template | Contents |
|---|---|
minimal |
one route, nothing else |
api |
a small REST resource with validation and OpenAPI |
It refuses to write into a non-empty directory. A directory holding only .git is fine, since
git init comes first for a lot of people.
oven dev
Section titled “oven dev”oven dev --port 4000Runs the entry under a watcher. Restarts on change rather than patching modules in place: in-place reloading leaves a server bound to the port and brick state half-initialised from the previous version, and a clean restart takes single-digit milliseconds in Bun. The correctness is worth more than the milliseconds.
Signals are forwarded, so Ctrl-C reaches the app and its graceful shutdown actually runs.
oven build
Section titled “oven build”oven build --outdir distTwo steps, in this order:
- Write a route manifest —
.oven/routes.ts, with a static import per route file. - Bundle with
Bun.build, targeting Bun.
The order matters: the bundler can only include route modules it can see imported by name.
oven routes and oven openapi
Section titled “oven routes and oven openapi”oven routesoven openapi > openapi.jsonoven openapi --out openapi.json --title "My API" --api-version 1.0.0Both import the app module — src/app.ts — not the entry. That is why the scaffold splits
them: app.ts builds and exports the app, index.ts calls listen(). If inspection imported
the entry, asking for a route table would bind a port.
With no --out, oven openapi writes only JSON to stdout, so it can be piped straight into a
client generator.
oven db
Section titled “oven db”oven db generate # write a migration from the current schemaoven db migrate # apply pending migrationsoven db push # push the schema straight to the database (development)oven db studio # open the database browseroven db drop # drop a generated migrationThese delegate to whatever migration tool your project already uses, detected from its config
file — drizzle.config.ts means drizzle-kit, prisma/schema.prisma means the Prisma CLI. Oven
does not reimplement migration generation; a wrapper around it would drift from the real tool
within a release.
What you get instead is one set of command names whatever adapter you chose, and translation
where the tools disagree — oven db push runs drizzle-kit push in one project and
prisma db push in another. A command with no equivalent says so rather than running something
that looks similar.
migrate runs in-process on Drizzle
Section titled “migrate runs in-process on Drizzle”generate, push, studio and drop shell out to drizzle-kit. migrate does not, on Drizzle
projects: drizzle-kit’s SQLite migrator cannot use bun:sqlite and asks you to install
better-sqlite3 or @libsql/client, which would mean the default Oven stack could generate a
migration and then fail to apply it.
Drizzle ships Bun-native migrators, so oven db migrate uses those. It reads the same
drizzle.config.ts drizzle-kit does, so the two cannot disagree about where migrations live.
Anything after -- is passed through unchanged:
oven db generate -- --name add_orders_tableoven worker
Section titled “oven worker”oven worker # long-running; ctrl-c drains in-flight jobsoven worker --concurrency 20oven worker --once # drain what is queued and exitImports your app module, so the worker gets the same database, the same mail driver and the same job definitions the app has. A worker configured separately from its app is a worker that drifts from it — and the symptom is jobs dead-lettering as “no handler registered” after a deploy.
--once is for a cron container or a CI step: it drains what is queued and exits rather than
idling. See queue.
oven doctor
Section titled “oven doctor”oven doctor✓ Bun version 1.2.23✓ Entry src/index.ts✓ App module src/app.ts✓ Routes src/routes! Environment .env.example present, .env missing Copy it: cp .env.example .env✓ Port 3000 is freeEvery check that is not ok says what to do about it. A diagnostic that only reports a problem
has done half the job.
Environment variables
Section titled “Environment variables”Not a command, but the same class of problem — catching configuration mistakes at boot rather than in production:
import { env, defineEnv } from '@theoven/core'
const port = env.port('PORT', 3000) // parses, or throws naming PORTconst debug = env.bool('DEBUG', false) // DEBUG=false is false, not truthyoven doctor checks that a .env and .env.example agree. See
Environment variables for the full reader and for defineEnv.
Commands that need a brick
Section titled “Commands that need a brick”oven db needs a migration toolchain in the project and oven worker needs the
queue brick registered. Neither guesses: a project without one gets a
sentence saying what is missing and what to add, rather than an error that looks like a typo you
made.