vineroute// field manual
Rider
Driver
Dispatch
Systems
Systems/Database
02 / 06

Database

Drizzle + PGlite locally, Postgres in prod

DrizzlePostgresPGlite

Overview

The schema lives at `apps/api/src/db/schema.ts` and defines every table — users, routes, stops, vehicles, assignments, reservations, offers, announcements, otpTokens, tripLogs, driverNotes. Each table is a Drizzle pgTable with typed columns, and the generated InferSelectModel types flow into the shared package so the mobile and admin apps see them too. Local dev uses PGlite so contributors don't need Docker.

How it works

1

`pnpm db:generate` runs drizzle-kit generate which produces a SQL migration in apps/api/drizzle/. We commit the migrations.

2

`pnpm db:migrate` runs the migrations against either PGlite (in dev) or the configured Postgres URL (in staging/prod). The same migration file works on both.

3

Dev DB: PGlite stores data in `apps/api/.pglite/` — a folder we gitignore. Resetting the dev DB is `rm -rf .pglite` and re-running migrate plus seed.

4

Indexes are explicit and named after the access pattern — users_email_unique, reservations_date_route — so query plans are predictable.

5

Soft deletes: most tables have a nullable deletedAt column. Queries `WHERE deleted_at IS NULL` by default, exposed through a Drizzle composable helper.

6

Connection: in dev, a singleton PGlite instance lives on globalThis to survive Next.js hot reloads. In prod, we use a pooled pg.Pool and let the driver manage connections.

Key decisions

PGlite for dev, real Postgres for prod

Running Postgres locally is fine but not free — Docker, ports, init scripts. PGlite gives us 'just clone and run' for new contributors. The same SQL, the same Drizzle, the same migrations. Production differences are limited to connection pooling and the migration runner.

Drizzle over Prisma

Drizzle's typed SQL feels closer to the database than Prisma's query builder, and the generated types are leaner. We also get migrations as SQL files we can read, which is the right level of abstraction for an operational system.

Schema in shared package, not API-only

The mobile and admin apps need the same row types — Offer, Route, Reservation. We export them through @vineroute/shared so a schema change propagates to every consumer in one TypeScript pass.

PreviousAuthenticationNextRealtime