Prisma with Next.js — Full-Stack Database Integration
In this tutorial, you will learn about Prisma with Next.js. We cover key concepts, practical examples, and best practices to help you master this topic.
Prisma with Next.js combines the type-safe database client with React's server components and API routes, enabling efficient data fetching on the server and reducing client-side data loading.
What You'll Learn
By the end of this lesson you will set up Prisma in Next.js, fetch data in server components, create API routes for client mutations, use server actions for form handling, and optimize Prisma for Next.js.
Why It Matters
Next.js App Router with server components enables fetching data directly from Prisma without building API endpoints for every query. This reduces network requests and improves performance.
Real-World Use
DodaZIP's Next.js frontend uses server components to fetch file lists directly from Prisma, while mutations (upload, delete) go through API routes or server actions for security.
Prisma Setup in Next.js
Initialize Prisma for Next.js App Router.
// 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', 'error', 'warn']
: ['error'],
});
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}
export type { PrismaClient };
# Install dependencies
npm install @prisma/client prisma --save-dev
# Initialize Prisma
npx prisma init --datasource-provider postgresql
# After schema changes
npx prisma migrate dev
npx prisma generate
# nextjs_setup.py
# Prisma in Next.js setup
def setup_steps():
print("Prisma + Next.js Setup:")
print()
print("1. Install: npm install @prisma/client && npm install -D prisma")
print("2. Init: npx prisma init --datasource-provider postgresql")
print("3. Create db.ts singleton in lib/")
print("4. Define schema and run prisma migrate dev")
print("5. Use prisma in server components and API routes")
setup_steps()
Server Components
Fetch data directly from Prisma in server components.
// app/dashboard/page.tsx
import { prisma } from '@/lib/prisma';
export default async function DashboardPage() {
const [files, stats] = await Promise.all([
prisma.file.findMany({
where: { status: { not: 'DELETED' } },
orderBy: { createdAt: 'desc' },
take: 10,
include: { user: { select: { name: true } } },
}),
prisma.file.aggregate({
_count: true,
_sum: { sizeBytes: true },
}),
]);
return (
<div>
<h1>Dashboard</h1>
<div className="stats">
<p>Total files: {stats._count}</p>
<p>Total storage: {formatBytes(stats._sum.sizeBytes || 0)}</p>
</div>
<ul>
{files.map(file => (
<li key={file.id}>
{file.name} - {file.user.name}
</li>
))}
</ul>
</div>
);
}
function formatBytes(bytes: number): string {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
}
# server_components.py
# Server component data fetching
def server_components():
print("Server Component Data Fetching:")
print()
print("Benefits:")
print(" - Fetch data directly from database")
print(" - No API route needed for reads")
print(" - Data is fetched during server render")
print(" - Zero client-side JavaScript for data fetching")
print()
print("Pattern:")
print(" async function Page() {")
print(" const data = await prisma.model.findMany()")
print(" return <Component data={data} />")
print(" }")
server_components()
API Routes
Create API endpoints for mutations and client-side needs.
// app/api/files/route.ts
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
// GET /api/files
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get('page') || '1');
const status = searchParams.get('status');
const where: any = {};
if (status) where.status = status;
const [files, total] = await Promise.all([
prisma.file.findMany({
where,
skip: (page - 1) * 20,
take: 20,
orderBy: { createdAt: 'desc' },
}),
prisma.file.count({ where }),
]);
return NextResponse.json({ data: files, total });
}
// POST /api/files
export async function POST(request: Request) {
const body = await request.json();
const file = await prisma.file.create({
data: {
name: body.name,
sizeBytes: body.sizeBytes,
userId: body.userId,
},
});
return NextResponse.json({ data: file }, { status: 201 });
}
// DELETE /api/files/:id
export async function DELETE(
request: Request,
{ params }: { params: { id: string } }
) {
await prisma.file.delete({
where: { id: parseInt(params.id) },
});
return NextResponse.json({ success: true });
}
# api_routes.py
# Next.js API route patterns
def api_patterns():
print("Next.js API Route Patterns:")
print()
print("Route handlers in app/api/:")
print(" app/api/files/route.ts")
print(" GET - List files with filters")
print(" POST - Create new file")
print()
print(" app/api/files/[id]/route.ts")
print(" GET - Get single file")
print(" PUT - Update file")
print(" DELETE - Delete file")
print()
print("Benefits over pages/api:")
print(" - Better TypeScript support")
print(" - Simpler route structure")
print(" - Native streaming support")
api_patterns()
Server Actions
Handle form submissions with server actions.
// app/files/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
import { prisma } from '@/lib/prisma';
import { z } from 'zod';
const createFileSchema = z.object({
name: z.string().min(1).max(255),
content: z.string().optional(),
});
export async function createFile(formData: FormData) {
const validated = createFileSchema.parse({
name: formData.get('name'),
content: formData.get('content'),
});
const file = await prisma.file.create({
data: {
name: validated.name,
userId: getCurrentUserId(), // From auth
},
});
revalidatePath('/files');
return { success: true, id: file.id };
}
export async function deleteFile(fileId: number) {
await prisma.file.delete({ where: { id: fileId } });
revalidatePath('/files');
return { success: true };
}
# server_actions.py
# Server action patterns
def server_actions():
print("Server Actions:")
print()
print("Form handling without API routes:")
print(" 'use server'")
print(" async function createFile(formData) {")
print(" const validated = schema.parse(formData)")
print(" await prisma.model.create({ data: validated })")
print(" revalidatePath('/path')")
print(" }")
print()
print("Benefits:")
print(" - No need for API routes for mutations")
print(" - Automatic revalidation")
print(" - Progressive enhancement (works without JS)")
print(" - Type-safe between client and server")
server_actions()
Common Mistakes
Creating multiple PrismaClient instances in development: Next.js hot reload creates new instances. Use the global Singleton pattern to prevent this.
Fetching data in client components unnecessarily: Use server components for initial data fetching to reduce client-side JavaScript and network requests.
Not Caching Prisma queries: Revalidate cached data with revalidatePath or revalidateTag instead of making new queries.
Using API routes for everything: Server components can fetch data directly. API routes are for client-side mutations or external consumers.
Not handling loading and error states: Server components can throw errors. Use error boundaries and loading states for resilience.
Practice Questions
How do you prevent multiple PrismaClient instances in Next.js? Use a global singleton pattern that caches the client on the global object.
What is the advantage of server components for data fetching? They fetch data directly from Prisma during server render, reducing client-side JavaScript and network requests.
When should you use API routes vs server actions? API routes for external API consumers and complex mutations. Server actions for form submissions within the same app.
What does revalidatePath do? It invalidates the cache for a specific route, causing server components to re-fetch data on the next request.
Challenge: Build a Next.js page that lists files from Prisma in a server component, with a server action to delete files and an API route for bulk operations.
FAQ
Mini Project
Create a Next.js application with Prisma: server component dashboard showing file statistics, API routes for file CRUD, server actions for file uploads, and proper error handling.
def nextjs_prisma_project():
print("Next.js + Prisma Project Structure:")
print()
print("app/")
print(" dashboard/page.tsx - Server component (direct Prisma)")
print(" files/page.tsx - File list (server component)")
print(" files/actions.ts - Server actions (mutations)")
print(" api/files/route.ts - API routes (external access)")
print("lib/")
print(" prisma.ts - PrismaClient singleton")
print(" validations.ts - Zod schemas")
nextjs_prisma_project()
What's Next
Next: Prisma Testing for testing database code.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro