Startseite / Artikel

Prisma ohne Prisma Client: Typsichere Abfragen in Next.js

Erfahren Sie, wie Sie Prisma für Schema-Arbeiten nutzen, Kysely-Typen mit prisma-kysely erzeugen, einen Neon- oder pg-Dialekt auswählen sowie sicher typbasiert abfragen können – direkt aus dem Servercode von Next.js.

Verlag: REACTAPP.TOP6 Min. Lesezeit1411 Wörter

Quellen: **Using Prisma for Schema and Kysely for Queries in a Next.js App**

Prisma eignet sich hervorragend für das Schema-Design, doch der Prisma Client stellt eine erhebliche Abhängigkeit dar, die in jede serverlose Funktion eingebunden werden muss – obwohl man eigentlich nur type-sichere SQL-Anfragen benötigt. Eine praktische Aufteilung sieht so aus: Prisma verwaltet das Schema während der Entwicklung, ein Generator wandelt es in TypeScript-Typen um, und Kysely führt die Abfragen in der Produktion aus. Diese Anleitung zeigt, wie man dies in einem Next.js App Router-Projekt einrichtet, und vergleicht den Neon HTTP-Dialekt mit einem klassischen pg-Pool.

Falls Sie stattdessen den herkömmlichen Ansatz mit dem Prisma Client zur Laufzeit bevorzugen, lesen Sie die Anleitung zur Einrichtung von Prisma 7 in einem Next.js 16-Projekt.

Wie sich die Aufgaben aufteilen

Jedes Tool in dieser Konfiguration hat eine klare Aufgabe:

  • Prisma ist für das Datenbankdesign verantwortlich: das Schema-File, die Synchronisierung oder Migration der Datenbank sowie Tools wie Prisma Studio zum Durchsuchen der Daten.
  • Kysely ist der Laufzeit-Abfrageschreiber. Sie schreiben abfragen im SQL-Stil in TypeScript, der Compiler überprüft die Tabellen- und Spaltennamen, und die Bibliothek selbst ist klein.
  • prisma-kysely dient als Verbindungsstück: ein Prisma-Generator, der direkt aus Ihrem Prisma-Schema Kysely-Typdefinitionen erzeugt.

Prisma bleibt im Entwicklungsworkflow, während Kysely die einzige Datenbankschicht im bereitgestellten Code ist. Der Kompromiss: Man verliert die relationenbasierte API des Prisma Clients, wie beispielsweise verschachtelte include-Anfragen und verschachtelte Schreibvorgänge, sowie die Möglichkeit, selbst Join-Operationen durchzuführen. Wenn Sie noch allgemein eine Datenbankschicht auswählen müssen, behandelt der Vergleich von raw SQL, Prisma und Drizzle die umfassendere Entscheidungsfindung.

Installation der Pakete

Der Laufzeitumgebung sind Kysely zusammen mit einem Dialekt und einem Treiber erforderlich; dadurch wird der auf Neon basierende Stack installiert (hier pnpm, aber auch npm oder yarn funktionieren):

pnpm add kysely kysely-neon @neondatabase/serverless
pnpm add -D prisma prisma-kysely

Beachten Sie, dass prisma und prisma-kysely Entwicklungskomponenten sind: Sie verwalten lediglich das Schema und erzeugen Code.

Definieren des Schemas und Erstellen von Kysely-Typen

Das Schema ist ein gewöhnliches Prisma-Schema, mit dem Unterschied, dass der Generator prisma-kysely ist (der in ./types schreibt) anstelle des üblichen Client-Generators. Es definiert User und Post mit einer eins-zu-vielen-Beziehung.

// db/schema.prisma

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

generator kysely {
  provider = "prisma-kysely"
  output   = "./types"
}

model User {
  id    Int    @id @default(autoincrement())
  name  String
  email String  @unique
  posts Post[]
}
model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  authorId  Int
  createdAt DateTime @default(now())
  author    User     @relation(fields: [authorId], references: [id])
}

prisma generate erzeugt nun Kysely-Typen anstelle eines Clients, und prisma db push wendet das Schema auf die Datenbank an:

pnpm prisma generate
pnpm prisma db push

