TypeScript ORM

Prisma

Prisma ist ein Schema-first ORM für TypeScript. Beschreiben Sie Ihre Modelle in der schema.prisma, generieren Sie einen vollständig typisierten Client und halten Sie die Datenbank mithilfe von Migrationen synchron.

intermediate15 min readUpdated 16. Sept. 2026
prisma/schema.prisma
prisma
// prisma/schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id        String   @id @default(uuid())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  published Boolean  @default(false)
  authorId  String
  author    User     @relation(fields: [authorId], references: [id], onDelete: Cascade)
  createdAt DateTime @default(now())

  @@index([authorId])
}
Veröffentlicht
2019
Aktuelle Hauptversion
6.x
Geschrieben in
TypeScript Client, optional Rust Engine
Schema-Datei
schema.prisma
Datenbanken
PostgreSQL, MySQL, SQLite, SQL Server, MongoDB
Migrationstool
Prisma Migrate

Warum es wichtig ist

Was Prisma unterscheidet

Aus dem Schema abgeleitete Typen

prisma generate erstellt einen Client, dessen Methoden und Rückgabetypen mit Ihren Modellen übereinstimmen, sodass ein falsch geschriebenes Feld zu einem Kompilierfehler wird.

Ein einziges deklaratives Schema

Datasource, Generator und jedes Modell befinden sich in einer einzigen, lesbaren Datei, die sowohl von Menschen als auch von Tools konsumiert werden kann.

Migrationen in der Versionsverwaltung

Prisma Migrate wandelt Schema-Änderungen in überprüfbare SQL-Dateien um, die Sie zusammen mit dem Code committen, der sie benötigt.

Das Gesamtbild

Drei Ideen, die Prisma prägen

Ein deklaratives Schema, ein generierter Client und Migrationen, die in der Versionsverwaltung liegen.

Das Schema

Deklarieren

schema.prisma beschreibt die Datasource, den Generator und jedes Modell mit seinen Feldern, Attributen und Relationen.

Der generierte Client

Typisieren

prisma generate erzeugt den PrismaClient, eine typisierte API, bei der jedes Modell zu einer Eigenschaft wird und jede Abfrage eine bekannte Struktur zurückgibt.

Die Datenbank

Speichern

Prisma übersetzt Client-Aufrufe in SQL und wendet Migrationen an, aber die Datenbank bleibt die Source of Truth für die Daten.

HTML5 auf einen Blick

Die Komponenten, die Sie tatsächlich nutzen werden

Generierter Client

Importieren Sie den PrismaClient und fragen Sie Modelle über typisierte Methoden ab.

Relationen und include

Laden Sie verwandte Datensätze mit include oder wählen Sie spezifische Felder mit select aus.

Prisma Migrate

migrate dev schreibt SQL; migrate deploy wendet es in der Produktion an.

Transaktionen

Sequenzielle $transaction-Arrays oder interaktive Callbacks.

Prisma CLI

generate, migrate, db push, db seed und studio.

Pooling und Accelerate

Optimieren Sie das connection_limit oder schalten Sie einen verwalteten Pooler davor.

Datenmodell

Was die erste Migration erstellt

Die Modelle User und Post werden zu zwei PostgreSQL-Tabellen. Prisma fügt einen Fremdschlüssel für die Relation und einen Unique-Index für das E-Mail-Feld hinzu.

Was die erste Migration erstelltPostgreSQL-Tabellen
  • User.idtextUUID-Primärschlüssel, generiert durch @default(uuid())
  • User.emailtextNOT NULL mit einem Unique-Index durch @unique
  • User.nametextNullable, da das Feld als String? deklariert ist
  • User.createdAttimestamptzNOT NULL, Standardwert now() durch @default(now())
  • Post.idserialAuto-inkrementierender Integer-Primärschlüssel
  • Post.authorIdtextFremdschlüssel zu User.id mit ON DELETE CASCADE

Die Modelle User und Post werden zu zwei PostgreSQL-Tabellen. Prisma fügt einen Fremdschlüssel für die Relation und einen Unique-Index für das E-Mail-Feld hinzu.

Eine kurze Geschichte

