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.
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 leprisma/schema.prismapar 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é.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 :
- Modifier les modèles dans
db/schema.prisma. - Régénérer les types et appliquer les modifications à l’aide des deux scripts.
- Utiliser
dbdepuis les composants serveur, les gestionnaires de route ou les actions serveur.
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
prismaetprisma-kyselyen 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
PostgresDialectgé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.
db push à des migrations appropriées avant que les données dans la base de données deviennent importantes.