how you use it

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 ↗.

how you use it

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.

your setup — done once
  • @babystack/* installed
  • babystack.config.ts — backend described once
  • vitest.config.ts — +3 lines
  • tests unchanged — no babystack import
every run — just pnpm test

each worker gets its own fresh, isolated real MySQL — green, then gone.

a real example

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.

tests/constraint.test.ts
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.

what the suite proves
  • 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 UNIQUE 409, 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.

00 · the deal

You own the description. babystack owns the mechanics.

you own

Two things, both familiar

babystack.config.ts (one file) + your tests, unchanged — they just read DATABASE_URL, like prod.

babystack owns

The whole lifecycle

provision · seed · isolate per worker · reset · dispose — a fresh real DB on demand, gone after.

01 · the pipeline, end to end

Set up once. Then it's just pnpm test.

Locally, in CI, and (deeper) for agents — the same command, the same engine.

① set up · once

install → hand-write config (baby init lands in Phase 1) → baby doctor

② test

pnpm test — a fresh seeded DB per worker, parallel, gone after.

③ dev

baby home — a throwaway seeded DB to poke at by hand.

④ CI

the same pnpm test + one cache step (no re-seed).

⑤ agents

baby wake + the reset loop — a resettable DB for a coding agent.

02 · one-time setup (per repo)

Three small steps.

1

Install

One package for the MySQL + Vitest wedge.

pnpm add -D @babystack/vitest
2

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',
3

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/
03 · everyday

Mostly: nothing new to learn.

▶ run your tests built · 0.4–0.5

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
⎇ local dev DB built · 0.7

baby home hands you a disposable, seeded DB to click through by hand.

eval "$(baby home)" # then: mysql "$DATABASE_URL"
04 · CI — the same command

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
05 · coding agents

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
06 · what it replaces

Delete the plumbing.

before hand-rolled
  • 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"
after babystack
  • one babystack.config.ts
  • two lines in vitest.config.ts
  • test files unchanged — just pnpm test
  • parallel, isolated, real, reproducible
07 · today vs. intended

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.

engine built

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 ↗.