Prérequis

Bases de données pour un SaaS : Postgres + Drizzle en termes débutants

Guide accessible de PostgreSQL et Drizzle ORM pour un SaaS : tables, lignes, relations, pourquoi Postgres, schéma typé en TypeScript, requêtes, index, intégrité des données, migrations, et différences local vs production.

Bases de données pour un SaaS : Postgres + Drizzle en termes débutants

Quand vous construisez une app SaaS, il vous faut un endroit pour stocker les données — utilisateurs, commandes, posts, etc. C’est le rôle de la base de données. Dans cet article, on démystifie les bases de données avec PostgreSQL (Postgres) comme choix par défaut, et on montre comment utiliser l’ORM Drizzle (Object‑Relational Mapper) en TypeScript pour interagir avec Postgres de façon simple. Nous couvrirons ce que sont les tables et les lignes (sans jargon), pourquoi Postgres est un excellent défaut, comment Drizzle permet de définir et interroger votre schéma facilement, ainsi que les bases des requêtes, des index et de la sécurité des données. Nous expliquerons aussi la différence entre une base locale en développement et celle de production, et répondrons aux questions courantes (« Dois‑je connaître SQL ? », « Que se passe‑t‑il si je change une colonne ? »). À la fin, vous comprendrez le rôle de la base dans votre SaaS et serez à l’aise pour exécuter des migrations afin de faire évoluer votre schéma. Allons‑y !


Tables, lignes et relations (sans jargon)

Pensez à une table de base de données comme à une feuille de calcul. Elle a des colonnes (champs) et des lignes (entrées) :

  • Table : un ensemble de données organisé en lignes/colonnes — par ex., une table users.
  • Ligne : une entrée unique dans une table. Pour users, une ligne représente un compte utilisateur.
  • Colonne : une information par ligne, avec un nom et un type. Par exemple, email, password, created_at. Chaque ligne a une valeur pour chaque colonne (une adresse e‑mail, une date d’inscription, etc.).
  • Clé primaire (Primary Key) : colonne(s) qui identifient de façon unique chaque ligne (un ID unique par ligne).
  • Relation : un lien entre tables. Par ex., une table sessions qui suit les connexions peut avoir une colonne user_id qui référence une ligne de users. C’est l’essence des bases relationnelles : une table référence l’ID d’une ligne d’une autre table.

En clair : une table = un tableau Excel ; chaque ligne = un enregistrement (un utilisateur, une commande) ; chaque colonne = une propriété ; et les tables se lient via des IDs (une commande est liée à l’utilisateur qui l’a passée).


Pourquoi PostgreSQL est un excellent choix par défaut

Pour un SaaS, PostgreSQL est souvent recommandé comme base par défaut [1] :

  • Fiabilité éprouvée : Postgres existe depuis des décennies et est réputé robuste (ACID — transactions sûres).
  • Polyvalent et puissant : généraliste, il couvre de nombreux cas (comptes, contenus, transactions), avec des fonctionnalités avancées (JSON, recherche plein texte, SIG…).
  • Évolutif : on démarre petit, on grandit ; de la startup à des millions d’utilisateurs (avec tuning et matériel) [1].
  • Écosystème et support : hébergeurs et outils nombreux (Heroku Postgres, Neon, Supabase, Railway, AWS RDS…) [2]. Une communauté vaste.
  • Open source et économique : gratuit, sans verrouillage éditeur ; les offres managées restent abordables.
  • Compatibilité : beaucoup de systèmes parlent « Postgres » ou en dérivent (Redshift, CockroachDB, TimescaleDB) [2] — un savoir réutilisable.

En bref, Postgres est une fondation de confiance et flexible. « Great default » ne vient pas de nulle part : vous faites rarement fausse route en le choisissant pour un SaaS [1].


Présentation de Drizzle ORM : schéma typé en TypeScript

Maintenant que l’on a une base, comment y parler depuis le code ? Plutôt que d’écrire des requêtes SQL à la main, on utilise un ORM. Drizzle est un ORM TypeScript moderne pour les bases SQL comme Postgres, avec un fort accent sur la sécurité de types.

Drizzle se distingue par la définition du schéma en TypeScript. Vous décrivez tables et colonnes en code, qui devient la « source de vérité » pour les requêtes et les migrations [3] :

  • Source unique : schéma défini en un seul endroit (le code). Drizzle s’en sert pour vérifier vos requêtes et générer des migrations.
  • Typage : auto‑complétion et vérifications à la compilation. Une colonne inexistante fera échouer le build.
  • Pas de « chaînes magiques » : vous utilisez des fonctions/constantes plutôt que des chaînes SQL fragiles.

