Describe your backend once. Then just run your tests.
You write one config file and never touch DB lifecycle code again. babystack gives every test run its own fresh, seeded, disposable real database — in parallel, thrown away after.
The internals live in How it works ↗.
Set it up once. Then just run your tests.
The whole babystack workflow — four small things you do once, then every
pnpm test hands each worker its own fresh, seeded,
isolated real database. Pick your setup and play it, or step through yourself.
- @babystack/* installed
- babystack.config.ts — backend described once
- vitest.config.ts — +3 lines
- tests unchanged — no babystack import
each worker gets its own fresh, isolated real MySQL — green, then gone.
Real tests. Real MySQL. Zero lifecycle code.
The walkthrough above isn't hypothetical — it's this app.
examples/users-api is a real Express 5 + Drizzle +
MySQL API whose whole suite runs against fresh, seeded, isolated real MySQL, with no
babystack imports in the tests. Copy it into your repo.
import request from 'supertest' import { expect, test } from 'vitest' import { createApp } from '../src/app' const app = createApp() // A real engine means the UNIQUE(email) constraint actually fires. // A mock would accept the duplicate and hand you false confidence. test('enforces the real UNIQUE(email) constraint', async () => { await request(app).post('/users') .send({ email: 'dup@example.com', name: 'First' }).expect(201) await request(app).post('/users') .send({ email: 'dup@example.com', name: 'Second' }).expect(409) })
No beforeEach, no truncation, no babystack
import — the DB is already fresh and seeded when the test runs.
- ✓ Isolation — each file's worker sees only the baseline, never another file's writes
- ✓ Committed writes — a created row is real and visible to the next request, not a rolled-back transaction
-
✓
Real constraints — a duplicate email is a real
UNIQUE409, not a mock that says yes - ✓ Seeded baseline — a fresh worker starts from exactly the seed, every run
Run it: pnpm --filter users-api test (Docker
running). More examples — Prisma, TypeORM — are on the
roadmap.
You own the description. babystack owns the mechanics.
Two things, both familiar
babystack.config.ts (one file) + your tests,
unchanged — they just read DATABASE_URL, like
prod.
The whole lifecycle
provision · seed · isolate per worker · reset · dispose — a fresh real DB on demand, gone after.
Set up once. Then it's just pnpm test.
Locally, in CI, and (deeper) for agents — the same command, the same engine.
install → hand-write config (baby init lands in
Phase 1) → baby doctor
pnpm test — a fresh seeded DB per worker,
parallel, gone after.
baby home — a throwaway seeded DB to poke at by
hand.
the same pnpm test + one cache step (no
re-seed).
baby wake + the reset loop — a resettable DB
for a coding agent.
Three small steps.
Install
One package for the MySQL + Vitest wedge.
pnpm add -D @babystack/vitest
Configure baby init · Phase 1
npx baby init will inspect your repo and write
these for you. Until then, hand-write two small files:
// babystack.config.ts — describe your backend ONCE export default defineConfig({ services: { db: { engine: 'mysql', image: 'mysql:8.4', baseline: { build: ['pnpm db:migrate', 'pnpm db:seed'] }, }, }, })
// vitest.config.ts — the whole integration globalSetup: ['@babystack/vitest/global-setup'], setupFiles: ['@babystack/vitest/setup'], pool: 'forks',
Preflight built · 0.7
baby doctor catches friction before you run —
Docker reachable, Node ≥ 22, a valid config, and no import-time
DATABASE_URL reads.
$ baby doctor ✔ node v22.10.0 (needs >=22) ✔ docker engine reachable ✔ config 1 service: db → mysql:8.4 ✔ env-read no import-time DATABASE_URL reads in src/
Mostly: nothing new to learn.
The whole point. Test files are untouched — babystack provisions once, seeds once, and hands each worker its own fresh database.
pnpm test # fresh · seeded · isolated real MySQL / worker · gone after
baby home hands you a disposable, seeded DB to
click through by hand.
eval "$(baby home)" # then: mysql "$DATABASE_URL"
No new pipeline.
The same pnpm test, plus one cache step so the
baseline isn't rebuilt each run (no re-seed, no DB access).
# .github/workflows/test.yml — Linux runners ship Docker - uses: actions/cache@v4 with: { path: .babystack/cache, key: babystack-${{ hashFiles('babystack.config.ts','db/**') }} } - run: pnpm test # babystack provisions its own MySQL — no services: block
A real backend an agent can break — and undo.
The same engine, CLI-first (built · 0.7): a coding agent gets its own persistent, seeded DB it can reset between attempts — and can never touch your prod. (A deeper MCP integration is Phase 2.5.)
baby wake # provision + seed a real MySQL, left running eval "$(baby home)" # export DATABASE_URL for this shell # … the agent writes / migrates / queries, checks the result … baby reset # back to the pristine baseline, same URL — try again baby sleep # done: dispose the container
Delete the plumbing.
-
✕
docker-compose.test.yml+ 180 lines of setup/teardown -
✕
beforeAll(migrate, seed…)/afterEach(truncate…)in every repo - ✕ a shared dev DB that flakes; tests that can't run in parallel
- ✕ "works on my machine"
-
✓ one
babystack.config.ts -
✓ two lines in
vitest.config.ts -
✓ test files unchanged — just
pnpm test - ✓ parallel, isolated, real, reproducible
The engine is built & proven; the glue lands sub-phase by sub-phase.
The flow above is the product experience. Here's what's real today.
0.1–0.3 — proven against real MySQL.
pnpm test
built
Vitest wiring + parallel isolation (0.4–0.5); safety hardening (0.6).
baby CLI
built · 0.7
doctor · wake · home · reset · sleep — the operator + agent surface.
Full written usage: docs/guide/getting-started.md ·
internals: How it works ↗.