Vom GraphQL-Backend zum Mainstream-ORM

  1. 2016

    Graphcool und ein GraphQL-Backend

    Das Projekt, aus dem Prisma hervorgeht, beginnt als gehosteter GraphQL-Layer über einer Datenbank.

    16
  2. 2019

    Prisma 1

    Das erste ORM-Release sitzt vor der Datenbank und stellt den Clients eine GraphQL-API zur Verfügung.

    19
  3. 2020

    Prisma 2 geht in GA

    Ein Rewrite macht das Schema zur Source of Truth und führt den generierten, typsicheren Prisma Client ein.

    20
  4. 2021

    Prisma Migrate erreicht GA

    Deklarative Migrationen und Seeding werden zu produktionsreifen Teilen des Toolkits.

    21
  5. 2024

    Ein Rust-freier Client

    Der prisma-client Generator und Driver-Adapter verschieben mehr Teile des Stacks nach TypeScript.

    24

Der vollständige Leitfaden

Prisma: Alles was Sie wissen müssen

Was ist Prisma?

Prisma ist ein schema-first ORM für TypeScript und Node.js. Du beschreibst deine Daten einmal in schema.prisma, führst einen Generator aus und erhältst einen Client, dessen Methoden und Rückgabetypen direkt aus diesem Schema abgeleitet werden. Es gibt keine Decorators, keine Repository-Klassen und in den gängigen Anwendungsfällen keine handgeschriebenen SQL-Strings.

Das Konzept dahinter ist, dass das Datenbankschema zu einer einzigen deklarativen Datei wird, die von Menschen gelesen und von Tools verarbeitet wird. Daraus generiert Prisma einen typisierten Client, den du in deinen Code importierst, SQL-Migrationen, die du prüfen und committen kannst, sowie eine Studio-UI zum Durchsuchen der Daten. Prisma unterstützt PostgreSQL, MySQL, MariaDB, SQLite, SQL Server, CockroachDB und MongoDB. Der generierte Client übersetzt jeden Aufruf in SQL oder das MongoDB-Wire-Protokoll, bevor die Zeilen wieder in einfache Objekte gemappt werden.

Wenn du bereits ein ORM im ActiveRecord-Stil verwendet hast, besteht der konzeptionelle Unterschied darin, dass Prisma schema-first und client-generated ist, anstatt class-first und runtime-reflective. Diese eine Entscheidung erklärt die meisten seiner Stärken sowie die meisten seiner Nachteile.

Das Schema ist die Source of Truth

Alles beginnt in schema.prisma. Es enthält drei Arten von Blöcken: einen datasource (wo die Datenbank liegt), einen generator (was ausgegeben werden soll) und einen model pro Tabelle.

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id        String   @id @default(uuid())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

Einige Konventionen sollte man verinnerlichen. Ein Feld, das auf ? endet, ist nullable, während ein List-Typ wie Post[] eine Relation und keine Spalte darstellt. Attribute beginnen mit @ für Felder und @@ für Blöcke. @id markiert den Primary Key, @unique erstellt einen Unique Index, @default(...) liefert einen Standardwert, und @map sowie @@map benennen die zugrunde liegende Spalte oder Tabelle um, falls diese nicht mit deinem Modell übereinstimmt.

Das obige Modell wird auf eine User-Tabelle mit den Spalten id, email, name und createdAt abgebildet, plus eine virtuelle posts-Relation, die Prisma über einen Join oder eine zweite Abfrage auflöst.

Den Client generieren und verwenden

Der Client von Prisma besteht aus generiertem Code. Führen Sie nach der Bearbeitung des Schemas folgenden Befehl aus:

pnpm prisma generate

Dies liest schema.prisma aus und schreibt einen Client in node_modules/.prisma/client oder in einen von Ihnen gewählten Ordner mit dem neueren prisma-client-Generator. Anschließend instanziieren Sie diesen einmal und verwenden ihn wiederholt:

import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

const user = await prisma.user.findUnique({
  where: { email: "[email protected]" },
});

Da der Client generiert wird, sind sowohl prisma.user als auch die Struktur von user dem Type Checker bekannt. Wenn Sie ein Feld im Schema umbenennen und den Client neu generieren, schlägt die Kompilierung bei jeder veralteten Verwendung fehl. Dieser Feedback-Loop ist der Hauptgrund, warum Teams Prisma einsetzen.