Exemple de table sessions pour suivre des connexions :

import { pgTable, varchar, text, timestamp, index, uniqueIndex } from "drizzle-orm/pg-core";

export const sessions = pgTable(
  "sessions",
  {
    id: varchar({ length: 255 }).primaryKey(),          // PK
    user_id: varchar({ length: 255 }).notNull(),        // propriétaire de la session
    token: varchar({ length: 512 }).notNull(),          // jeton unique
    expires_at: timestamp({ withTimezone: true }).notNull(),
    ip_address: varchar({ length: 255 }),
    user_agent: text(),
    created_at: timestamp({ withTimezone: true }).notNull().defaultNow(),
    updated_at: timestamp({ withTimezone: true }).notNull().defaultNow(),
  },
  (table) => [
    uniqueIndex("sessions_token_unique_idx").on(table.token),
    index("sessions_user_id_idx").on(table.user_id),
  ]
);

En clair :

  • pgTable("sessions", {...}, (table) => [...]) crée la table.
  • Les colonnes définissent types et contraintes (notNull, valeurs par défaut, timestamps…).
  • Le tableau final déclare index et contraintes (unicité du token, index sur user_id pour accélérer les recherches par utilisateur).

Tout est en TypeScript : le code et votre éditeur savent quelles colonnes existent et de quels types. Le schéma est lisible dans le repo et servira aux migrations.

Pourquoi Drizzle en Next.js/TypeScript ? Parce qu’il est léger, typé et s’intègre naturellement (pas de « moteur » lourd). Vous branchez votre connexion Postgres (pg) à Drizzle et c’est parti, y compris en environnements serverless/edge.


Interroger les données avec Drizzle (et bases d’index & d’intégrité)

Définir des tables ne suffit pas ; il faut lire/écrire. Drizzle fournit une API fluide et typée pour select/insert/update/delete.

Exemple : récupérer les transactions de crédits d’un utilisateur :

SELECT *
FROM credits
WHERE user_uuid = 'some-user-id'
ORDER BY created_at DESC;

En Drizzle :

import { db } from "@/db";
import { credits as creditsTable } from "@/db/schema";

const userId = "some-user-uuid";
const userCredits = await db.select().from(creditsTable)
  .where(creditsTable.user_uuid.eq(userId))
  .orderBy(creditsTable.created_at.desc());

Cette requête sélectionne toutes les colonnes de credits pour user_uuid = userId, par date décroissante. Remarquez :

  • creditsTable représente la table credits (exportée depuis le schéma).
  • user_uuid.eq(userId) évite les fautes de frappe et produit une requête paramétrée (protection contre l’injection SQL).
  • orderBy(created_at.desc()) pour le tri.

Résultat : un tableau d’objets typés côté TypeScript (forme conforme au schéma).

Index, contraintes, sécurité :

  • Index : l’index sur user_id (dans sessions) accélère les recherches « sessions d’un utilisateur ». Ajoutez des index sur les colonnes souvent filtrées/jointes.
  • Unicité : uniques sur email, token, order_no… garantissent l’intégrité (pas de doublons inattendus).
  • NOT NULL : empêche l’insertion de lignes incomplètes (ex. session sans user_id).
  • Types : la base impose les bons types (impossible d’insérer « Demain » dans un timestamp). Drizzle vous guide aussi côté TS.
  • Transactions : pour des opérations liées qui doivent réussir/échouer ensemble (mention rapide).
  • Sauvegardes : en production, sauvegardez — les erreurs arrivent.

En résumé, Drizzle rend les requêtes abordables et sûres ; les index/contraintes gardent les données propres et les requêtes rapides.


Base locale vs base de production

Travailler en local diffère de la gestion en production :

