Prisma with Next.js
Prisma with Next.js
Section titled “Prisma with Next.js”Introduction
Section titled “Introduction”Prisma is the most popular ORM for Next.js applications. It provides type-safe database access, auto-generated queries, and declarative migrations. This topic covers the full workflow from schema design to production queries.
Why Prisma?
Section titled “Why Prisma?”- Type-safe queries with full TypeScript support
- Auto-generated client from your schema
- Declarative migrations with version control
- Visual data browser (Prisma Studio)
- Great serverless support via Prisma Accelerate
npm install @prisma/clientnpx prisma initThis creates:
prisma/ schema.prisma # Your data model.env # Database connection stringSchema Design
Section titled “Schema Design”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 bio String? 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 tags TagOnPost[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt}
model Tag { id String @id @default(cuid()) name String @unique posts TagOnPost[]}
model TagOnPost { postId String tagId String post Post @relation(fields: [postId], references: [id]) tag Tag @relation(fields: [tagId], references: [id])
@@id([postId, tagId])}Running Migrations
Section titled “Running Migrations”# Create the initial migrationnpx prisma migrate dev --name init
# Create migration after schema changesnpx prisma migrate dev --name add-bio-field
# Apply migrations in productionnpx prisma migrate deploy
# Generate client after pulling schemanpx prisma generateCRUD Operations
Section titled “CRUD Operations”Create
Section titled “Create”"use server"
import { db } from '@/lib/db'
export async function createPost(formData: FormData) { const post = await db.post.create({ data: { title: formData.get('title') as string, content: formData.get('content') as string, authorId: session.user.id, tags: { create: ['react', 'nextjs'].map(name => ({ tag: { connectOrCreate: { where: { name }, create: { name } } } })) } }, include: { author: true, tags: { include: { tag: true } } } })
return post}// app/posts/page.tsx (Server Component)async function getPosts() { return db.post.findMany({ where: { published: true }, include: { author: { select: { name: true, email: true } }, _count: { select: { tags: true } } }, orderBy: { createdAt: 'desc' }, take: 20, skip: 0, })}
export default async function PostsPage() { const posts = await getPosts()
return <div>{/* render posts */}</div>}Update
Section titled “Update”"use server"
export async function updatePost(formData: FormData) { await db.post.update({ where: { id: formData.get('postId') as string }, data: { title: formData.get('title') as string, content: formData.get('content') as string, } })
revalidatePath('/posts')}Delete
Section titled “Delete”"use server"
export async function deletePost(formData: FormData) { const postId = formData.get('postId') as string
// Cascading delete handles related records await db.post.delete({ where: { id: postId } })
revalidatePath('/posts')}Relations
Section titled “Relations”// Eager loading with includeconst userWithPosts = await db.user.findUnique({ where: { id: userId }, include: { posts: { include: { tags: { include: { tag: true } } }, where: { published: true }, orderBy: { createdAt: 'desc' }, } }})
// Lazy loading (separate query)const user = await db.user.findUnique({ where: { id: userId } })const posts = await db.user.findUnique({ where: { id: userId } }).posts()Pagination
Section titled “Pagination”async function getPaginatedPosts(page: number) { const take = 10 const skip = (page - 1) * take
const [posts, total] = await Promise.all([ db.post.findMany({ take, skip, orderBy: { createdAt: 'desc' } }), db.post.count() ])
return { posts, totalPages: Math.ceil(total / take), currentPage: page, }}Prisma Studio
Section titled “Prisma Studio”npx prisma studioOpens a visual data browser at http://localhost:5555 where you can view, create, and edit records.
Common Mistakes
Section titled “Common Mistakes”- N+1 queries — Use
includeorselectto eager-load relations instead of fetching in a loop - Selecting all fields — Use
selectto only fetch the fields you need - Forgetting
generateafter schema changes — The client must be regenerated - Not using transactions — Wrap related mutations in
$transaction
Best Practices
Section titled “Best Practices”- Always generate the Prisma client after schema changes
- Use
selectto limit fetched fields for performance - Use
includefor eager loading to avoid N+1 - Wrap related mutations in transactions
- Use Prisma Accelerate or PgBouncer for serverless
- Run
npx prisma studioduring development for quick data inspection
Summary
Section titled “Summary”Prisma provides type-safe database access with auto-generated queries and declarative migrations. Use include for eager loading, select for field limiting, and $transaction for atomic operations. The Prisma client is your type-safe bridge between Next.js and your database.