Development
Learn how to run Litestore day to day: starting Postgres and the Turbopack dev server, reseeding with db:seed and db:reset, and the checks bun run verify runs before you commit.
Local development needs one live service — PostgreSQL — and the scripts in package.json.
That is the whole standing requirement. Redis, S3 and Stripe are configured as variables, but nothing local has to connect to them for the store to run.
This page covers the commands you run every day, how the seed and reset scripts behave, and the gate to run before you commit. It assumes you have completed Setup.
Services
The repository ships compose.yml with two services, Postgres and PgBouncer:
docker compose up -dStarts both. Only one of them is something you point the app at.
| Service | Port | Needed locally |
|---|---|---|
| PostgreSQL | 5432 | Yes — point DATABASE_URL here |
| PgBouncer | 6543 | No — docker compose up -d starts it anyway; it exists for production pooling |
| Redis (Upstash) | — | No live instance — checkRateLimit returns early in development and never reaches Redis |
| S3 bucket | — | Only when testing uploads |
REDIS_REST_URL and REDIS_REST_TOKEN are still required by env.ts, which validates with @t3-oss/env-nextjs at module load — an unset value throws before any route runs.
Placeholder strings satisfy it locally because checkRateLimit short-circuits in development. Production needs a real Upstash instance.
The daily loop
bun run devRuns next dev -p 3000 --turbopack.
The storefront serves on http://localhost:3000 and the admin on http://localhost:3000/admin — one Next.js app, not two servers.
After a prisma/schema.prisma change, regenerate the client and restart:
bun run db:generatepostinstall runs the same script, so a fresh bun install regenerates the client for you.
Resetting state
Re-running the seed is safe:
bun run db:seedThe script is tsx prisma/seed.ts. It writes through upsert, and most of those upserts carry an empty update: {} block — so rows you have already edited keep your values rather than reverting to seed values.
That is usually what you want, and it is also why reseeding does not undo a broken local state. To get the seed's values back, rebuild instead:
bun run db:resetThat is prisma migrate reset: it drops the database, re-applies the committed migrations, then runs the seed, which prisma.config.ts registers as bun prisma/seed.ts.
To inspect rows directly:
bun run db:studioPrisma Studio binds to port 5556, not Prisma's default 5555, so it does not collide with another project's Studio.
Checks before you commit
Each leg of the gate is its own script, so you can run the one that matches what you just changed.
bun run lintRuns biome check --write, then cache, discipline, dead, flex, critical-tests, no-as-any and cleanliness. This one writes fixes.
bun run lint:disciplineRuns scripts/check-discipline.ts alone.
bun run vitest runA single pass of the test suite. bun run test is the same suite in watch mode.
bun run typecheckRuns tsc --noEmit across the app.
Or the whole gate:
bun run verifyverify runs biome check, twelve lint:* scripts (lint:cleanliness chains a further thirteen), vitest run and typecheck in sequence, and stops at the first failure.
There are 25 scripts/check-*.ts checks in total; see The harness for what each one enforces.
Typecheck memory
typecheck sets NODE_OPTIONS='--max-old-space-size=12288' — a 12 GB heap
ceiling. On a smaller machine, stop the dev server before running it.
Verifying a change end to end
The seeded catalog is a working store, so most flows run without extra setup: browse /products, add to cart, and walk checkout to the payment step.
STRIPE_SECRET_KEY is optional in env.ts, so the step past that needs Stripe configured.
In the admin, Settings → Developer renders the self-host readiness items, which probe Postgres with SELECT 1 and Redis with ping() on a 3-second timeout. Home renders the computed task list.
Troubleshooting
Every route returns 500 with "Invalid environment variables". env.ts validates at module load, so one missing value fails every request. The console output names the keys; REDIS_REST_URL and REDIS_REST_TOKEN are the pair most often left unset.
Pages load, but slowly. The Prisma adapter in services/db.ts sets connectionTimeoutMillis: 30_000, so an unreachable or distant DATABASE_URL turns each connection attempt into a long wait instead of an immediate error. Point it at your local Postgres on 5432.
A Prisma model "does not exist". The generated client is stale. Run bun run db:generate.
Which command to reach for
While working, run the narrowest thing that covers what you touched: lint:discipline after moving code between modules, vitest run after changing logic, typecheck after changing types or the schema.
Run bun run verify before you commit, because that is the gate a commit has to pass anyway.
Reach for db:reset only when you want the seed's values back — db:seed deliberately will not overwrite rows you have edited.