Skip to content
Learni
Voir tous les tutoriels
Base de données

Comment implémenter Drizzle ORM avec TypeScript en 2026

Read in English

Introduction

Drizzle ORM est un ORM léger et performant pour TypeScript, conçu pour les développeurs qui veulent le contrôle total sur leurs requêtes SQL sans la lourdeur des ORMs traditionnels comme Prisma ou TypeORM. En 2026, il s'impose comme choix privilégié pour les applications Node.js scalables grâce à sa compilation type-safe, ses migrations automatisées et son support natif des bases comme PostgreSQL, MySQL ou SQLite.

Pourquoi l'adopter ? Imaginez un ORM qui génère du SQL pur optimisé, infère les types de votre base de données en temps réel, et évite les abstractions magiques qui masquent les performances. Ce tutoriel intermédiaire vous guide pour implémenter une API complète avec utilisateurs et posts, incluant relations, transactions et validations. À la fin, vous maîtriserez Drizzle pour booster vos backends de 30-50% en vitesse de requête. Prêt à plonger dans le code concret ? (128 mots)

Prérequis

  • Node.js 20+ et npm/yarn/pnpm
  • Connaissances en TypeScript et SQL basique
  • Éditeur comme VS Code avec extension Drizzle
  • SQLite pour simplicité (aucune config DB externe requise)

Installation des dépendances

terminal
mkdir drizzle-tutorial
cd drizzle-tutorial
npm init -y
npm install drizzle-orm better-sqlite3
dnpm install -D drizzle-kit typescript @types/node tsx
npm install -D @types/better-sqlite3

Cette commande initialise un projet Node.js et installe Drizzle ORM avec le driver SQLite (better-sqlite3 pour performances optimales). Drizzle-kit gère les migrations, TypeScript assure la type-safety, et tsx permet d'exécuter les fichiers TS directement. Évitez pg pour PostgreSQL ici pour simplicité ; relancez npm install si des peer deps manquent.

Configuration initiale de TypeScript

Créez un fichier tsconfig.json pour une configuration stricte. Cela active les checks d'erreurs statiques et l'inférence de types Drizzle, essentiel pour éviter les bugs runtime. Drizzle exploite les generics TS pour mapper parfaitement schémas et queries.

Configuration TypeScript

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "noEmit": true,
    "composite": true,
    "strict": true,
    "downlevelIteration": true,
    "skipLibCheck": true,
    "jsx": "react-jsx",
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "allowJs": true,
    "noFallthroughCasesInSwitch": true,
    "resolveJsonModule": true
  }
}

Ce tsconfig est optimisé pour Drizzle : strict: true force la type-safety, moduleResolution: bundler supporte les imports modernes. Copiez-collez tel quel ; il prévient 90% des erreurs de typage SQL. Piège : sans composite: true, les builds incrémentaux échouent.

Définition du schéma de base de données

src/schema.ts
import { sqliteTable, text, integer, uniqueIndex } from 'drizzle-orm/sqlite-core';

export const users = sqliteTable('users', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  name: text('name').notNull(),
  email: text('email').notNull().unique(),
});

export const posts = sqliteTable('posts', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  title: text('title').notNull(),
  content: text('content'),
  userId: integer('user_id').references(() => users.id),
}, (table) =>({
  userIdx: uniqueIndex('user_idx').on(table.userId),
}));

Ce schéma définit deux tables : users avec champs basiques et posts avec relation foreign key vers users. Drizzle infère les types TS automatiquement (ex: SelectUsers). L'index unique sur userId optimise les jointures. Piège : oubliez references(() => users.id) et les types relationnels cassent.

Génération des migrations

Drizzle-kit analyse votre schéma TS pour générer des migrations SQL idempotentes. C'est comme un 'git pour DB' : versionnez-les dans migrations/ pour déploiements CI/CD.

Configuration Drizzle Kit

drizzle.config.ts
import type { Config } from 'drizzle-kit';

export default {
  schema: './src/schema.ts',
  out: './migrations',
  dialect: 'sqlite',
  dbCredentials: {
    url: './sqlite.db',
  },
} satisfies Config;

Ce config pointe vers votre schéma et output des migrations SQL. dialect: 'sqlite' adapte au driver ; url pointe la DB fichier. Exécutez ensuite npx drizzle-kit generate:sqlite. Piège : chemin relatif faux = migrations vides.

Connexion à la base et migration