Vermeiden Sie in Serverless- oder Hot-Reloading-Umgebungen die Erstellung eines neuen Clients pro Request oder pro Reload. Hängen Sie stattdessen einen Client an das globale Objekt an:

import { PrismaClient } from "@prisma/client";

const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };

export const prisma = globalForPrisma.prisma ?? new PrismaClient();

if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;

Daten lesen

Prisma stellt für jede Lese-Struktur eine eigene Methode bereit. findUnique ruft eine einzelne Zeile über ein eindeutiges Feld ab, findFirst ruft eine Zeile ab, die einem beliebigen Filter entspricht, und findMany gibt eine Liste zurück.

const post = await prisma.post.findUnique({ where: { id: 42 } });

const latest = await prisma.post.findFirst({
  where: { published: true },
  orderBy: { createdAt: "desc" },
});

const posts = await prisma.post.findMany({
  where: {
    published: true,
    title: { contains: "prisma", mode: "insensitive" },
    authorId: { in: [userId] },
  },
  orderBy: { createdAt: "desc" },
  take: 20,
  skip: 0,
});

Filter-Operatoren sind benannt statt symbolisch: equals, not, in, notIn, lt, lte, gt, gte, contains, startsWith, endsWith und mode. Kombinieren Sie diese mit AND, OR und NOT-Arrays:

const posts = await prisma.post.findMany({
  where: {
    OR: [
      { title: { contains: "orm" } },
      { author: { email: { endsWith: "@example.com" } } },
    ],
    NOT: { published: false },
  },
});

take und skip implementieren Offset-Pagination. Bei großen Ergebnismengen ist die Cursor-Pagination vorzuziehen, da diese ab der letzten Zeile paginiert, anstatt vom Anfang an zu zählen:

const page = await prisma.post.findMany({
  take: 20,
  skip: 1,
  cursor: { id: lastSeenId },
  orderBy: { id: "asc" },
});

findUnique akzeptiert kein nicht-eindeutiges where; verwenden Sie dafür findFirst. Die Varianten findUniqueOrThrow und findFirstOrThrow werfen einen Fehler, anstatt null zurückzugeben, was einen Conditional-Branch überflüssig macht, wenn die Zeile zwingend existieren muss.

Daten schreiben

Erstellen, Aktualisieren und Löschen sind gleichermaßen typisiert. create, update, upsert und delete operieren auf einer einzelnen Zeile, während createMany, updateMany und deleteMany auf Datensätzen operieren.

const user = await prisma.user.create({
  data: { email: "[email protected]", name: "Ada" },
});

await prisma.user.update({
  where: { id: user.id },
  data: { name: "Ada Lovelace" },
});

await prisma.user.upsert({
  where: { email: "[email protected]" },
  update: { name: "Ada Lovelace" },
  create: { email: "[email protected]", name: "Ada" },
});

await prisma.user.delete({ where: { id: user.id } });

Für Bulk-Inserts führt createMany ein einzelnes INSERT aus und ist dramatisch schneller als eine Schleife über create:

await prisma.post.createMany({
  data: [
    { title: "Hello", authorId: user.id },
    { title: "World", authorId: user.id },
  ],
  skipDuplicates: true,
});

Der Kompromiss besteht darin, dass createMany keine verschachtelten Relationen schreiben kann; es ist nur für flache Zeilen gedacht. updateMany und deleteMany akzeptieren dieselben Filter wie findMany, sodass ein fehlendes where tatsächlich jede Zeile betrifft.

Relations, include und select

Relations werden auf beiden Seiten definiert. User.posts ist eine Liste und Post.author ist ein Einzelwert, die auf der besitzenden Seite durch @relation(fields: [authorId], references: [id]) miteinander verknüpft sind.

Standardmäßig gibt Prisma nur skalare Spalten zurück. Um eine Relation zu laden, fügst du include hinzu:

const user = await prisma.user.findUnique({
  where: { id: userId },
  include: {
    posts: {
      where: { published: true },
      orderBy: { createdAt: "desc" },
      take: 10,
    },
  },
});

