Setting Up Your Database & Seeding
So far your Next.js pages can render UI. Real apps also need a database — users, invoices, products. Think of the database as a filing cabinet in the office basement, and your Server Components as staff who walk downstairs, pull a folder, and bring it back to the front desk.
Your app needs a place to store data
So far your Next.js pages can render UI. Real apps also need a database — users, invoices, products. Think of the database as a filing cabinet in the office basement, and your Server Components as staff who walk downstairs, pull a folder, and bring it back to the front desk.
In the App Router you almost always talk to the database from the server (Server Components, Server Actions, or Route Handlers) — never from the browser with your secret connection string.
Postgres or SQLite — pick what fits
Postgres is the usual choice for production (Vercel Postgres, Neon, Supabase, Railway). SQLite (often via Prisma + file DB or Turso) is great for local learning and small projects.
Real-life example: Postgres is a shared warehouse for a growing shop. SQLite is a single notebook on your desk — perfect for practice, limited when many cashiers write at once.
- Postgres — cloud-friendly, great for multi-user apps
- SQLite — zero setup locally, one file
- Prisma, Drizzle, or raw SQL drivers all work with App Router
# Never commit this file
DATABASE_URL="postgresql://user:password@host:5432/mydb?sslmode=require"
# Local SQLite example (Prisma)
# DATABASE_URL="file:./dev.db"import { sql } from "@vercel/postgres";
export async function getInvoices() {
const { rows } = await sql`
SELECT id, customer_name, amount, status
FROM invoices
ORDER BY created_at DESC
`;
return rows;
}// lib/db.ts
import { PrismaClient } from "@prisma/client";
const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log: process.env.NODE_ENV === "development" ? ["error", "warn"] : ["error"],
});
if (process.env.NODE_ENV !== "production") {
globalForPrisma.prisma = prisma;
}
// Usage in a Server Component
// const invoices = await prisma.invoice.findMany();Seed scripts — fill the cabinet before opening day
A seed script inserts starter rows so your dashboard is not empty on first run. You run it once (or when resetting local data): npm run seed.
Real-life example: Seeding is stocking shelves before a shop opens — empty shelves make demos boring and tests flaky.
import { sql } from "@vercel/postgres";
async function seed() {
await sql`
CREATE TABLE IF NOT EXISTS invoices (
id SERIAL PRIMARY KEY,
customer_name TEXT NOT NULL,
amount INT NOT NULL,
status TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
)
`;
await sql`
INSERT INTO invoices (customer_name, amount, status)
VALUES
('Asha Traders', 2500, 'paid'),
('Ravi Motors', 1800, 'pending'),
('Knowvora Labs', 4200, 'paid')
ON CONFLICT DO NOTHING
`;
console.log("Seed complete");
}
seed().catch((err) => {
console.error(err);
process.exit(1);
});