Bases de données pour SaaS : Postgres + Drizzle en termes simples
Guide débutant sur PostgreSQL et Drizzle ORM pour SaaS : tables, lignes, relations, pourquoi Postgres, schéma typé en TypeScript, requêtes, index, sécurité des données, migrations, local vs production.
Bases de données pour SaaS : Postgres + Drizzle en termes simples
Quand on construit un SaaS, il faut stocker des données : utilisateurs, commandes, posts, sessions, etc. Ce guide explique les bases avec PostgreSQL (Postgres) comme choix par défaut, et montre comment Drizzle ORM permet de définir et interroger le schéma en TypeScript.
Vous apprendrez :
- Ce que signifient tables, lignes, colonnes et relations.
- Pourquoi Postgres est un excellent choix par défaut.
- Comment Drizzle définit des schémas et des requêtes typées.
- Pourquoi index et contraintes rendent les données rapides et propres.
- La différence entre local et production.
- Ce que sont les migrations et pourquoi elles comptent.
Tables, lignes et relations (sans jargon)
Imaginez une table comme un tableau :
- Table : un ensemble de données (ex.
users). - Ligne : une entrée (un utilisateur).
- Colonne : un champ par ligne (email, created_at, plan).
- Primary key : ID unique par ligne.
- Relation : lien entre tables via un ID.
Exemple : la table sessions peut contenir user_id qui pointe vers une ligne de users. C’est une relation. D’où “relational database”.
En clair : les tables stockent des types d’objets, les lignes stockent des objets, les relations les relient.
Pourquoi PostgreSQL est un excellent choix par défaut
Postgres est souvent recommandé comme choix par défaut [1] car il est :
- Fiable : des décennies d’usage et des transactions ACID.
- Généraliste : adapté au SaaS, finance, contenu, etc.
- Scalable : on commence petit et on grandit [1].
- Bien supporté : nombreuses options d’hébergement géré [2].
- Open source : pas de lock‑in ni de licences.
- Compatible : beaucoup de systèmes parlent “Postgres” [2].
Sauf cas très spécifique, Postgres est une base solide pour un SaaS.
Drizzle ORM : schéma typé en TypeScript
Un ORM (Object‑Relational Mapper) permet d’interagir avec la base via le langage plutôt que du SQL brut. Drizzle est un ORM TypeScript moderne qui garde SQL proche, avec de la sécurité de types.
Pourquoi Drizzle est simple :
- Source unique de vérité : schéma en TypeScript réutilisé pour requêtes et migrations [3].
- Type safety : un champ inexistant est détecté au build.
- Pas de chaînes magiques : API fluide au lieu de strings SQL.
Exemple de schéma Drizzle (sessions) :
import { pgTable, varchar, text, timestamp, index, uniqueIndex } from "drizzle-orm/pg-core";
export const sessions = pgTable(
"sessions",
{
id: varchar({ length: 255 }).primaryKey(), // Primary key column, a string ID
user_id: varchar({ length: 255 }).notNull(), // ID of the user who owns this session (must have a value)
token: varchar({ length: 512 }).notNull(), // A unique session token (must have a value)
expires_at: timestamp({ withTimezone: true }).notNull(), // When the session expires
ip_address: varchar({ length: 255 }), // (Optional) IP address of the session
user_agent: text(), // (Optional) User agent string of the browser/device
created_at: timestamp({ withTimezone: true }).notNull().defaultNow(),
updated_at: timestamp({ withTimezone: true }).notNull().defaultNow(),
},
(table) => [
// Indexes and constraints:
uniqueIndex("sessions_token_unique_idx").on(table.token), // Ensure no two sessions have the same token
index("sessions_user_id_idx").on(table.user_id), // Index to quickly look up sessions by user_id
]
);En termes simples :
sessionsest la table.id,user_id,token,expires_atsont obligatoires.ip_addressetuser_agentsont optionnels.created_atetupdated_atse remplissent automatiquement.- Index unique sur
tokenpour éviter les doublons. - Index sur
user_idpour accélérer les recherches.
Le schéma TypeScript aide à éviter les erreurs et sert de base aux migrations.
Requêter avec Drizzle (index et sécurité)
Drizzle propose une API fluide pour select/insert/update/delete, qui génère du SQL sûr.
Exemple SQL :
SELECT *
FROM credits
WHERE user_uuid = 'some-user-id'
ORDER BY created_at DESC;Exemple Drizzle :
import { db } from "@/db"; // our Drizzle database instance
import { credits as creditsTable } from "@/db/schema"; // the credits table schema we defined
const userId = "some-user-uuid";
const userCredits = await db.select().from(creditsTable)
.where(creditsTable.user_uuid.eq(userId))
.orderBy(creditsTable.created_at.desc());Pourquoi c’est plus sûr :
- Type safety : noms de colonnes vérifiés au build.
- Requêtes paramétrées : réduit le risque d’injection SQL.
- Intention claire : le code reflète la requête.
Index, contraintes, sécurité :
- Index : accélèrent les recherches fréquentes.
- Contraintes uniques : évitent les doublons (email unique, token unique).
- NOT NULL : empêche les valeurs manquantes.
- Types : Postgres impose les types et Drizzle les reflète.
- Transactions : regroupent des opérations pour réussir/échouer ensemble.
- Sauvegardes : indispensables en production.
Base locale vs base de production
Développement local :
- Sur laptop ou Docker.
- Reset possible sans risque.
DATABASE_URLen.env.- Données de test.
Production :
- Données réelles : pas d’expérimentation.
- Services managés (Neon, Supabase, RDS, etc.).
- Identifiants sécurisés, backups, monitoring.
- Migrations appliquées avec soin [5].
Point clé : séparer dev et prod, tester les migrations en local avant prod.
Questions fréquentes
Q : Dois‑je apprendre SQL pour utiliser Postgres et Drizzle ?
R : Pas immédiatement, mais c’est utile à long terme. Un ORM évite d’écrire du SQL brut, mais SQL aide pour debug et performance [4].
Bases utiles :
- Modélisation : tables et relations.
- Requêtes :
SELECT,INSERT,UPDATE,DELETE,WHERE. - Joins : combiner des données liées.
- Agrégations : compter, sommer, moyenner.
Drizzle ne cache pas SQL ; il aide à l’utiliser correctement. Pensez à l’ORM comme à des petites roues.
Q : Et si je change une colonne ou une table ? (migrations)
Changer le schéma en code ne change pas la base automatiquement. Les migrations synchronisent le tout.
Comment ça marche avec Drizzle :
- Modifier le schéma en TypeScript.
- Lancer
drizzle-kit generate[5]. - Vérifier la migration (Drizzle peut demander si c’est un rename).
- Appliquer avec
drizzle-kit migrate. - Versionner les migrations.
Exemple : passer credits de INT à BIGINT nécessite une migration. En prod, faites un backup et appliquez pendant le déploiement.
Next Steps : essayer les migrations Drizzle
Suivez le README de votre projet pour générer et appliquer les migrations. Par exemple :
npm run db:generate # generates a migration based on schema differences
npm run db:migrate # applies the migration to the databasePuis :
- Insérer une donnée de test.
- La relire via Drizzle.
- Ajouter une colonne et migrer à nouveau.
Vous verrez comment le schéma TypeScript devient SQL et garde la DB alignée.
Références
Frontend vs Backend vs Full‑Stack pour débutants SaaS
Guide débutant pour comprendre frontend, backend et full‑stack dans un SaaS, avec une analogie simple et des exemples d’un stack moderne.
Guide budget SaaS — coûts, hosting et l’essentiel
Guide pratique des coûts réels pour démarrer un SaaS : hosting, base de données, paiements, email et où part l’argent. Démarrez gratuit, validez vite, payez seulement en croissance.