Prisma in Next.js
Define your schema in schema.prisma, run prisma migrate dev, and import the Prisma Client in your Server Components or Server Actions. Avoid instantiating a new client on every hot reload by using the global singleton pattern.
Prisma ORM with Next.js
Prisma is a modern, type-safe Object-Relational Mapper (ORM) that simplifies database integrations using a declaratively defined database schema. In a Next.js environment, Prisma auto-generates TypeScript types matching your database structures, allowing you to write queries with complete autocomplete support. In this lesson, we will cover how to design schema tables, write migrations, configure the Prisma Client using a global singleton to prevent hot-reload connection leaks, and perform data reads and writes inside Server Components and Actions.
1. Setting Up Prisma inside Next.js
To integrate Prisma into a Next.js 15 project, install the Prisma CLI and the client packages:
npm install @prisma/client
npm install -D prisma
Initialize Prisma config directories:
npx prisma init
This command scaffolds a prisma/ folder at your project root containing a schema.prisma file, and adds a .env file containing your database connection string template.
2. Modeling Tables in schema.prisma
The schema.prisma file serves as the single source of truth for your database layout. You define your database engine source (e.g. PostgreSQL), type generators, and data models:
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
}
3. Generating Migrations and Prisma Client
Once you define or edit models in schema.prisma, run the Prisma CLI to generate a migration file and apply modifications to your live database schema:
npx prisma migrate dev --name init
This command:
- Generates a SQL migration file in
prisma/migrations/. - Executes the SQL queries to update your live database.
- Generates the local Prisma Client library, updating its TypeScript typings.
4. The Global Client Singleton Pattern
In Next.js development mode, files are hot-reloaded on every edit. If you instantiate the Prisma Client directly in your data helper files, code edits will run constructor triggers repeatedly:
// BAD: DO NOT DO THIS
import { PrismaClient } from '@prisma/client';
export const prisma = new PrismaClient();
This locks up database sockets within minutes. Instead, use a global singleton pattern to preserve a single client instance:
// lib/prisma.ts
import { PrismaClient } from '@prisma/client';
const prismaClientSingleton = () => {
return new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
});
};
declare global {
var prismaGlobal: undefined | ReturnType<typeof prismaClientSingleton>;
}
export const prisma = globalThis.prismaGlobal ?? prismaClientSingleton();
if (process.env.NODE_ENV !== 'production') {
globalThis.prismaGlobal = prisma;
}
5. Querying Database in async Server Components
Because Server Components compile server-side, you can import the prisma client and execute queries directly within your components:
// app/blog/page.tsx
import { prisma } from '@/lib/prisma';
export default async function BlogIndexPage() {
// Query only published posts, fetching the author relation
const posts = await prisma.post.findMany({
where: { published: true },
include: {
author: {
select: { name: true, email: true },
},
},
orderBy: { createdAt: 'desc' },
});
return (
<main className="p-8">
<h1 className="text-3xl font-bold mb-6">Course Articles</h1>
<div className="space-y-4">
{posts.map((post) => (
<article key={post.id} className="border p-4 rounded-xl">
<h2 className="text-xl font-bold">{post.title}</h2>
<p className="text-sm text-neutral-500">
By {post.author.name || 'Anonymous'}
</p>
</article>
))}
</div>
</main>
);
}
6. Handling Data Mutations with Server Actions
Write Server Actions to handle mutations. Prisma will type-check parameters automatically, ensuring you do not run queries with malformed types:
// app/actions/post-actions.ts
'use server';
import { prisma } from '@/lib/prisma';
import { revalidatePath } from 'next/cache';
export async function createPostAction(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
const authorId = 1; // Replace with session authenticated user ID
if (!title || title.trim() === '') {
throw new Error('Title is required.');
}
await prisma.post.create({
data: {
title,
content,
published: true,
authorId,
},
});
// Revalidate cache to display new post instantly on blog path
revalidatePath('/blog');
}
7. Optimizing Queries to Minimize Payload Weight
Avoid querying entire columns if they are not needed in the layout. Prisma supports select-filtering which limits SQL payloads to the exact fields defined:
// Selecting specific fields
const userList = await prisma.user.findMany({
select: {
id: true,
email: true,
// Avoids loading large metadata fields
},
});
8. The Prisma Edge Compatibility Dilemma
Standard Prisma Client uses a Rust-based query engine binary that runs on Node.js but crashes in Next.js Edge Middleware or Cloudflare Worker runtimes.
If your application requires database queries within Edge layouts, you must utilize the @prisma/extension-accelerate extension or access your database via HTTP request APIs.
9. Common Gotchas
- Socket Exhaustion during Live Reloads: Forgetting to implement the global client singleton in Next.js dev mode will exhaust your database pool limit in minutes, blocking your dev server.
-
N+1 Query Problems: Fetching users and then loop-mapping them to query their posts individually results in multiple SQL requests. Always use the
includeblock to join tables in a single transaction. -
Schema Out-of-Sync Errors: If you run schema changes manually on your database without executing
prisma generate, client queries will throw database schema out-of-sync warnings.
Key Takeaways
- Prisma uses the
schema.prismafile as the single definition layout for your entire database schema. - Use
npx prisma migrate devto apply database alterations and compile type-safe database clients. - Implement the global client singleton pattern to preserve connection pools across development hot reloads.
- Import the Prisma client and query databases directly inside Server Components without writing REST endpoints.
- Limit network overhead by choosing exact field listings inside the
selectparameter.
Prisma simplifies schema management and autocomplete validation. However, its heavy Rust query engine can affect cold start performance in serverless platforms. In the next lesson, we will explore Drizzle ORM — A Lighter Alternative, building edge-compatible schemas using pure TypeScript.