select ist das präzisere Werkzeug: Damit legst du exakt fest, welche Felder zurückgegeben werden sollen – sowohl für das Modell als auch für verschachtelte Relations.

const users = await prisma.user.findMany({
  select: {
    id: true,
    email: true,
    posts: {
      select: { title: true },
      where: { published: true },
    },
    _count: { select: { posts: true } },
  },
});

Du kannst select und include nicht auf derselben Ebene kombinieren, da select bereits die Frage beantwortet, was zurückgegeben werden soll. Nutze select, wenn ein Endpoint eine feste Antwortstruktur hat, und include, wenn du tatsächlich den gesamten verknüpften Datensatz benötigst. Ganze Zeilen zurückzugeben und diese erst in JavaScript zu filtern, verschwendet Bandbreite und Speicher; select verhindert hier eine häufige Performance-Verschlechterung.

Verschachtelte Schreibvorgänge (Nested Writes)

Eines der nützlichsten Features von Prisma ist die Möglichkeit, einen Parent und dessen Children in einem einzigen Aufruf zu schreiben. Die verschachtelten Operationen create, connect, update und delete werden alle innerhalb einer impliziten Transaction ausgeführt.

const user = await prisma.user.create({
  data: {
    email: "[email protected]",
    posts: {
      create: [
        { title: "First post" },
        { title: "Second post", published: true },
      ],
    },
  },
  include: { posts: true },
});

Um bestehende Zeilen zu verbinden, anstatt sie neu zu erstellen, wird connect verwendet; zum Trennen einer Relation nutzt man disconnect:

await prisma.post.update({
  where: { id: postId },
  data: {
    author: { connect: { email: "[email protected]" } },
  },
});

Verschachtelte Schreibvorgänge halten verwandte Daten konsistent, ohne dass Sie manuell eine Transaction öffnen müssen – genau die Art von Bookkeeping, die ein ORM übernehmen sollte.

Migrationen in der Entwicklung und Produktion

Prisma Migrate wandelt die Unterschiede zwischen deinem Schema und deiner Datenbank in versionierte SQL-Dateien unter prisma/migrations um.

pnpm prisma migrate dev --name add_posts
pnpm prisma migrate deploy
pnpm prisma migrate status

migrate dev ist ein Befehl für die Entwicklung. Er vergleicht das Schema mit der Datenbank, schreibt eine neue Migration, wendet diese an und regeneriert den Client. Zudem wird eine Shadow Database verwendet, um Drift zu erkennen; daher benötigt die Entwickler-Rolle die Berechtigung, Datenbanken zu erstellen und zu löschen. Wenn Drift festgestellt wird, kann ein Reset der Datenbank angeboten werden, wodurch Daten gelöscht werden.

migrate deploy ist der Befehl für die Produktion. Er wendet ausstehende Migrationen an und sonst nichts: keine Generierung, keine Shadow Database, keine Resets. Führe diesen Befehl in deiner Deploy-Pipeline aus, bevor der neue Code Traffic empfängt.

Für schnell zusammengestellte Prototypen überspringt prisma db push die Migrationshistorie komplett und erzwingt, dass die Datenbank dem Schema entspricht:

pnpm prisma db push

db push ist schnell und bequem, hinterlässt jedoch keinen Audit-Trail und kann Spalten oder Tabellen löschen, um das Schema anzupassen. Richte diesen Befehl niemals auf die Produktion. Weitere nützliche Befehle sind prisma migrate reset, um eine Entwicklungsdatenbank zu löschen, neu zu erstellen und neu zu seeden, sowie prisma migrate diff, um anzuzeigen, was geändert würde, ohne die Änderungen tatsächlich anzuwenden.

Seeding

Seed-Daten gehören in prisma/seed.ts. Registriere sie in package.json, damit Prisma weiß, wie sie auszuführen sind:

{
  "prisma": {
    "seed": "tsx prisma/seed.ts"
  }
}
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

async function main() {
  await prisma.user.upsert({
    where: { email: "[email protected]" },
    update: {},
    create: {
      email: "[email protected]",
      name: "Ada",
      posts: { create: [{ title: "Welcome" }] },
    },
  });
}

