Litestore · Developer guide
Getting started

Development

This page shows you how to run Litestore day to day, reset its data and check your work before you commit.

Once Setup is done, this page covers the commands you run every day. It also explains how the seed and reset scripts behave and which checks to run before you commit.

To develop locally you only need one live service, PostgreSQL, plus the scripts in package.json. You still set variables for Redis, S3 and Stripe, but the store runs without connecting to any of them on your machine.

Services

The repository ships a compose.yml with two services, Postgres and PgBouncer. Start both with:

docker compose up -d

Only Postgres is something you point the app at.

ServicePortNeeded locally
PostgreSQL5432Yes. Point DATABASE_URL here.
PgBouncer6543No. docker compose up -d starts it anyway, but it is there for connection pooling in production.
Redis (Upstash)—No live instance. checkRateLimit returns early in development and never reaches Redis.
S3 bucket—Only when you test uploads.

env.ts still requires REDIS_REST_URL and REDIS_REST_TOKEN. It validates with @t3-oss/env-nextjs when the module loads, so an unset value throws before any route runs.

Locally, placeholder strings pass that check because checkRateLimit skips Redis in development. In production you need a real Upstash instance.

The daily loop

bun run dev

This runs next dev -p 3000 --turbopack.

The storefront serves on http://localhost:3000 and the admin on http://localhost:3000/admin. They are one Next.js app, not two servers.

When you change prisma/schema.prisma, regenerate the client and restart the dev server:

bun run db:generate

postinstall runs the same script, so a fresh bun install also regenerates the client for you.

Resetting state

You can safely run the seed again:

bun run db:seed

The script is tsx prisma/seed.ts. It writes with upsert, and most of those upserts have an empty update: {} block. Rows you have already edited keep your values and don't revert to the seed values.

That is usually what you want. It also means reseeding won't fix a broken local database. To get the seed's values back, rebuild the database instead:

bun run db:reset

This runs prisma migrate reset. It drops the database, applies the committed migrations again, and then runs the seed. prisma.config.ts registers the seed as bun prisma/seed.ts.

To look at rows directly, open Prisma Studio:

bun run db:studio

Prisma Studio listens on port 5556 instead of Prisma's default 5555. That way it doesn't collide with another project's Studio.

Checks before you commit

Each part of the commit gate is its own script. You can run just the one that matches what you changed.

bun run lint

This runs biome check --write and then the cache, discipline, dead, flex, critical-tests, no-as-any and cleanliness checks. It writes fixes to your files.

bun run lint:discipline

This runs scripts/check-discipline.ts on its own.

bun run vitest run

This runs the test suite once. bun run test runs the same suite in watch mode.

bun run typecheck

This runs tsc --noEmit across the app.

To run the whole gate at once:

bun run verify

verify runs biome check, then twelve lint:* scripts, then vitest run and typecheck. One of those scripts, lint:cleanliness, runs a further thirteen checks. verify runs everything in order and stops at the first failure.

There are 25 scripts/check-*.ts checks in total. The harness explains what each one enforces.

Typecheck memory

typecheck sets NODE_OPTIONS='--max-old-space-size=12288', which allows Node a 12 GB heap. On a smaller machine, stop the dev server before you run it.

Verifying a change end to end

The seeded catalog is a working store, so you can try most flows without extra setup. Browse /products, add something to the cart and go through checkout up to the payment step.

STRIPE_SECRET_KEY is optional in env.ts. To get past the payment step, you need Stripe configured.

In the admin, Settings → Developer shows the self-host readiness items. They check Postgres with SELECT 1 and Redis with ping(), each with a 3-second timeout. Home shows the task list that Litestore computes from your data.

Troubleshooting

Every route returns 500 with "Invalid environment variables". env.ts validates when the module loads, so one missing value fails every request. The console output names the keys. People most often leave REDIS_REST_URL and REDIS_REST_TOKEN unset.

Pages load, but slowly. The Prisma adapter in services/db.ts sets connectionTimeoutMillis: 30_000. If DATABASE_URL points at a database that is unreachable or far away, each connection attempt becomes 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 you work, run the narrowest check that covers what you touched:

  • Run lint:discipline after you move code between modules.
  • Run vitest run after you change logic.
  • Run typecheck after you change types or the schema.

Run bun run verify before you commit, because a commit has to pass that gate anyway.

Use db:reset only when you want the seed's values back. db:seed deliberately won't overwrite rows you have edited.

Next Steps

On this page