Accueil / Articles

Prisma sans Prisma Client : requêtes SQL sécurisées par types dans Next.js

Apprenez à utiliser Prisma pour le travail sur les schémas, à générer des types Kysely avec prisma-kysely, à choisir un dialecte Neon ou pg, et à effectuer des requêtes de manière sécurisée depuis du code serveur Next.js.

Éditeur : REACTAPP.TOP6 min de lecture1411 mots

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

Prisma est excellent pour la conception de schémas, mais Prisma Client représente une dépendance importante à intégrer dans chaque fonction serverless, alors que tout ce dont on a besoin, c’est de SQL sécurisé par des types. Une approche pratique consiste à faire en sorte que Prisma gère le schéma pendant le développement, qu’un générateur le transforme en types TypeScript, et que Kysely exécute les requêtes en production. Ce guide montre comment mettre cela en place dans un projet Next.js App Router et compare le dialecte HTTP Neon avec une pool classique pg.

Si vous préférez l’approche conventionnelle avec Prisma Client en temps de exécution, consultez la mise en place de Prisma 7 dans un projet Next.js 16.

Comment se répartissent les responsabilités

Chaque outil de cette configuration a une tâche bien définie :

  • Prisma gère la conception de la base de données : le fichier de schéma, la synchronisation ou la migration de la base, ainsi que des outils comme Prisma Studio pour explorer les données.
  • Kysely est l’outil de construction de requêtes en temps de exécution. Vous écrivez des requêtes ressemblant à du SQL en TypeScript, le compilateur vérifie les noms de tables et de colonnes, et la bibliothèque elle-même est de petite taille.
  • prisma-kysely sert de lien entre les deux : c’est un générateur Prisma qui produit directement des définitions de types Kysely à partir de votre schéma Prisma.

Prisma reste intégré à votre flux de travail de développement, tandis que Kysely constitue la seule couche de base de données présente dans le code déployé. Le compromis : vous perdez l’API de Prisma Client qui prend en compte les relations, telle que les include imbriqués et les écritures imbriquées, ainsi que la possibilité d’écrire vous-même des jointures. Si vous devez encore choisir une couche de données en général, la comparaison entre SQL brut, Prisma et Drizzle aborde la décision dans son ensemble.

Installation des paquets

Le runtime nécessite Kysely ainsi qu’un dialecte et un pilote ; cela installe la pile basée sur Neon (pnpm ici, mais npm ou yarn fonctionnent également) :

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

Notez que prisma et prisma-kysely sont des dépendances de développement : ils gèrent uniquement le schéma et génèrent du code.

Définition du schéma et génération des types Kysely

Le schéma est un schéma Prisma ordinaire, à ceci près que le générateur utilisé est prisma-kysely (qui écrit dans ./types) au lieu du générateur de client habituel. Il définit User et Post avec une relation un-à-plusieurs.

// 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 génère désormais des types Kysely plutôt qu’un client, et prisma db push applique le schéma à la base de données :

pnpm prisma generate
pnpm prisma db push