Die Ausgabe ist eine Datei wie db/types/kysely.d.ts mit einer DB-Schnittstelle, die jede Tabelle beschreibt. Drei Hinweise:

  • Das Schema befindet sich in db/schema.prisma, nicht in Prismas Standard-Datei prisma/schema.prisma; daher muss die CLI darauf verweisen (zum Beispiel mit --schema), da Ihre Prisma-Version dies erfordert.
  • db push synchronisiert die Datenbank, ohne Migrationsdateien zu erstellen. Für Produktionsdaten liefern prisma migrate dev und prisma migrate deploy versionierte Migrationsdateien, wobei der Rest des Workflows unverändert bleibt.
  • In neueren Prisma-Versionen werden die Verbindungs-Einstellungen in eine separate Konfigurationsdatei verschoben; falls die url-Zeile abgelehnt wird, sollten Sie die aktuellen Dokumentationen prüfen.
  • Paket-Skripte für den täglichen Ablauf

    Mehrere Skripte machen den Workflow für das Team verständlich:

    {
      "scripts": {
        "db:push": "prisma db push",
        "db:generate": "prisma generate",
        "db:studio": "prisma studio"
      }
    }
    

    Mit diesen Skripten reduziert sich eine Schema-Änderung auf zwei Befehle:

    # To generate the types
    pnpm db:generate
    # To push to the remote DB
    pnpm db:push
    

    Dadurch bleiben die Datenbank und die Kysely-Typen übereinstimmen, sodass der Compiler alle Abfragen meldet, die eine umbenannte oder entfernte Spalte verwenden.

    Auswahl eines Kysely-Dialekts

    Kysely spricht nicht direkt mit der Datenbank selbst. Ein Dialekt befindet sich zwischen dem Abfrageschreiber und dem Treiber und weiß, wie Abfragen für eine bestimmte Datenbank sowie Verbindungsart kompiliert und gesendet werden. Offizielle Dialekte umfassen PostgreSQL, MySQL und SQLite; Community-Dialekte decken Dienste wie Neon, PlanetScale und Turso ab. Für Postgres reichen zwei Optionen in den meisten Fällen aus.

    Neon über HTTP für serverlose Implementierungen

    Auf Vercel oder einer ähnlichen Plattform passt Neons HTTP-Treiber gut: Jede Abfrage ist eine stateless-Anfrage, sodass zwischen kurzen Aufrufen keine persistente Verbindung aufrechterhalten werden muss. Die Einrichtung umhüllt neon() mit NeonDialect und typisiert Kysely mit dem generierten Typ DB:

    // db/index.ts
    import { Kysely } from 'kysely'
    import { NeonDialect } from 'kysely-neon'
    import { neon } from '@neondatabase/serverless'
    import type { DB } from './types/kysely'
    
    const db = new Kysely<DB>({
      dialect: new NeonDialect({
        neon: neon(process.env.DATABASE_URL!),
      }),
    })
    
    export default db
    

    Der ! schweigt TypeScript lediglich; er überprüft nichts. Die größere Einschränkung besteht darin, dass kysely-neon über HTTP weder Transaktionen noch abfragebasierte Abläufe durchführen kann, weshalb es eher für einfache CRUD-Aufgaben und lesungsintensive Seiten geeignet ist als für Aufgaben mit mehreren Anweisungen. Mit Neons WebSocket-Driver benötigen Sie kysely-neon überhaupt nicht: Übergeben Sie Neons Pool an Kyselys eingebauten PostgresDialect. Die API von kysely-neon hat sich zudem zwischen den Versionen geändert, daher sollten Sie sie mit der aktuellen README-Vorlage vergleichen.

    node-postgres für langlaufende Server

    Für einen langzeit laufenden Node.js-Server ist der offizielle pg-Driver die vollständigste Wahl, da er Transaktionen, Pools sowie vorbereitete Anweisungen bietet. Er integriert sich in Kyselys eigenen PostgresDialect:

    // db/index.ts
    import { Kysely, PostgresDialect } from 'kysely'
    import { Pool } from 'pg'
    import type { DB } from './types/kysely'
    
    const db = new Kysely<DB>({
      dialect: new PostgresDialect({
        pool: new Pool({
          connectionString: process.env.DATABASE_URL,
        }),
      }),
    })
    
    export default db
    

    Auswahl zwischen ihnen

    Der Unterschied liegt im Verbindungsmodell. kysely-neon ist ein stateless HTTP-Protokoll: leichtgewichtig und serverlos-freundlich, bietet aber keine Transaktionen. pg hält gepoolte TCP-Verbindungen offen und verfügt über das volle Funktionsset, was sich für ständig laufende Server eignet. Verwenden Sie kysely-neon auf serverlosen Plattformen wie Vercel oder Netlify sowie pg oder den Neon WebSocket-Pool, wenn Sie Transaktionen benötigen. Achten Sie darauf, keinen pg-Pool innerhalb serverloser Funktionen zu verwenden: Jede Instanz öffnet eigene Verbindungen, was ohne externe Pooler die Datenbankgrenzen überschreiten kann.

    Aufrufe von einem Server-Komponenten aus

    Jeder serverseitige Code kann db importieren. Diese Server-Komponente listet Benutzer auf und wählt dabei drei Spalten nach der id-Reihenfolge aus:

    // app/users/page.tsx
    import db from '@/db'
    
    export default async function UsersPage() {
      const users = await db
        .selectFrom('user')
        .select(['id', 'name', 'email'])
        .orderBy('id')
        .execute()
    
      return (
        <div>
          <h1>Users</h1>
          <ul>
            {users.map(u => (
              <li key={u.id}>{u.name} ({u.email})</li>
            ))}
          </ul>
        </div>
      )
    }
    

    Da DB aus dem Prisma-Schema stammt, führt ein falsch geschriebener Tisch oder Spalte zu einem Typüberprüfungss Fehler, und users wird mit genau den ausgewählten Feldern abgeleitet.

    Eine Besonderheit: Prisma benennt Tische standardmäßig nach Modellen, wodurch dieses Schema einen Tisch User erstellt, und die generierten Typen verwenden diesen Namen. selectFrom('user') führt nur eine Typüberprüfung durch, wenn man @@map("user") zum Modell hinzufügt; ansonsten wird 'User' abgefragt. Außerdem sollte db nicht in Client Components verwendet werden, da es ein ausschließlich auf dem Server verfügbares Geheimnis liest.

    Der tägliche Arbeitsablauf

    Sobald alles verbunden ist, sieht der Zyklus wie folgt aus:

    1. Ändern Sie die Modelle in db/schema.prisma.
    2. Regenerieren Sie die Typen und wenden Sie die Änderung mit den beiden Skripten an.
    3. Verwenden Sie db in Server Components, Route-Handlern oder Server Actions.
  • Validieren Sie die Eingaben mit Zod oder ähnlichen Werkzeugen vor dem Einfügen; Kysely überprüft die Struktur der Abfragen, nicht die Werte der Formulare.
  • Durch das resultierende Layout bleiben alle mit der Datenbank verbundenen Elemente unter db/:

    📦 my-next-app
    ├── 📁 app
    │   └── 📁 users
    │       └── page.tsx
    ├── 📁 db
    │   ├── schema.prisma
    │   ├── index.ts
    │   └── 📁 types
    │       └── kysely.d.ts
    ├── 📁 node_modules
    ├── .env
    ├── package.json
    ├── pnpm-lock.yaml
    └── tsconfig.json
    

    Zusammenfassung

    Durch diese Trennung bleibt Prismas Schema-Workflow sowie Studio unverändert, ohne dass der Prisma Client mitgeliefert werden muss. Zudem erhalten Sie eine kleine Abfrageschicht im SQL-Stil, deren Typen das Schema erfassen.

    • Lassen Sie prisma und prisma-kysely als Entwicklungskomponenten; nur Kysely, ein Dialekt sowie ein Driver werden in die Produktion geliefert.
    • Verwenden Sie den Neon HTTP-Dialekt für einfache serverlose Workloads und wechseln Sie auf einen PostgresDialect mit Pools, wenn Transaktionen erforderlich sind.
    • Achten Sie auf die Benennung der Tabellen: Die generierten Typen folgen den Namen der Datenbanken, die standardmäßig den Namen der Prisma-Modelle verwenden.
  • Wechseln Sie von db push auf ordentliche Migrationsstrategien, bevor die Daten in der Datenbank von Bedeutung werden.