main()
  .then(() => prisma.$disconnect())
  .catch(async (error) => {
    console.error(error);
    await prisma.$disconnect();
    process.exit(1);
  });

Führe sie mit pnpm prisma db seed aus. Da das Seeding nach migrate dev und migrate reset erfolgt, ist eine frisch erstellte Datenbank niemals leer, was das Onboarding und die Tests vorhersehbar macht.

Transaktionen

Prisma bietet zwei Transaction-APIs. Die sequentielle Form nimmt ein Array von Operationen entgegen und führt diese nacheinander aus:

const [debit, credit] = await prisma.$transaction([
  prisma.account.update({
    where: { id: 1 },
    data: { balance: { decrement: 5000 } },
  }),
  prisma.account.update({
    where: { id: 2 },
    data: { balance: { increment: 5000 } },
  }),
]);

Die interaktive Form stellt einen Transaction-Client bereit und ermöglicht es, basierend auf den Ergebnissen zu verzweigen:

await prisma.$transaction(async (tx) => {
  const sender = await tx.account.findUniqueOrThrow({ where: { id: 1 } });
  if (sender.balance < 5000) throw new Error("insufficient_funds");

  await tx.account.update({
    where: { id: 1 },
    data: { balance: { decrement: 5000 } },
  });
  await tx.account.update({
    where: { id: 2 },
    data: { balance: { increment: 5000 } },
  });
});

Verwenden Sie tx für jede Abfrage innerhalb einer interaktiven Transaktion; die Verwendung des äußeren prisma-Clients würde die Transaktion verlassen. Sie können das Verhalten mit maxWait anpassen, welches steuert, wie lange auf eine Verbindung gewartet wird, und mit timeout, welches die maximale Laufzeit der Transaktion begrenzt. Das Isolation Level lässt sich über isolationLevel festlegen. Halten Sie Transaktionen kurz und lassen Sie diese niemals über einen HTTP-Aufruf oder eine Benutzerinteraktion hinweg offen.

Raw SQL, wenn es nötig ist

Der Query Builder deckt die meisten Lesezugriffe ab, aber für Reporting-Abfragen und datenbankspezifische Features ist manchmal SQL erforderlich. $queryRaw ist ein Tagged Template, das Werte automatisch für Sie parametrisiert:

import { Prisma } from "@prisma/client";

const rows = await prisma.$queryRaw<
  { day: Date; revenue: bigint }[]
>(Prisma.sql`
  SELECT date_trunc('day', created_at) AS day,
         sum(total_cents)             AS revenue
  FROM orders
  WHERE status = 'paid'
  GROUP BY 1
  ORDER BY 1 DESC
`);

Verwenden Sie $queryRaw für SELECT und $executeRaw für Schreibvorgänge. Beide akzeptieren Tagged Templates, welche SQL-Injection verhindern, da interpolierte Werte zu Bound-Parametern werden. $queryRawUnsafe und $executeRawUnsafe existieren für wirklich dynamisches SQL und sollten als gefährlich eingestuft werden: Interpolieren Sie niemals Benutzereingaben in diese Funktionen. Prisma.sql und Prisma.join ermöglichen es Ihnen, Fragmente zu kombinieren und dabei die Parametrisierung beizubehalten.

Pooling, Serverless und Accelerate

Jede PrismaClient-Instanz besitzt einen eigenen Connection Pool. Auf einem Long-lived-Server ist genau das gewünscht. Auf einer Serverless-Plattform erstellt jedoch jede Funktionsinstanz ihren eigenen Client und damit ihren eigenen Pool. Ein Traffic-Spike kann so die max_connections der Datenbank innerhalb von Sekunden erschöpfen.

Der erste Hebel ist der Connection String. Begrenzen Sie den Pool und die Wartezeit:

DATABASE_URL="postgresql://user:pass@host:5432/shop?connection_limit=5&pool_timeout=10"