src/db.ts
import Database from 'better-sqlite3';
import { drizzle } from 'drizzle-orm/better-sqlite3';
import * as schema from './schema';

const sqlite = new Database('./sqlite.db');
export const db = drizzle(sqlite, { schema });

// Migration manuelle (ou via CLI)
import { migrate } from 'drizzle-orm/better-sqlite3/migrator';
await migrate(db, { migrationsFolder: './migrations' });

Cette connexion crée un pool SQLite lightweight et attache le schéma pour inférence types. migrate() applique les migrations automatiquement. Utilisez async/await dans votre app init. Piège : sans schema, pas d'autocomplétion TS sur queries.

Opérations CRUD basiques

Comme un chef cuisinier SQL : Drizzle compile vos queries TS en SQL optimisé. Les generics assurent zero runtime errors.

Exemple CRUD complet

src/crud.ts
import { db } from './db';
import { users, posts } from './schema';
import { eq, desc } from 'drizzle-orm';

// CREATE
await db.insert(users).values({ name: 'Alice', email: 'alice@example.com' });

// READ avec jointure
const results = await db
  .select({
    postId: posts.id,
    title: posts.title,
    userName: users.name,
  })
  .from(posts)
  .leftJoin(users, eq(posts.userId, users.id))
  .orderBy(desc(posts.id));

// UPDATE
await db.update(users).set({ name: 'Alice Updated' }).where(eq(users.id, 1));

// DELETE
await db.delete(users).where(eq(users.id, 1));

Ce bloc démontre insert, select avec join, update et delete. eq() et desc() sont des helpers type-safe pour WHERE/ORDER. Copiez dans un script tsx src/crud.ts après migration. Piège : sans eq(), clauses dynamiques perdent type-safety.

Transactions et relations avancées

src/advanced.ts
import { db } from './db';
import { users, posts } from './schema';
import { eq, and } from 'drizzle-orm';

await db.transaction(async (tx) => {
  const newUserId = await tx.insert(users).values({ name: 'Bob', email: 'bob@example.com' }).returning({ id: users.id });
  await tx.insert(posts).values([
    { title: 'Premier post', content: 'Contenu', userId: newUserId[0].id },
    { title: 'Deuxième post', userId: newUserId[0].id }
  ]);
});

// Query relationnelle
const userPosts = await db.query.users.findFirst({
  where: eq(users.id, 1),
  with: {
    posts: true,
  },
});

Les transactions ACID protègent les ops multiples ; returning() renvoie l'ID généré. db.query simplifie les relations one-to-many. Idéal pour APIs users/posts. Piège : oubliez tx dans transaction = commits séparés, data inconsistency.

Intégration dans une API Next.js

Pour une API réelle, wrappez db dans un singleton. Exemple route GET /users.

Route API avec Drizzle

app/api/users/route.ts
import { db } from '@/db';
import { users } from '@/db/schema';
import { NextResponse } from 'next/server';

export async function GET() {
  const allUsers = await db.select().from(users);
  return NextResponse.json(allUsers);
}

export async function POST(request: Request) {
  const body = await request.json();
  const newUser = await db.insert(users).values(body).returning();
  return NextResponse.json(newUser, { status: 201 });
}

Route Next.js 14+ App Router utilisant Drizzle pour GET/POST. Type-safety sur body via Zod recommandé. Testez avec curl POST. Piège : sans returning(), pas de data response ; ajoutez validation pour prod.

Bonnes pratiques

  • Toujours utiliser transactions pour ops multi-tables : atomicité garantie.
  • Validez inputs avec Zod et prepared() pour SQL injection (bien que Drizzle prépare nativement).
  • Paginer queries : limit(10).offset(page * 10) pour scalabilité.
  • Indexez relations : ajoutez index() dans schéma pour jointures rapides.
  • Environnement vars pour DB URL : process.env.DATABASE_URL.

Erreurs courantes à éviter

  • Pas de migrate au démarrage : DB vide = crashes sur selects ; wrappez dans try/catch.
  • Imports relatifs faux : schema.ts doit être accessible partout sinon types perdus.
  • Oubli references() : relations cassées, queries sans autocomplétion.
  • Exécuter sans tsx : erreurs TS ignorées runtime ; toujours tsx src/script.ts.

Pour aller plus loin

Approfondissez avec PostgreSQL pour prod : remplacez better-sqlite3 par @neondatabase/serverless. Consultez la doc officielle Drizzle. Découvrez nos formations Learni sur les bases de données avancées pour maîtriser migrations CI/CD et sharding.