Travailler avec des bases de données en TypeScript signifiait autrefois choisir entre des chaînes SQL brutes sans aucune sécurité de typage ou des query builders approximatifs qui comprenaient à peine votre schéma. Ça a changé. Prisma a inauguré une approche schema-first qui génère un client entièrement typé à partir d’un DSL dédié. Drizzle a suivi avec un angle différent : définir son schéma en TypeScript, requêter avec une API qui reflète le SQL, et se passer entièrement de génération de code. Ensemble, ils représentent le meilleur de ce que l’écosystème ORM TypeScript a à offrir.
Prisma : penser en modèles, pas en tables
L’idée centrale de Prisma est que votre schéma de base de données devrait être la source de vérité unique, et que tout le reste devrait en découler. Vous écrivez un fichier .prisma, Prisma génère un client, et toute votre couche d’accès aux données est typée à partir de ce moment.
model User {
id String @id @default(cuid())
email String @unique
name String?
posts Post[]
}
model Post {
id String @id @default(cuid())
title String
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
createdAt DateTime @default(now())
}
Naviguer dans les données comme un graphe
Le client généré permet de traverser les relations naturellement. Vous n’écrivez pas de jointures, vous décrivez ce que vous voulez inclure. TypeScript restreint le type de retour en fonction de vos choix select et include, ce qui permet au compilateur d’attraper les erreurs avant qu’elles n’atteignent la base.
const userWithPosts = await prisma.user.findUnique({
where: { email: 'thomas@example.com' },
include: {
posts: {
where: { published: true },
orderBy: { createdAt: 'desc' },
},
},
});
Les migrations comme fonctionnalité de premier plan
Prisma Migrate compare votre fichier de schéma avec l’état de la base de données et génère automatiquement des fichiers de migration SQL. Lancer prisma migrate dev crée la migration, l’applique et régénère le client en une seule commande. L’historique des migrations vit dans votre dépôt, versionné aux côtés de votre code.
const recentPosts = await prisma.post.findMany({
where: {
published: true,
createdAt: { gte: new Date('2025-01-01') },
},
select: {
title: true,
author: { select: { name: true } },
},
take: 10,
});
Prisma Studio offre également un navigateur visuel pour vos données, pratique pour le débogage et les modifications rapides en développement sans écrire de requêtes jetables.
Drizzle : du SQL qui se type tout seul
Drizzle part d’une prémisse différente. Au lieu de générer un client à partir d’un schéma externe, vous définissez vos tables en TypeScript et obtenez l’inférence de types gratuitement. Le query builder se calque étroitement sur la syntaxe SQL, ce qui signifie que le modèle mental est le SQL lui-même, avec en prime l’autocomplétion et la vérification à la compilation.
import { pgTable, text, boolean, timestamp } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: text('id')
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
email: text('email').notNull().unique(),
name: text('name'),
});
export const posts = pgTable('posts', {
id: text('id')
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
title: text('title').notNull(),
published: boolean('published').default(false),
authorId: text('author_id')
.notNull()
.references(() => users.id),
createdAt: timestamp('created_at').defaultNow(),
});
Un query builder qui se lit comme du SQL
Si vous avez déjà écrit un SELECT avec un WHERE et un ORDER BY, l’API de Drizzle vous semblera immédiatement familière. Les jointures sont explicites, les filtres se composent avec des helpers and/or, et vous savez toujours quel SQL sera généré parce que l’API s’y calque directement.
import { eq, desc, and } from 'drizzle-orm';
const userWithPosts = await db
.select()
.from(users)
.leftJoin(posts, eq(posts.authorId, users.id))
.where(and(eq(users.email, 'thomas@example.com'), eq(posts.published, true)))
.orderBy(desc(posts.createdAt));
Des requêtes relationnelles pour le confort
Quand le builder style SQL semble verbeux pour du chargement imbriqué simple, Drizzle propose une API de requêtes relationnelles. Elle gère le chargement eager avec une syntaxe plus épurée tout en produisant des requêtes efficaces sous le capot.
const recentPosts = await db.query.posts.findMany({
where: (posts, { eq, gte, and }) =>
and(eq(posts.published, true), gte(posts.createdAt, new Date('2025-01-01'))),
with: { author: true },
limit: 10,
});
Conçu pour l’edge
L’empreinte runtime de Drizzle est minuscule. Pas d’étape de génération de code, pas de client lourd à charger. Il se connecte via des drivers PostgreSQL standards (ou MySQL, SQLite) et fonctionne nativement sur Vercel Edge Functions, Cloudflare Workers et tout autre environnement contraint.
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
const client = postgres(process.env.DATABASE_URL!);
export const db = drizzle(client);
Deux philosophies, un même objectif
Prisma et Drizzle résolvent tous deux le même problème fondamental : rendre l’accès aux bases de données en TypeScript sûr, productif et maintenable. Prisma le fait en abstrayant le SQL en une traversée de graphe haut niveau. Drizzle le fait en rendant le SQL lui-même typé. Les deux offrent un excellent support TypeScript, des communautés actives et un outillage prêt pour la production.
Connaître les deux signifie pouvoir choisir le bon outil selon le projet. Une application riche en contenu avec des relations complexes peut bénéficier des requêtes style graphe de Prisma. Une API critique en performance déployée sur l’edge penchera plutôt vers le runtime léger de Drizzle. Dans tous les cas, l’époque de l’accès non typé aux bases de données en TypeScript est révolue.