3.4 · SQL avec Node : PostgreSQL, MySQL, Prisma

Niveau 3 · Express & bases de données

3.4SQL avec Node : PostgreSQL, MySQL, Prisma

Objectif : savoir persister avec une base relationnelle (PostgreSQL/MySQL) via un ORM moderne (Prisma), en écrivant des requêtes sûres — complétant votre maîtrise des deux familles de bases.
Temps estimé : 11 min · lecture autonome · Si vous n'avez que 5 min : lisez « L'essentiel en 30s » + le schéma.

L'essentiel en 30 secondes

  • Le relationnel structure : tables, lignes, clés étrangères (PostgreSQL/MySQL), schéma strict et transactions ACID pour l'intégrité.
  • Le SQL s'écrit ou se génère : SELECT/INSERT/UPDATE/DELETE + JOIN en brut paramétré, ou méthodes d'ORM (Prisma/Sequelize) qui génèrent un SQL sûr.
  • La sécurité est non négociable : jamais de concaténation de données utilisateur (injection SQL) — requêtes paramétrées ou ORM + compréhension du SQL sous-jacent.

Mini-cas filé — Salim facture en SQL sans faille

Salim, développeur junior à Alger, doit facturer : clients, commandes et lignes liées, totaux à garantir. Le NoSQL souple ne suffit plus. Il modélise trois tables reliées par clés étrangères (PostgreSQL), écrit ses premières requêtes SELECT/JOIN en paramétré, puis passe à Prisma : schéma déclaratif schema.prisma, migration qui versionne, client typé prisma.commande.create() et findMany(). Quand un champ de recherche contient un guillemet piégé, le paramétrage le neutralise : aucune injection.

Dernier épisode du fil Salim — d'Express aux deux familles de bases : la persistance complète.

1. Le modèle relationnel & le SQL

La famille SQL / relationnelle (surtout PostgreSQL, puissant et open source, et MySQL/MariaDB) organise les données en tables (colonnes typées), lignes (enregistrements) et relations formelles par clés étrangères (une commande référence un client). Le langage SQL interroge : SELECT (lire), INSERT (créer), UPDATE, DELETE, JOIN (combiner des tables liées). Garanties fortes : schéma strict et transactions ACID pour la cohérence — idéales pour données structurées et critiques.

Depuis Node, deux niveaux : le driver brut (ex. pg pour PostgreSQL) où l'on écrit le SQL dans des chaînes — avec l'impératif requêtes paramétrées ($1, $2…) car concaténer des entrées utilisateur ouvre l'injection SQL, faille critique. Ou l'ORM qui génère le SQL à votre place.

2. Prisma : schéma, migrations, client typé

Un ORM (Object-Relational Mapper) mappe tables ↔ objets JS : on manipule prisma.client.create({ data }) et findMany({ where }) au lieu de chaînes SQL — plus lisible, plus productif, et protégé des injections par paramétrage automatique. Les courants : Prisma (moderne, très apprécié) et Sequelize (ancien, éprouvé).