In Funktionen, in denen jede Instanz jeweils nur einen Request bearbeitet, ist connection_limit=1 oft die richtige Wahl. Wenn Sie PgBouncer verwenden, fügen Sie ?pgbouncer=true hinzu, damit Prisma auf Prepared Statements verzichtet, da diese von Transaction Pooling nicht beibehalten werden können. Falls Sie die Connection Limits der Datenbank nicht ändern können, fungiert Prisma Accelerate als managed Pooler und Cache vor der Datenbank; Sie erweitern den Client mit der entsprechenden Extension und routen die Queries über einen globalen Endpoint. Der ältere Data Proxy löste dasselbe Problem und wurde durch Accelerate ersetzt.

Unabhängig von Ihrer Wahl gilt die gleiche Regel: Verwenden Sie pro Prozess nur einen Client und achten Sie darauf, dass die Anzahl der Applikationsinstanzen nicht zu mehr Verbindungen führt, als die Datenbank verarbeiten kann.

Die N+1-Frage

Ein N+1-Problem tritt auf, wenn Sie eine Liste abrufen und anschließend für jede Zeile eine eigene Abfrage ausführen, um eine Relation zu laden. Prisma vermeidet die klassische Form dieses Problems, da include und select Relationen als Teil desselben Aufrufs laden: Für ein findMany mit einer Relation wird eine Abfrage für die Eltern und eine für die Kinder ausgeführt, nicht eine pro Elternelement.

Sie führen das Problem selbst wieder ein, wenn Sie eine Schleife verwenden:

const users = await prisma.user.findMany();

for (const user of users) {
  // One query per user: this is the N+1.
  const posts = await prisma.post.findMany({ where: { authorId: user.id } });
}

Laden Sie die Relation stattdessen direkt in der ursprünglichen Abfrage:

const users = await prisma.user.findMany({
  include: { posts: true },
});

Bei tief verschachtelten Lesezugriffen führt die Standardstrategie von Prisma eine Abfrage pro Relationsebene aus. In Fällen, in denen eine einzelne Joined-Query spürbar schneller ist, weist die Option relationLoadStrategy: "join" Prisma an, stattdessen einen LEFT JOIN zu verwenden. Messen Sie die Performance, bevor Sie wechseln, da die beiden Strategien einen Kompromiss zwischen der Anzahl der Round-Trips und duplizierten Elternspalten darstellen.

Best Practices

  • Betrachten Sie schema.prisma als die “Source of Truth” und ändern Sie diese ausschließlich über Migrationen, niemals manuell.
  • Instanziieren Sie PrismaClient einmal pro Prozess und verwenden Sie diese Instanz wieder; schützen Sie den Hot Reload mit einem globalen Objekt.
  • Verwenden Sie select für feste Response-Strukturen und include nur dann, wenn Sie vollständige Datensätze benötigen.
  • Bevorzugen Sie Cursor-Pagination gegenüber großen skip-Offsets.
  • Nutzen Sie createMany für Bulk-Inserts und Nested Writes für verwandte Zeilen.
  • Halten Sie interaktive Transaktionen kurz und verwenden Sie innerhalb dieser immer den tx-Client.
  • Greifen Sie auf $queryRaw mit Tagged Templates zurück – niemals auf die Unsafe-Varianten –, wenn Sie SQL benötigen.
  • Setzen Sie connection_limit und nutzen Sie in Serverless-Umgebungen einen Pooler wie Accelerate oder PgBouncer.
  • Committen Sie die Migrations-Ordner und führen Sie migrate deploy in der CI/CD aus, anstatt db push.

Häufige Fehler

  • Ausführen von prisma db push gegen die Production-Umgebung und dabei Spalten verlieren.
  • Erstellen eines neuen PrismaClient pro Request, was zur Erschöpfung der Verbindungen führt.
  • Vergessen von prisma generate nach einer Schema-Änderung und sich anschließend wundern, warum die Typen veraltet sind.
  • Abrufen vollständiger Zeilen mit include und anschließendes Filtern der Felder in JavaScript.
  • Verwendung des äußeren Clients innerhalb einer interaktiven Transaction, wodurch die Transaction stillschweigend verlassen wird.
  • Ignorieren von where bei updateMany oder deleteMany und damit das Aktualisieren jeder einzelnen Zeile.
  • Die Annahme, dass findUnique jeden beliebigen Filter akzeptiert; es wird ein eindeutiges Feld benötigt.
  • Interpolieren von Benutzereingaben in $queryRawUnsafe.
  • Offenhalten einer Transaction während eines awaiteten externen API-Aufrufs.

