Next.js + Prisma Komplett-Guide: Von der Konfiguration zur Praxis (inkl. Lösung für Connection-Leaks)

Wieder diese rote Zeile im Terminal: Error: Can't reach database server at localhost:5432. Genauer gesagt: FATAL: sorry, too many clients already.
Die Datenbank läuft, die Connection-String stimmt – und trotzdem keine Verbindung? Am Anfang ging alles, nach ein paar Codeänderungen bricht es zusammen. Next.js-Dev-Server neu starten? Hilft nicht. Datenbank neu starten? Kurz Ruhe, dann wieder dasselbe.
Später wurde klar: Das ist einer der klassischen Stolpersteine bei Next.js + Prisma – Connection-Leaks durch Hot Reload. Bei jeder Codeänderung erzeugt Hot Reload eine neue Prisma-Instanz, alte Verbindungen bleiben offen, und irgendwann ist der Connection Pool voll.
Vielleicht kennen Sie das: Datenbank für ein Next.js-Projekt, Schwanken zwischen Prisma, TypeORM und Drizzle; Prisma gewählt, aber Fehler bei der Konfiguration; unsicher bei Eins-zu-Viele oder Viele-zu-Viele im Schema – oder mitten in der Entwicklung fällt die Datenbank aus.
Prisma ist weniger steil als man denkt, die Konfiguration ist überschaubar. Entscheidend sind: Umgebung aufsetzen, Connection-Leaks vermeiden, Schema entwerfen, CRUD schreiben. Genau das ordnet dieser Artikel – mit weniger Umwegen.
Warum Prisma? (Vergleich mit TypeORM und nativem SQL)
Bei Next.js gibt es viele Datenbankoptionen. TypeORM ist etabliert, Drizzle leichtgewichtig, natives SQL maximal performant. Warum trotzdem Prisma?
Typsicherheit – wirklich sicher
Als ich zum ersten Mal die automatisch generierten TypeScript-Typen von Prisma sah, war ich echt beeindruckt – nicht nur „nett“, sondern „so geht das also“.
Schema schreiben, npx prisma generate ausführen – fertig sind Typdefinitionen für alle Modelle. Keine simplen Interfaces, sondern vollständige Typen mit IntelliSense. Bei prisma.user.findUnique({ where: { id: zeigt VSCode Typ und verfügbare Where-Felder.
Vergleich:
- TypeORM: Viele Decorators (
@Entity(),@Column()), Typen und DB-Schema getrennt – leicht desynchron - Natives SQL: Ergebnisse oft
any, Interfaces von Hand, Schema-Änderungen manuell nachziehen - Prisma: Schema als Single Source of Truth, Typen synchron, nach Schema-Änderung neu generieren
Das ist nicht nur Komfort. Typsicherheit fängt Fehler beim Schreiben ab – nicht erst zur Laufzeit.
Developer Experience – Details zählen
Besonders angenehm finde ich:
Prisma Studio: Visuelles DB-UI – npx prisma studio, Daten im Browser ansehen und bearbeiten, ohne TablePlus oder SQL.
Migrationen: prisma migrate dev erzeugt und wendet Migrationen an – einfacher als bei TypeORM. Schema ändern, Prisma erkennt Diff, fragt nach Migration – kein manuelles SQL-Skript.
Query-Syntax: Prisma fühlt sich nach JavaScript an:
const users = await prisma.user.findMany({
where: { email: { contains: '@gmail.com' } },
include: { posts: true },
orderBy: { createdAt: 'desc' }
})
Intuitiv – kein SQL, keine Decorator-Syntax.
TypeORM QueryBuilder zum Vergleich:
const users = await userRepository.createQueryBuilder("user")
.where("user.email LIKE :email", { email: "%@gmail.com%" })
.leftJoinAndSelect("user.posts", "posts")
.orderBy("user.createdAt", "DESC")
.getMany()
Gleiche Funktion – Prisma klarer und fehlerärmer.
Performance und Ökosystem
„ORM ist langsam – in Produktion natives SQL?“ – ein Mythos. Prisma hat Overhead, für die meisten Projekte vernachlässigbar. Optimierungen:
- N+1: Mit
includewerden Abfragen zusammengeführt - Connection Pool: Sinnvolle Defaults, z. B.
num_cpus * 2 + 1 - Feldauswahl:
selectreduziert übertragene Daten
Engpass? Natives SQL geht auch:
const result = await prisma.$queryRaw`SELECT * FROM User WHERE id = ${userId}`
ORM-Komfort plus SQL dort, wo es zählt.
Ökosystem: 38k+ GitHub-Stars, Vercel-Empfehlung, starke Doku. Next.js-Doku hat eine Prisma-Integration – mainstream-tauglich.
Prisma ist nicht perfekt, aber für die meisten Next.js-Full-Stack-Projekte überwiegen Typsicherheit, DX und Ökosystem.
Umgebung einrichten: Prisma von null konfigurieren
Prisma gewählt – als Nächstes die Umgebung. In etwa zehn Minuten erledigt.
Abhängigkeiten installieren und initialisieren
Next.js-Projekt vorausgesetzt – sonst npx create-next-app@latest.
Prisma installieren:
npm install prisma @prisma/client
Zwei Pakete:
prisma: CLI (Init, Migrationen, Studio)@prisma/client: Client für DB-Zugriff
Initialisieren:
npx prisma init
Erstellt:
prisma/schema.prisma(Kernkonfiguration).envmitDATABASE_URL
Datenbankverbindung konfigurieren
In .env steht typischerweise:
DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"
PostgreSQL-Format:
postgresql://Benutzer:Passwort@Host:Port/Datenbank?schema=public
Lokal empfiehlt sich PostgreSQL – vollständig, beste Prisma-Unterstützung, Produktion oft ebenfalls PostgreSQL.
Kein PostgreSQL? Docker ist am schnellsten:
docker-compose.yml:
version: '3.8'
services:
postgres:
image: postgres:15
restart: always
environment:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypassword
POSTGRES_DB: mydb
ports:
- '5432:5432'
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Starten:
docker-compose up -d
.env anpassen:
DATABASE_URL="postgresql://myuser:mypassword@localhost:5432/mydb?schema=public"
Wichtig: .env nicht committen – in .gitignore:
.env
.env.local
MySQL oder SQLite – leicht andere URLs:
MySQL:
DATABASE_URL="mysql://root:password@localhost:3306/mydb"
SQLite (Datei-DB für kleine Projekte):
DATABASE_URL="file:./dev.db"
Prisma Client generieren
Nach der Verbindung:
npx prisma generate
Liest schema.prisma, erzeugt Typen und Query-Methoden in node_modules/@prisma/client.
Nach jeder Schema-Änderung erneut npx prisma generate – sonst TypeScript-Fehler.
Umgebung steht. Als Nächstes: Connection-Leaks beim Hot Reload – ein Schwerpunkt vieler Fehltritte.
Connection-Leaks durch Hot Reload lösen (Schwerpunkt)
Jetzt das Problem, das viele verzweifeln lässt: FATAL: sorry, too many clients already.
Was passiert?
Im Dev-Modus nutzt Next.js Hot Module Replacement (HMR): Code speichern, Seite aktualisiert sich – ohne Server-Neustart.
Mit Prisma kollidiert das.
Bei jedem Reload werden Module neu geladen. Direktes new PrismaClient() in API Routes oder Server Components → neue Instanz pro Reload.
Jede Instanz öffnet DB-Verbindungen; alte bleiben hängen.
PostgreSQL erlaubt standardmäßig ~100 Verbindungen. Nach einigen Reloads: 10, 20, 50, 100 – voll. too many clients already.
Ich war ratlos – DB lief, kurz nach Neustart wieder kaputt. In Prisma GitHub Issue #10247 berichten viele dasselbe.
Singleton-Muster: die Lösung
Singleton – nur eine PrismaClient-Instanz pro App.
Instanz auf globalThis legen. globalThis überlebt Hot Reload. Erste Instanz wird wiederverwendet, keine neuen Verbindungen.
lib/prisma.ts im Projektroot:
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma || new PrismaClient({
log: ['query', 'error', 'warn'], // Alle Queries in der Entwicklung sichtbar
})
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma
}
Erklärung:
globalForPrismatypisiertglobalThisfür TypeScriptglobalForPrisma.prisma || new PrismaClient()– vorhandene Instanz nutzen oder neu anlegenNODE_ENV !== 'production'– Caching nur in Dev; Produktion hat kein HMR
Überall prisma aus dieser Datei importieren:
App Router (Next.js 13+) Server Component:
// app/users/page.tsx
import { prisma } from '@/lib/prisma'
export default async function UsersPage() {
const users = await prisma.user.findMany()
return (
<div>
{users.map(user => (
<div key={user.id}>{user.name}</div>
))}
</div>
)
}
API Route:
// 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()
return NextResponse.json(users)
}
Pages Router (Next.js 12 und älter):
// pages/api/users.ts
import type { NextApiRequest, NextApiResponse } from 'next'
import { prisma } from '@/lib/prisma'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const users = await prisma.user.findMany()
res.status(200).json(users)
}
Niemals direkt new PrismaClient() – immer aus lib/prisma.ts importieren.
Produktion: kein Problem
Auf Vercel o. Ä. gibt es kein HMR; Builds sind statisch. NODE_ENV !== 'production' sorgt für Singleton in Dev, normales Verhalten in Prod.
Offizielle Prisma-Empfehlung – in der Doku dokumentiert.
Der Stolperstein ist verbreitet, die Lösung simpel: lib/prisma.ts einmal setzen, Leaks vergessen.
Schema-Design – Best Practices
Umgebung steht, Leaks behoben – Tabellen in prisma/schema.prisma definieren.
Basis-Modelle
Blog-Beispiel User und Post:
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
password String
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Wichtige Attribute:
@id: Primärschlüssel@default(autoincrement()): Auto-Increment@unique: Eindeutigkeit (z. B. email)String?: Optional/nullable@default(now()): Zeitstempel bei Erstellung@updatedAt: Automatisches Update-Datum
Namenskonvention:
- Modelle PascalCase:
User,Post,UserProfile - Felder camelCase:
createdAt,authorId - Tabellennamen: Prisma mappt zu Kleinbuchstaben Plural (
User→users)
Beziehungen: Eins-zu-Viele, Viele-zu-Viele, Eins-zu-Eins
Eins-zu-Viele (One-to-Many)
Ein User, viele Posts; ein Post, ein User.
User:posts Post[]Post:author User,authorId Int
@relation(fields: [authorId], references: [id]):
fields: [authorId]– Fremdschlüssel in Postreferences: [id]– referenziert User.id
Viele-zu-Viele (Many-to-Many)
Post mit vielen Tags, Tag in vielen Posts.
Implizit (Prisma verwaltet Zwischentabelle):
model Post {
id Int @id @default(autoincrement())
title String
tags Tag[]
}
model Tag {
id Int @id @default(autoincrement())
name String
posts Post[]
}
Prisma legt _PostToTag an; Abfrage mit include: { tags: true }.
Explizite Zwischentabelle (Extra-Felder möglich):
model Post {
id Int @id @default(autoincrement())
title String
postTags PostTag[]
}
model Tag {
id Int @id @default(autoincrement())
name String
postTags PostTag[]
}
model PostTag {
id Int @id @default(autoincrement())
postId Int
tagId Int
post Post @relation(fields: [postId], references: [id])
tag Tag @relation(fields: [tagId], references: [id])
createdAt DateTime @default(now())
@@unique([postId, tagId])
}
Z. B. createdAt auf der Verknüpfung – sonst reicht implizit.
Eins-zu-Eins (One-to-One)
User mit einem Profile:
model User {
id Int @id @default(autoincrement())
email String @unique
profile Profile?
}
model Profile {
id Int @id @default(autoincrement())
bio String?
userId Int @unique
user User @relation(fields: [userId], references: [id])
}
userId mit @unique – ein Profile pro User.
Fortgeschritten
Enum:
enum Role {
USER
ADMIN
MODERATOR
}
model User {
id Int @id @default(autoincrement())
email String @unique
role Role @default(USER)
}
Zusammengesetzter Unique-Index:
model Post {
id Int @id @default(autoincrement())
title String
authorId Int
slug String
@@unique([authorId, slug])
}
Mehrere Relationen zwischen Modellen – name:
model User {
id Int @id @default(autoincrement())
writtenPosts Post[] @relation("PostAuthor")
favoritePosts Post[] @relation("PostFavorites")
}
model Post {
id Int @id @default(autoincrement())
title String
authorId Int
author User @relation("PostAuthor", fields: [authorId], references: [id])
favoritedBy User[] @relation("PostFavorites")
}
Ohne name ist die Zuordnung mehrdeutig.
Vollständiges Blog-Schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum Role {
USER
ADMIN
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
password String
role Role @default(USER)
posts Post[]
comments Comment[]
profile Profile?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Profile {
id Int @id @default(autoincrement())
bio String?
avatar String?
userId Int @unique
user User @relation(fields: [userId], references: [id])
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id])
comments Comment[]
tags Tag[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Comment {
id Int @id @default(autoincrement())
content String
postId Int
post Post @relation(fields: [postId], references: [id])
authorId Int
author User @relation(fields: [authorId], references: [id])
createdAt DateTime @default(now())
}
model Tag {
id Int @id @default(autoincrement())
name String @unique
posts Post[]
}
npx prisma migrate dev --name init – Tabellen, Indizes, Fremdschlüssel.
Schema-Design: drei Beziehungstypen, @relation, Enums, Unique-Indizes – reicht für die meisten Fälle.
CRUD in der Praxis
Schema steht – Daten per Code. Prisma-API ist selbsterklärend.
Immer prisma aus lib/prisma.ts importieren.
Create
Einzelner Datensatz:
import { prisma } from '@/lib/prisma'
const user = await prisma.user.create({
data: {
email: '[email protected]',
name: 'Alice',
password: 'hashed_password_here'
}
})
Bulk:
const users = await prisma.user.createMany({
data: [
{ email: '[email protected]', name: 'Bob', password: 'pass1' },
{ email: '[email protected]', name: 'Charlie', password: 'pass2' }
],
skipDuplicates: true
})
console.log(`${users.count} Benutzer erstellt`)
Verschachtelt (User + Profile):
const user = await prisma.user.create({
data: {
email: '[email protected]',
name: 'Dave',
password: 'pass',
profile: {
create: {
bio: 'Entwickler mit Leidenschaft für Code'
}
}
},
include: {
profile: true
}
})
Read
Einzeln:
const user = await prisma.user.findUnique({
where: { email: '[email protected]' }
})
const firstPost = await prisma.post.findFirst({
where: { published: true },
orderBy: { createdAt: 'desc' }
})
Mehrere:
const users = await prisma.user.findMany({
where: {
email: {
contains: '@gmail.com'
}
},
orderBy: { createdAt: 'desc' },
take: 10,
skip: 0
})
Relationen:
include:
const user = await prisma.user.findUnique({
where: { id: 1 },
include: {
posts: true,
profile: true
}
})
select (performanter):
const user = await prisma.user.findUnique({
where: { id: 1 },
select: {
id: true,
email: true,
posts: {
select: {
id: true,
title: true
}
}
}
})
include lädt alle Felder; select nur Gewählte – bei großen Datenmengen spürbar.
Filter:
const posts = await prisma.post.findMany({
where: {
OR: [
{ title: { contains: 'Next.js' } },
{ content: { contains: 'Prisma' } }
],
AND: [
{ published: true },
{ authorId: { not: 1 } }
]
}
})
Operatoren: equals, not, in, notIn, contains, startsWith, endsWith, gt/gte, lt/lte.
Update
Einzeln:
const user = await prisma.user.update({
where: { id: 1 },
data: { name: 'Alice Updated' }
})
Mehrere:
const result = await prisma.user.updateMany({
where: { email: { contains: '@gmail.com' } },
data: { role: 'ADMIN' }
})
console.log(`${result.count} Benutzer aktualisiert`)
Upsert:
const user = await prisma.user.upsert({
where: { email: '[email protected]' },
update: { name: 'Alice Updated' },
create: {
email: '[email protected]',
name: 'Alice',
password: 'pass'
}
})
Delete
Einzeln:
const user = await prisma.user.delete({
where: { id: 1 }
})
Mehrere:
const result = await prisma.user.deleteMany({
where: {
createdAt: {
lt: new Date('2023-01-01')
}
}
})
console.log(`${result.count} Benutzer gelöscht`)
Transaktionen
Mehrere Schritte atomar – z. B. Überweisung:
Array-Transaktion:
const [user, post] = await prisma.$transaction([
prisma.user.create({ data: { email: '[email protected]', password: 'pass' } }),
prisma.post.create({ data: { title: 'Test Post', authorId: 1 } })
])
Interaktiv:
const transferMoney = await prisma.$transaction(async (tx) => {
const accountA = await tx.account.update({
where: { id: 1 },
data: { balance: { decrement: 100 } }
})
if (accountA.balance < 0) {
throw new Error('Insufficient balance')
}
const accountB = await tx.account.update({
where: { id: 2 },
data: { balance: { increment: 100 } }
})
return { accountA, accountB }
})
Fehler → Rollback, Salden unverändert.
Vollständiges CRUD-API-Beispiel
// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)
const page = parseInt(searchParams.get('page') || '1')
const limit = parseInt(searchParams.get('limit') || '10')
const posts = await prisma.post.findMany({
where: { published: true },
include: {
author: {
select: { id: true, name: true, email: true }
},
tags: true
},
orderBy: { createdAt: 'desc' },
skip: (page - 1) * limit,
take: limit
})
const total = await prisma.post.count({ where: { published: true } })
return NextResponse.json({ posts, total, page, limit })
} catch (error) {
return NextResponse.json({ error: 'Failed to fetch posts' }, { status: 500 })
}
}
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const { title, content, authorId, tagIds } = body
const post = await prisma.post.create({
data: {
title,
content,
authorId,
tags: {
connect: tagIds.map((id: number) => ({ id }))
}
},
include: { tags: true }
})
return NextResponse.json(post, { status: 201 })
} catch (error) {
return NextResponse.json({ error: 'Failed to create post' }, { status: 500 })
}
}
// app/api/posts/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const post = await prisma.post.findUnique({
where: { id: parseInt(params.id) },
include: {
author: { select: { id: true, name: true } },
tags: true,
comments: {
include: {
author: { select: { id: true, name: true } }
}
}
}
})
if (!post) {
return NextResponse.json({ error: 'Post not found' }, { status: 404 })
}
return NextResponse.json(post)
} catch (error) {
return NextResponse.json({ error: 'Failed to fetch post' }, { status: 500 })
}
}
export async function PATCH(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const body = await request.json()
const post = await prisma.post.update({
where: { id: parseInt(params.id) },
data: body
})
return NextResponse.json(post)
} catch (error) {
return NextResponse.json({ error: 'Failed to update post' }, { status: 500 })
}
}
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
await prisma.post.delete({
where: { id: parseInt(params.id) }
})
return NextResponse.json({ message: 'Post deleted' })
} catch (error) {
return NextResponse.json({ error: 'Failed to delete post' }, { status: 500 })
}
}
Mit create, findUnique, findMany, update, delete, Filtern, Relationen und Transaktionen decken Sie die meisten Szenarien ab.
Fortgeschrittene Tipps und häufige Fragen
CRUD sitzt – in echten Projekten zählen Performance, Migrationen und Debugging.
Performance
select für weniger Felder
Standard: alle Spalten. Bei großen Textfeldern select:
const posts = await prisma.post.findMany({
select: {
id: true,
title: true,
createdAt: true,
author: {
select: { name: true }
}
}
})
Listen brauchen selten vollen content.
N+1 vermeiden
const users = await prisma.user.findMany({
include: { posts: true }
})
Statt Schleife mit Einzelabfragen pro User.
Connection Pool
Default: num_cpus * 2 + 1. Serverless/Hochlast anpassen:
DATABASE_URL="postgresql://user:password@localhost:5432/mydb?connection_limit=5"
Serverless: klein starten (z. B. 1). Auf Vercel: Prisma Accelerate für HTTP-Pooling und Cache.
Migrationen
Entwicklung – nach Schema-Änderung:
npx prisma migrate dev --name add-user-role
Erkennt Diff, erzeugt SQL, wendet an, regeneriert Client. --name beschreibend wählen.
Produktion – nie migrate dev:
npx prisma migrate deploy
Wendet nur vorhandene Migrationen an. In CI/CD vor Deploy ausführen.
Rollback – kein Built-in; npx prisma migrate status, manuelles SQL oder Backup-Restore. Backups in Prod Pflicht.
Debugging
Query-Logs:
export const prisma = new PrismaClient({
log: ['query', 'info', 'warn', 'error']
})
Prod: log: ['error'].
Prisma Studio:
npx prisma studio
Tabellen ansehen, Datensätze bearbeiten, Relationen testen.
Typische Fehler
P2002: Unique constraint failed → Duplikat (z. B. email).
P2025: Record not found → Update/Delete auf fehlenden Datensatz.
P1001: Can't reach database server → URL, DB-Lauf prüfen.
Vercel-Deployment
In package.json:
{
"scripts": {
"build": "prisma generate && next build"
}
}
DATABASE_URL im Vercel-Dashboard – nicht im Code.
PostgreSQL: Vercel Postgres oder Supabase mit Free-Tier.
Kernpunkte:
- Performance:
select,include, Connection Pool - Migrationen: Dev
migrate dev, Prodmigrate deploy - Debug: Logs, Studio, Fehlercodes
- Deploy:
prisma generateim Build, Env-Vars setzen
Fazit
Von Setup über CRUD bis Connection-Leaks und Schema – ein Durchlauf durch Next.js + Prisma.
Noch einmal die wichtigsten Punkte:
Connection-Leaks: lib/prisma.ts mit Singleton, überall importieren – Pflicht in der Entwicklung.
Schema: Eins-zu-Viele, Viele-zu-Viele, Eins-zu-Eins und @relation; einheitliche Namen, Enums nutzen.
CRUD: create, findMany, update, delete; include vs. select; $transaction für Konsistenz.
Performance & Deploy: select sparsam, N+1 vermeiden, Migrationen nach Umgebung trennen, auf Vercel prisma generate.
Prisma ist zugänglicher als rohes SQL, mit starker Typsicherheit und DX – Overhead gibt es, komplexe Queries manchmal natives SQL. Für die meisten Next.js-Full-Stack-Projekte lohnt es sich.
Als Nächstes:
- Kleines Next.js + Prisma-Projekt bauen
- Prisma-Dokumentation für Advanced Features
- Prisma Studio ausprobieren
- GitHub Discussions der Community bei Fragen
Teilen Sie gern Erfahrungen und Stolpersteine in den Kommentaren – anderen hilft das oft weiter.
Next.js + Prisma – vollständiger Konfigurationsablauf
Vom Installieren bis zur Behebung von Connection-Leaks, Schema-Design und CRUD-Operationen
⏱️ Estimated time: 4 hr
- 1
Step 1: Prisma installieren und initialisieren
Abhängigkeiten installieren:
• npm install prisma @prisma/client
• npx prisma init
Die Initialisierung erstellt:
• prisma/schema.prisma: Schema-Definitionsdatei
• .env: Umgebungsvariablen (inkl. DATABASE_URL)
Datenbankverbindung konfigurieren:
• DATABASE_URL in .env setzen
• Format: postgresql://user:password@localhost:5432/dbname - 2
Step 2: Connection-Leak-Problem lösen
Singleton-Muster anlegen:
• Datei lib/prisma.ts erstellen
• In der Entwicklung Instanz über globalThis cachen
• In Produktion Instanz direkt exportieren
Code:
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }
export const prisma = globalForPrisma.prisma || new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
So vermeiden Sie mehrere Verbindungen durch Hot Reload - 3
Step 3: Schema entwerfen
Modelle definieren:
• Tabellen mit model definieren
• Primärschlüssel mit @id markieren
• Standardwerte mit @default setzen
• Beziehungen mit @relation definieren
Beziehungstypen:
• Eins-zu-Eins: @relation(fields, references)
• Eins-zu-Viele: Ein Modell mit @relation, das andere ohne
• Viele-zu-Viele: Zwischentabelle (@relation-Tabelle)
Migration erzeugen:
• npx prisma migrate dev --name init - 4
Step 4: CRUD-Operationen implementieren
Daten anlegen:
• prisma.user.create({ data: { name, email } })
Daten abfragen:
• prisma.user.findMany(): mehrere Datensätze
• prisma.user.findUnique({ where: { id } }): ein Datensatz
• prisma.user.findFirst({ where: { ... } }): erster Treffer
Daten aktualisieren:
• prisma.user.update({ where: { id }, data: { name } })
Daten löschen:
• prisma.user.delete({ where: { id } }) - 5
Step 5: Relationen abfragen
include nutzen:
• prisma.user.findMany({ include: { posts: true } })
• Gibt Benutzer inkl. aller Beiträge zurück
select nutzen:
• prisma.user.findMany({ select: { id: true, name: true } })
• Nur angegebene Felder zurückgeben, weniger Daten
N+1-Problem vermeiden:
• include für Relationen in einer Abfrage
• Keine Relationen in äußeren Schleifen nachladen - 6
Step 6: Deployment und Migrationen
Produktions-Deployment:
• DATABASE_URL als Umgebungsvariable setzen
• npx prisma generate ausführen
• npx prisma migrate deploy für Migrationen
Vercel-Deployment:
• DATABASE_URL im Vercel Dashboard setzen
• prisma generate in den Build-Befehl einbinden
• prisma migrate deploy für Migrationen
Hinweis: migrate dev nicht in Produktion ausführen
FAQ
Warum tritt der Fehler ‚too many clients already‘ auf?
Bei jeder Codeänderung entsteht eine neue PrismaClient-Instanz, alte Verbindungen schließen sich nicht automatisch – der Connection Pool ist irgendwann erschöpft.
Lösung: Singleton-Muster; in der Entwicklung PrismaClient über globalThis cachen.
Was unterscheidet Prisma von TypeORM und Drizzle?
• Beste Typsicherheit, beste Developer Experience
• Etwas Performance-Overhead
TypeORM:
• Mächtig, komplexe Queries möglich
• Konfiguration aufwendiger
Drizzle:
• Leichtgewichtig, gute Performance
• Typsicherheit schwächer als bei Prisma
Für Next.js-Projekte überwiegen bei Prisma Einfachheit und Typsicherheit.
Wie entwirft man eine Eins-zu-Viele-Beziehung?
Beispiel: User hat viele Posts
• Im Post-Modell userId und @relation(fields: ['userId'], references: [id])
• Im User-Modell posts Post[]
Was ist der Unterschied zwischen include und select?
• Für Relationen, liefert verknüpfte Daten
• Erhöht Datenmenge der Abfrage
select:
• Wählt Felder aus, nur angegebene Felder
• Reduziert Datenmenge
Kombinierbar: { include: { posts: true }, select: { id: true, name: true } }
Wie vermeidet man N+1-Abfragen?
Unterstützt Prisma Transaktionen?
Wie deployt man Prisma auf Vercel?
1) DATABASE_URL im Vercel Dashboard setzen
2) prisma generate in package.json build einbinden
3) prisma migrate deploy für Migrationen (nicht migrate dev)
Produktions-Datenbankverbindung prüfen.
13 Min. Lesezeit · Veröffentlicht am: 20. Dez. 2025 · Aktualisiert am: 14. Juli 2026
Next.js Komplettleitfaden
Wenn du über die Suche hier gelandet bist, kommst du am schnellsten weiter, indem du zum vorherigen oder nächsten Beitrag dieser Serie springst.
Vorheriger
Next.js Datenbank-Auswahl: PostgreSQL, MySQL, MongoDB und Cloud-Dienste im Vergleich
Welche Datenbank passt zu Ihrem Next.js-Projekt? Vergleich von PostgreSQL, MySQL und MongoDB sowie Vercel Postgres, Supabase, PlanetScale und MongoDB Atlas – drei Fragen für eine schnelle Entscheidung ohne teure Fehlgriffe.
Teil 22 von 51
Nächster
Next.js State-Management: Zustand vs Jotai – Praxisvergleich und Auswahlhilfe
Redux zu schwergewichtig, Context zu langsam? Dieser Artikel vergleicht Zustand und Jotai in Next.js, liefert eine klare Auswahlhilfe und App-Router-Best-Practices für leichtgewichtige State-Management-Lösungen.
Teil 24 von 51
Ähnliche Beiträge
Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick

Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick
Next.js 15 in der Praxis: Wie ich an einem Wochenende ein produktionsreifes Blog-System aufgebaut habe


Kommentare
Melde dich mit GitHub an, um einen Kommentar zu hinterlassen