Prisma se distingue par son schéma déclaratif (fichier schema.prisma décrivant modèles et relations), ses migrations (versionner l'évolution du schéma) et son client généré et typé (autocomplétion, sécurité des types — excellent avec TypeScript, section 5-1). Recommandé pour la plupart des applis ; garder le SQL brut pour les requêtes très pointues. Réflexe de Salim : comprendre le SQL généré (charger les relations efficacement, éviter le N+1) au lieu de traiter l'ORM en boîte noire.

Erreur d'interprétation fréquente : « Avec un ORM, inutile d'apprendre le SQL, et concaténer une entrée dans la requête dépanne ». Non. Sans bases SQL (jointures, chargement des relations), l'ORM produit des requêtes inefficaces (N+1) incomprises. Et concaténer une donnée utilisateur, même « juste pour tester », ouvre une injection SQL exploitable : paramétrage toujours, sans exception.
SQL avec Node : deux niveaux Du SQL direct au client typé RelationnelTables, lignes, clés étrangères+Driver brutSQL paramétré, jamais concaténé+ORM PrismaSchéma, migrations, client typé L'ORM protège des injections ; le SQL reste à comprendre.
Du SQL direct au client typé
Version texte du schéma (SEO / lecteurs d'écran / traduction) : 1) Relationnel : Salim lie clients, commandes et lignes par clés étrangères. 2) Driver brut : ses SELECT/JOIN passent en requêtes paramétrées. 3) ORM : Prisma génère un client typé depuis schema.prisma avec migrations. Leçon : productivité de l'ORM sans abandonner le SQL.

🔎 Exemple concret — Prisma : un CRUD typé et sûr

Salim crée et liste des clients sans écrire de SQL.

// schema.prisma déclare Client et Commande liés (1-N)
const nouveau = await prisma.client.create({
  data: { nom: 'Salim', email: 'salim@exemple-fictif.test' }
});
const actifs = await prisma.client.findMany({
  where: { actif: true }
});
// Driver brut équivalent SÛR (paramétré, pg) :
// await pool.query('SELECT * FROM clients WHERE email = $1', [emailSaisi]);

Lecture : objets et méthodes côté Prisma, $1 paramétré côté brut.

Leçon : les deux voies neutralisent l'injection ; la concaténation, jamais.

Vocabulaire de la section

SQL / relationnel
Bases en tables, lignes et clés étrangères (PostgreSQL, MySQL) ; schéma strict et transactions ACID.

Exemple simple : Clients, commandes et lignes liés chez Salim.

Ce que ce n'est pas : des documents flexibles sans schéma.

Réflexe : le choisir pour données liées et critiques.

SQL (langage)
SELECT (lire), INSERT (créer), UPDATE, DELETE, JOIN (combiner des tables liées).

Exemple simple : SELECT avec JOIN commandes-clients de Salim.

Ce que ce n'est pas : des appels de méthodes d'ORM.

Réflexe : savoir lire un SELECT/JOIN même avec un ORM.

Injection SQL
Faille critique : une entrée concaténée devient du code exécuté ; se protège par paramétrage.

Exemple simple : Un guillemet piégé neutralisé par $1.

Ce que ce n'est pas : une simple erreur d'affichage sans gravité.

Réflexe : ne jamais concaténer, toujours paramétrer.

ORM (Prisma, Sequelize)
Mappe tables ↔ objets JS et génère le SQL : méthodes lisibles, sûres et productives.

Exemple simple : prisma.client.findMany({ where }) chez Salim.

Ce que ce n'est pas : du SQL brut concaténé à la main.

Réflexe : privilégier l'ORM pour le CRUD courant.

Prisma (moderne)
ORM à schéma déclaratif (schema.prisma), migrations versionnées et client généré typé.

Exemple simple : La migration qui ajoute la table lignes.

Ce que ce n'est pas : un driver brut sans modèle ni type.

Réflexe : versionner chaque évolution par une migration.

En pratique — Persister avec une base SQL et un ORM

  1. Comprenez le modèle relationnel : tables, colonnes typées, lignes, relations par clés étrangères ; et les opérations SQL de base (SELECT, INSERT, UPDATE, DELETE, JOIN).
  2. Retenez l'impératif de sécurité : ne JAMAIS concaténer des données utilisateur dans du SQL (injection) — requêtes paramétrées ou ORM.
  3. Découvrez un ORM moderne : définissez un schéma (Prisma : schema.prisma) et faites un CRUD via des méthodes (prisma.user.create/findMany/update/delete).
  4. Explorez les migrations Prisma (versionner l'évolution du schéma) et l'intérêt du client typé (autocomplétion, sécurité des types avec TypeScript).
Vous savez persister avec une base relationnelle (PostgreSQL/MySQL) via un ORM moderne (Prisma), en écrivant des requêtes sûres — complétant votre maîtrise des deux familles de bases.

Script prêt à dire — présenter son SQL sûr (30 s)

« La facturation est en PostgreSQL : tables clients, commandes et lignes liées par clés étrangères. Le CRUD passe par Prisma, schéma déclaratif plus migrations, client typé. Le SQL brut restant est paramétré en $1, jamais concaténé : testé avec une entrée piégée, l'injection est neutralisée. »

À dire en revue sécu ; montrer le schéma et une requête paramétrée.

Modèle copier-coller — Prisma + requête paramétrée

// schema.prisma (extrait factice) :
// model Client { id Int @id @default(autoincrement())
//   nom String
//   email String @unique
//   commandes Commande[] }
async function main() {
  const nouveau = await prisma.client.create({
    data: { nom: 'Salim', email: 'salim@exemple-fictif.test' }
  });
  const liste = await prisma.client.findMany({ where: { actif: true } });
  await prisma.client.update({
    where: { email: 'salim@exemple-fictif.test' },
    data: { actif: false }
  });
  // Rappel brut SÛR (pg) — jamais de concaténation :
  // await pool.query('SELECT * FROM clients WHERE email = $1', [emailSaisi]);
}
main().catch(console.error);

Indépendant

Facturer en PostgreSQL + Prisma : trois tables liées et un CRUD typé suffisent.

PME à Alger

Salim versionne chaque ajout de colonne par une migration relue avant déploiement.

Équipe produit

Imposer le client typé partagé pour des requêtes cohérentes entre équipes.

Débutant

Cette semaine : un modèle Prisma, un create et un findMany avec where.

Prudence : Données et accès SQL : travaillez sur une base locale ou de test avec des données factices (emails en @exemple-fictif.test), ne collez jamais de mot de passe ni de chaîne de connexion réelle dans le code ou les captures, et donnez à chaque rôle le minimum de droits. Toute entrée utilisateur va en paramètre ($1, ORM), jamais concaténée. En cas de doute — secret exposé ou requête concaténée — stoppez, purgez le secret et régénérez-le avant de continuer.

✓ Bonnes pratiques

  • Utiliser un ORM moderne (Prisma) pour la productivité et la sécurité.
  • Toujours des requêtes paramétrées (jamais de concaténation) contre l'injection SQL.
  • Comprendre le SQL sous-jacent et charger les relations efficacement (éviter le N+1).

✗ Pièges fréquents

  • Concaténer des données utilisateur dans une requête SQL (injection critique).
  • Traiter l'ORM comme une boîte noire (requêtes inefficaces, N+1).
  • Ignorer les bases du SQL en se reposant entièrement sur l'ORM.

Points clés à retenir

  • SQL / RELATIONNEL (PostgreSQL, MySQL) : données en TABLES/LIGNES avec relations par CLÉS ÉTRANGÈRES ; garanties fortes (ACID).
  • Langage SQL : SELECT/INSERT/UPDATE/DELETE + JOIN. Sécurité CRITIQUE : jamais concaténer des données utilisateur (INJECTION SQL).
  • Un ORM (PRISMA, Sequelize) mappe tables ↔ objets et génère le SQL : lisible, sûr contre les injections, productif.
  • PRISMA : schéma déclaratif + migrations + client TYPÉ. Maîtriser SQL + NoSQL = boîte à outils de persistance complète.

Défi 48h — appliquez demain

Déclarez 1 modèle Prisma et faites un create + findMany, puis réécrivez un SELECT en paramétré $1.

Checklist sortie de section
  • Je cite tables, clés étrangères et ACID.
  • Mon SQL brut est paramétré, jamais concaténé.
  • Mon CRUD Prisma passe par schéma + migration.
Auto-évaluation 1-5 : mes requêtes brutes sont-elles toutes paramétrées ?sais-je expliquer le SQL généré par mon ORM ?

Questions fréquentes

Driver SQL brut ou ORM (Prisma/Sequelize) : que choisir ?

Pour la plupart des applis, un ORM : productivité (prisma.user.create() concis), sécurité (paramétrage automatique anti-injection), lisibilité et relations simples. Le driver brut (pg) garde sa place pour le contrôle fin et les requêtes pointues. Salim combine : ORM au quotidien, SQL paramétré pour les rapports JOIN complexes.

Qu'est-ce que l'injection SQL et comment s'en protéger ?

Une entrée utilisateur concaténée dans du SQL peut devenir du code exécuté (contournement d'auth, lecture ou destruction de données). Exemple du danger : ' OR '1'='1 glissé dans un email. Protection : requêtes paramétrées (placeholders $1 séparant code et données) et ORM qui paramètrent par défaut. Jamais de concaténation, même en test — c'est un savoir de sécurité fondamental.

Vérifiez votre compréhension

Quelle pratique protège d'une injection SQL en Node ?

Ressources supplémentaires (optionnel, pour aller plus loin)

Examen du niveau 3Questions inédites et durcies pour valider vos acquis