Integrazione con Next.js
Prisma e Next.js
Next.js e Prisma sono una combinazione molto popolare. Tuttavia, l’architettura di Next.js (hot-reload in sviluppo, ambienti serverless in produzione) richiede alcune accortezze nella configurazione.
Singleton Pattern per PrismaClient
In sviluppo, Next.js esegue hot-reload ad ogni modifica del codice. Ogni reload crea una nuova istanza di PrismaClient, aprendo nuove connessioni al database fino a esaurire il pool.
Setup Consigliato
Crea un file lib/prisma.ts (o lib/db.ts):
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
const prismaClientSingleton = () => {
return new PrismaClient()
}
declare const globalThis: {
prismaGlobal: ReturnType<typeof prismaClientSingleton>
} & typeof global
const prisma = globalThis.prismaGlobal ?? prismaClientSingleton()
export default prisma
if (process.env.NODE_ENV !== 'production') {
globalThis.prismaGlobal = prisma
}
Ora importa prisma da questo file ovunque nel progetto:
import prisma from '@/lib/prisma'
Uso nelle API Routes
Le API Routes di Next.js sono funzioni serverless. Ogni richiesta puo’ creare una nuova invocazione.
// app/api/users/route.ts
import { NextResponse } from 'next/server'
import prisma from '@/lib/prisma'
export async function GET() {
const users = await prisma.user.findMany({
select: {
id: true,
name: true,
email: true,
_count: { select: { posts: true } },
},
})
return NextResponse.json(users)
}
export async function POST(request: Request) {
const body = await request.json()
try {
const user = await prisma.user.create({
data: {
email: body.email,
name: body.name,
},
})
return NextResponse.json(user, { status: 201 })
} catch (error) {
return NextResponse.json(
{ error: 'Email gia\' in uso' },
{ status: 400 }
)
}
}
Route con Parametri Dinamici
// app/api/users/[id]/route.ts
import { NextResponse } from 'next/server'
import prisma from '@/lib/prisma'
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const user = await prisma.user.findUnique({
where: { id: parseInt(params.id) },
include: {
posts: { where: { published: true } },
profile: true,
},
})
if (!user) {
return NextResponse.json({ error: 'Utente non trovato' }, { status: 404 })
}
return NextResponse.json(user)
}
Server Components (App Router)
Con l’App Router, i componenti sono Server Components di default. Puoi usare Prisma direttamente senza API intermedie:
// app/posts/page.tsx
import prisma from '@/lib/prisma'
export default async function PostsPage() {
const posts = await prisma.post.findMany({
where: { published: true },
include: { author: { select: { name: true } } },
orderBy: { createdAt: 'desc' },
})
return (
<div>
<h1>Blog</h1>
{posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>di {post.author.name}</p>
<p>{post.content}</p>
</article>
))}
</div>
)
}
Pagina con Parametri Dinamici
// app/posts/[slug]/page.tsx
import { notFound } from 'next/navigation'
import prisma from '@/lib/prisma'
export default async function PostPage({
params,
}: {
params: { slug: string }
}) {
const post = await prisma.post.findUnique({
where: { slug: params.slug },
include: {
author: true,
comments: {
orderBy: { createdAt: 'desc' },
include: { author: { select: { name: true } } },
},
},
})
if (!post) notFound()
return (
<article>
<h1>{post.title}</h1>
<p>Autore: {post.author.name}</p>
<div>{post.content}</div>
<h2>Commenti ({post.comments.length})</h2>
{post.comments.map((comment) => (
<div key={comment.id}>
<strong>{comment.author.name}</strong>
<p>{comment.text}</p>
</div>
))}
</article>
)
}
Server Actions
Le Server Actions permettono di eseguire mutazioni lato server direttamente dai form:
// app/posts/new/page.tsx
import prisma from '@/lib/prisma'
import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'
export default function NewPostPage() {
async function createPost(formData: FormData) {
'use server'
const title = formData.get('title') as string
const content = formData.get('content') as string
await prisma.post.create({
data: {
title,
content,
published: false,
authorId: 1, // In un caso reale, dall'autenticazione
},
})
revalidatePath('/posts')
redirect('/posts')
}
return (
<form action={createPost}>
<input name="title" placeholder="Titolo" required />
<textarea name="content" placeholder="Contenuto" required />
<button type="submit">Pubblica</button>
</form>
)
}
Server Action per Toggle
// actions/posts.ts
'use server'
import prisma from '@/lib/prisma'
import { revalidatePath } from 'next/cache'
export async function togglePublish(postId: number) {
const post = await prisma.post.findUniqueOrThrow({
where: { id: postId },
})
await prisma.post.update({
where: { id: postId },
data: { published: !post.published },
})
revalidatePath('/posts')
}
export async function deletePost(postId: number) {
await prisma.post.delete({
where: { id: postId },
})
revalidatePath('/posts')
}
Best Practice per Next.js
1. Non Importare Prisma nei Client Components
// MAI fare questo in un Client Component ('use client')
// import prisma from '@/lib/prisma' // ERRORE!
// Usa sempre API routes o Server Actions per le mutazioni
2. Gestione Errori
// lib/actions.ts
'use server'
import prisma from '@/lib/prisma'
import { Prisma } from '@prisma/client'
export async function createUser(data: { email: string; name: string }) {
try {
const user = await prisma.user.create({ data })
return { success: true, user }
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
if (error.code === 'P2002') {
return { success: false, error: 'Email gia\' registrata' }
}
}
return { success: false, error: 'Errore interno' }
}
}
3. Script postinstall
Aggiungi la generazione automatica del client nel package.json:
{
"scripts": {
"postinstall": "prisma generate"
}
}
Questo assicura che il Prisma Client venga generato automaticamente dopo ogni npm install, anche in ambienti CI/CD e piattaforme di deploy.