Base locale (dev) :

  • Postgres sur votre machine ou via Docker ; utilisé pour le build et les tests.
  • Liberté d’expérimenter : données d’exemple, reset de la base, opérations destructrices possibles sans impact réel.
  • Connexion via variables d’environnement (ex. DATABASE_URL=postgres://.../myapp_dev).
  • Chaque dev peut avoir sa base locale ; jamais de données réelles.
  • Performances modestes : gardez en tête qu’un dev dataset minuscule masque parfois des requêtes lentes en prod.

Base de production :

  • Base « sacrée » contenant les données réelles. Pas d’expérimentations.
  • Souvent managée (Heroku, AWS, GCP, Azure, Supabase, Neon…) : backups, mises à jour, scaling.
  • Chaîne de connexion distincte et sécurisée (variables d’environnement côté serveur).
  • Migrations en prod : testez d’abord en local/dev, puis appliquez‑les lors du déploiement quand le code est prêt à les supporter. Voir Drizzle Kit generate [5].
  • Volume et monitoring : surveillez perfs (requêtes lentes, index), journaux et métriques.
  • Sécurité et sauvegardes : mots de passe forts, accès réseau restreint, mises à jour.

En bref : une base pour le dev (erreurs sans gravité), une base distincte pour la prod (protégée, sauvegardée, changements réfléchis). Ne pointez jamais votre app locale vers la prod.


Questions fréquentes (débutants)

Q : Dois‑je apprendre SQL pour utiliser Postgres et Drizzle ?

R : Pas immédiatement, mais c’est précieux à terme. L’intérêt d’un ORM comme Drizzle est de pouvoir démarrer sans écrire du SQL brut pour chaque opération. Vous pouvez construire des features avec les méthodes de Drizzle. Mais comprendre les bases du SQL et des bases relationnelles vous aidera énormément [4] : modélisation des données (tables/relations), requêtes simples (SELECT/INSERT/UPDATE/DELETE, WHERE), JOIN, agrégations (COUNT, SUM, etc.). Pensez à l’ORM comme à des « petites roues » : utile pour démarrer, mais savoir pédaler sans (SQL) vous rend polyvalent.

Q : Que se passe‑t‑il si je change une colonne/table plus tard ? (Pourquoi les migrations comptent‑elles ?)

R : Le changement est constant. Vous voudrez ajouter une colonne, renommer un champ, scinder une adresse… Quand vous modifiez le schéma Drizzle en code, votre application s’attend à ce que la base reflète ce nouveau schéma. La base, elle, n’en sait rien tant que vous n’appliquez pas une migration. Sinon, vous aurez des erreurs (« colonne inconnue »).

Les migrations sont des scripts qui font évoluer le schéma (et parfois les données) d’une version à l’autre. Drizzle fournit Drizzle Kit pour les gérer :

  1. Vous modifiez le schéma en TypeScript.
  2. Vous lancez la génération (npx drizzle-kit generate ou script de projet) [5].
  3. Drizzle détecte les différences et crée un fichier SQL (ex. ALTER TABLE users ADD COLUMN age INTEGER;).
  4. Vous appliquez la migration (npx drizzle-kit migrate).
  5. Vous committez le fichier et la « photo » de schéma pour les prochains changements.

Exemple : passer credits de INT à BIGINT ? Changer integer() en bigint() dans le schéma ne suffit pas ; la migration générera l’ALTER TABLE approprié. En cas de changement non trivial (ex. textint), attendez‑vous à des avertissements et potentiellement des étapes de migration de données.

En dev, on peut réinitialiser si besoin. En prod, on applique les migrations avec précaution (sauvegardes, scripts réversibles si possible). L’essentiel : quand le modèle change, la base doit suivre — Drizzle + migrations rendent cela simple et sûr.


Prochaines étapes : essayez les migrations Drizzle

Suivez le README du projet pour exécuter des migrations — typiquement :

npm run db:generate   # génère une migration à partir des différences de schéma
npm run db:migrate    # applique la migration à la base

Vous verrez un nouveau fichier de migration (dossier drizzle/ ou migrations/) contenant du SQL (création de tables, index…). C’est l’occasion de voir comment vos définitions TypeScript se traduisent en SQL. Ensuite, appliquez la migration et vérifiez que votre Postgres local a maintenant les tables/colonnes attendues. 🎉

Mettez tout bout à bout :

  • Écrivez une petite requête Drizzle pour insérer des données de test.
  • Relisez‑les et affichez‑les.
  • Ajoutez une colonne (ex. nickname dans users), regénérez/appliquez et observez la détection du changement.

Félicitations : vous avez fait un grand pas dans le monde des bases pour SaaS — fondements relationnels, intérêt de Postgres, DRY et typage de Drizzle, et migrations sûres. Bon code, et bons modèles de données !


Références