Wie geht es weiter?

Prisma ist eine Lösung dafür, wie man von TypeScript aus mit einer relationalen Datenbank kommuniziert. Vergleiche es mit Drizzle, bei dem SQL sichtbar bleibt und auf den generierten Client verzichtet wird, sowie mit TypeORM, dem Klassen- und Decorator-Ansatz, der beide bereits überholt hat. Unter all diesen Tools liegt die Datenbank selbst, daher ist der PostgreSQL-Guide ein tieferer Einblick, der sich unabhängig vom gewählten ORM auszahlt. Wenn du dich noch nicht für eine Runtime für den Server entschieden hast, auf dem das alles läuft, beginne mit Node.js.

In der Praxis

Schema, Abfrage, verschachteltes Schreiben, Transaktion

Die vier Strukturen, die Sie in einem Prisma-Projekt am häufigsten schreiben.

prisma/schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id        String   @id @default(uuid())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  published Boolean  @default(false)
  authorId  String
  author    User     @relation(fields: [authorId], references: [id], onDelete: Cascade)
  createdAt DateTime @default(now())

  @@index([authorId])
}

Nur das Notwendige abrufen

select wählt exakte Felder für die Antwort aus, während include ganze verwandte Zeilen lädt, die man eventuell später verwirft.

Bevorzugt
const emails = await prisma.user.findMany({
  select: { email: true },
});
Vermeiden
const users = await prisma.user.findMany({
  include: { posts: true },
});

// Most of each row is discarded in JavaScript.
const emails = users.map((u) => u.email);

Migration vs. Prototyping

migrate dev schreibt versioniertes SQL, das Sie prüfen und deployen können. db push erzwingt das Schema ohne Historie und ist für Wegwerf-Datenbanken gedacht.

Bevorzugt
pnpm prisma migrate dev --name add_posts
pnpm prisma migrate deploy
Vermeiden
# No migration files, no audit trail,
# and columns can be dropped to fit.
pnpm prisma db push

Abwägungen

Sollte Prisma Ihr ORM sein?

Prisma optimiert auf Sicherheit und Produktivität. Dieser Kompromiss lohnt sich für die meisten Anwendungscodes, weniger jedoch, wenn Sie feinere Kontrolle benötigen.

Strengths

  • Produktiv ab dem ersten Modell

    Das Schema, der generierte Client und das Migrationstool teilen sich ein mentales Modell, sodass kaum Glue-Code geschrieben werden muss.

  • Vom Compiler erzwungene Sicherheit

    Typen werden aus dem Schema abgeleitet, wodurch umbenannte Felder, falsche Argumentstrukturen und fehlende Relationen bereits vor der Laufzeit erkannt werden.

  • Integrierte Migrationen und Seeding

    Prisma Migrate, Seed-Skripte und Prisma Studio decken die tägliche Datenbankarbeit ohne zusätzliche Bibliotheken ab.

Trade-offs

  • Weniger Kontrolle über das SQL

    Sie tauschen einen Teil der Abfragekontrolle gegen die Abstraktion ein. Komplexe Reports erfordern oft Raw SQL, wo die Typsicherheit schwächer ist.

  • Ein generierter Client ist ein Build-Schritt

    Eine Änderung am Schema erfordert eine Neugenerierung; wird dies in der CI oder bei einem frischen Clone vergessen, entstehen verwirrende Typfehler.

  • Serverless erfordert Aufmerksamkeit

    Ein Client pro Funktionsinstanz vervielfacht die Verbindungen, daher benötigen Serverless-Deployments Connection-Limits oder einen Pooler wie Accelerate.

Häufig gestellte Fragen

Häufig gestellte Fragen

Keep learning

Related topics from the roadmap.

$ Lernen Sie jetzt

Bereit, Prisma zu lernen?

Unser interaktives Tutorial führt Sie Schritt für Schritt durch Prisma — mit Quizzen und echtem Code, den Sie im Browser ausführen können.