Le résultat est un fichier tel que db/types/kysely.d.ts contenant une interface DB qui décrit chaque table. Trois précautions à prendre :

  • Le schéma se trouve dans db/schema.prisma, et non dans le prisma/schema.prisma par défaut de Prisma ; il faut donc indiquer ce chemin à la CLI (par exemple avec --schema) en fonction de la version de Prisma utilisée.
  • db push synchronise la base de données sans enregistrer de fichiers de migration. Pour les données de production, prisma migrate dev et prisma migrate deploy génèrent des migrations versionnées tout en laissant le reste du flux de travail inchangé.
  • Les dernières versions de Prisma déplacent les paramètres de connexion dans un fichier de configuration distinct ; si la ligne url est rejetée, consultez la documentation actuelle.
  • Scripts de package pour le cycle quotidien

    Quelques scripts rendent le flux de travail clair pour l’équipe :

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

    Avec ceux-ci en place, toute modification de schéma se résume à deux commandes :

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

    La base de données et les types Kysely restent alors alignés, ce qui permet au compilateur d’indiquer toute requête touchant une colonne renommée ou supprimée.

    Sélection d’un dialecte Kysely

    Kysely ne communique pas directement avec la base de données elle-même. Un dialecte se situe entre l’éditeur de requêtes et le pilote, et sait comment compiler et envoyer des requêtes pour une base de données et un méthode de connexion donnés. Les dialectes officiels couvrent PostgreSQL, MySQL et SQLite ; ceux développés par la communauté concernent des services tels que Neon, PlanetScale et Turso. Pour Postgres, deux options suffisent dans la plupart des cas.

    Neon via HTTP pour les déploiements serverless

    Au sein de Vercel ou d’une plateforme similaire, le pilote HTTP de Neon s’adapte bien : chaque requête étant une demande sans état, il n’y a pas de connexion persistante à maintenir entre les appels courts. La configuration enrobe neon() dans NeonDialect et définit Kysely en utilisant le type DB généré :

    // 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
    

    Le ! ne fait que silencer TypeScript ; il ne valide rien. La plus grande limitation est que kysely-neon via HTTP ne peut pas exécuter de transactions ni de requêtes basées sur des sessions, ce qui le rend adapté aux opérations CRUD simples et aux pages à forte charge de lecture plutôt qu’aux tâches nécessitant plusieurs instructions. Avec le pilote WebSocket de Neon, vous n’avez pas du tout besoin de kysely-neon : transmettez le Pool de Neon au PostgresDialect intégré à Kysely. L’API de kysely-neon a également changé entre les versions, il convient donc de la comparer avec son README actuel.

    node-postgres pour les serveurs à long terme

    Pour un serveur Node.js qui doit fonctionner sur le long terme, le pilote officiel pg est le choix le plus complet, offrant des transactions, un système de gestion de pools et des requêtes préparées. Il s’intègre directement au PostgresDialect de Kysely :

    // 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
    

    Choix entre eux

    La différence réside dans le modèle de connexion. kysely-neon est un HTTP sans état : léger et adapté aux serveurs sans gestion, mais il ne prend pas en charge les transactions. pg maintient des connexions TCP en pool ouvertes, avec l’ensemble complet des fonctionnalités, ce qui convient aux serveurs en permanence actifs. Utilisez kysely-neon sur des plateformes sans gestion comme Vercel ou Netlify, et pg ou le pool WebSocket de Neon lorsque vous avez besoin de transactions. Faites attention aux pools pg à l’intérieur des fonctions sans gestion : chaque instance ouvre ses propres connexions et peut épuiser la limite de la base de données en l’absence d’un pooleur externe.

    Interrogation depuis un composant serveur

    Tout code côté serveur peut importer db. Ce composant serveur liste les utilisateurs, en sélectionnant trois colonnes classées par id :

    // 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>
      )
    }
    

    Puisque DB provient du schéma Prisma, une erreur d’orthographe dans le nom d’une table ou d’une colonne entraîne un échec du contrôle de type, et users est déduit en fonction des seuls champs sélectionnés.

    Un bémol : par défaut, Prisma nomme les tables d’après les modèles, ce qui fait que ce schéma crée une table User, et les types générés utilisent ce nom. selectFrom('user') ne permet le contrôle de type que si l’on ajoute @@map("user") au modèle ; sinon, la requête utilise 'User'. De plus, il faut éviter d’utiliser db dans les composants clients, car il lit un secret réservé au serveur.

    Le flux de travail quotidien

    Lorsque tout est configuré, la boucle ressemble à ceci :

    1. Modifier les modèles dans db/schema.prisma.
    2. Régénérer les types et appliquer les modifications à l’aide des deux scripts.
    3. Utiliser db depuis les composants serveur, les gestionnaires de route ou les actions serveur.
  • Vérifiez les entrées à l’aide de Zod ou d’un outil similaire avant insertion ; les types Kysely vérifient la structure de la requête, et non les valeurs du formulaire.
  • Le layout résultant place tout ce qui concerne la base de données sous 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
    

    En résumé

    Cette séparation permet de conserver le flux de travail de schéma de Prisma et Studio sans inclure Prisma Client, tout en offrant une couche de requêtes inspirée de SQL dont les types suivent le schéma.

    • Gardez prisma et prisma-kysely en tant que dépendances de développement ; seuls Kysely, un dialecte et un pilote sont envoyés en production.
    • Utilisez le dialecte HTTP Neon pour des charges de travail serverless simples, et passez à un PostgresDialect gérant des connexions partagées lorsque vous avez besoin de transactions.
    • Faites attention à la nommage des tables : les types générés suivent les noms de la base de données, qui correspondent par défaut aux noms des modèles Prisma.
  • Passez de db push à des migrations appropriées avant que les données dans la base de données deviennent importantes.
  • Lectures complémentaires