Introduction
Most APIs need a database. Prisma is a type-safe ORM that makes database operations feel like TypeScript—autocomplete, type checking, and no raw SQL strings.
Key Concepts
Prisma consists of three parts:
- Schema: Define your data models in
schema.prisma - Client: Auto-generated, type-safe database client
- Migrate: Database migration tool
Real World Context
Prisma gives you:
- TypeScript types generated from your schema
- Autocomplete for queries and relations
- Protection against SQL injection
- Database-agnostic queries
Deep Dive
Setup
bashnpm install prisma @prisma/client npx prisma init
Define Your Schema
prisma// prisma/schema.prisma generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id String @id @default(cuid()) email String @unique name String? role String @default("user") posts Post[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Post { id String @id @default(cuid()) title String content String? published Boolean @default(false) author User @relation(fields: [authorId], references: [id]) authorId String createdAt DateTime @default(now()) }
Generate Client and Migrate
bashnpx prisma migrate dev --name init npx prisma generate
Singleton Pattern (Critical for Next.js)
typescript// lib/prisma.ts import { PrismaClient } from '@prisma/client'; const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined; }; export const prisma = globalForPrisma.prisma ?? new PrismaClient({ log: process.env.NODE_ENV === 'development' ? ['query'] : [], }); if (process.env.NODE_ENV !== 'production') { globalForPrisma.prisma = prisma; }
Using in Route Handlers
typescriptimport { prisma } from '@/lib/prisma'; export async function GET() { const users = await prisma.user.findMany({ include: { posts: true }, orderBy: { createdAt: 'desc' }, }); return Response.json(users); } export async function POST(request: Request) { const { name, email } = await request.json(); const user = await prisma.user.create({ data: { name, email }, }); return Response.json(user, { status: 201 }); }
Common Query Patterns
typescript// Find with conditions const activeUsers = await prisma.user.findMany({ where: { role: 'admin' }, }); // Find one by unique field const user = await prisma.user.findUnique({ where: { email: 'john@example.com' }, }); // Find with relations const userWithPosts = await prisma.user.findUnique({ where: { id: userId }, include: { posts: true }, }); // Select specific fields const emails = await prisma.user.findMany({ select: { email: true, name: true }, });
Common Pitfalls
-
Multiple PrismaClient instances: Without the singleton pattern, hot reload creates new connections until your database runs out.
-
Not handling missing records:
findUniquereturnsnullif not found. Always check before using. -
Forgetting to generate: After schema changes, run
npx prisma generatebefore using new fields.
Best Practices
- Always use the singleton pattern: Prevents connection exhaustion in development
- Use transactions for related operations: Keep data consistent
- Index frequently queried fields: Add
@@indexto your schema - Use
selectfor large tables: Don't fetch fields you don't need
Summary
Prisma provides type-safe database access for your Next.js API. Define models in schema.prisma, generate the client, and use the singleton pattern to prevent connection issues. The generated client gives you autocomplete and type safety for all database operations.