Database
How to add tables, run migrations, and query the database from your app.
All apps share a single Postgres database. Prisma manages the schema (and generates migrations); apps use the pg library at runtime. You never run migrations locally — they're generated and applied automatically on PRs and merges.
The One Schema File
Every table in every app lives in a single file:
shared/database/prisma/schema.prisma
Your app's tables sit alongside tables from every other app. Prefix every app-specific table with the app name in snake_case so ownership is obvious:
// ============================================
// Meal Planner Tables
// ============================================
model MealPlanner_Recipe {
id String @id @default(cuid())
name String
createdAt DateTime @default(now())
@@map("meal_planner_recipe")
}
The @@map(...) is the actual SQL table name; the Prisma model name is for codegen readability.
See Naming Conventions for the full rules.
Migration Workflow (Zero Local Setup)
You never touch prisma migrate dev or manage .sql files by hand. The pipeline does it:
- Edit the schema: add/modify a model in
shared/database/prisma/schema.prisma. - Update docs: reflect the change in shared/schema.md — ownership, purpose, relationships.
- Commit and push to your feature branch; open a PR to
main. - CI auto-generates the migration and commits it back to your PR.
- A Neon DB branch is created for the PR, the migration is applied, and the preview app deploys against it.
- On merge to
main, the migration applies to Neon main and production redeploys.
Prefer additive changes: add new tables and nullable columns instead of modifying existing ones. Schema changes that require data migration need developer review.
Querying at Runtime
Use pg via the getDb() helper from the template (app/_lib/db.ts). Always use parameterized queries ($1, $2, …):
import { getDb } from '@/app/_lib/db';
const db = getDb();
const result = await db.query(
'SELECT * FROM meal_planner_recipe WHERE user_id = $1',
[userId]
);
const recipes = result.rows;import { getDb } from '@/app/_lib/db';
const db = getDb();
const result = await db.query(
'INSERT INTO meal_planner_recipe (name, user_id) VALUES ($1, $2) RETURNING *',
[name, userId]
);
const newRecipe = result.rows[0];Never build SQL by string concatenation — that's a SQL injection. $1 / $2 placeholders are safe.
Where You Actually Hit the Database
Database queries only run in PR preview environments and production — never locally. When you open a PR, the pipeline creates an isolated Neon branch with your migrations applied, and the preview deploy connects to it. That's where you test database behavior; the preview URL is posted as a PR comment.
When Something Goes Wrong
If the migration step fails on your PR (red check), see the Database Migration Errors troubleshooting page.
Quiz
You added a new model to shared/database/prisma/schema.prisma. What do you do next to get it into the production database?