• 1. implémenter la multitenancy PostgreSQL (schema per tenant) dans un SaaS Node.js

  • 1.1. Pourquoi choisir "schema per tenant" ? (rappel rapide)

  • 1.2. Architecture ciblée

  • 1.3. Étapes pratiques

  • 1.4. Erreurs fréquentes et comment les corriger

  • 1.5. Bonnes pratiques sécurité & confidentialité

  • 1.6. Comparaison rapide des approches

  • 1.7. Conseils de performance

  • 1.8. Ressources officielles utiles

  • 1.9. Conclusion

implémenter la multitenancy PostgreSQL (schema per tenant) dans un SaaS Node.js — guide technique

Image de implémenter la multitenancy PostgreSQL (schema per tenant) dans un SaaS Node.js — guide technique

implémenter la multitenancy PostgreSQL (schema per tenant) dans un SaaS Node.js

Ce guide technique montre comment implémenter une stratégie "schema per tenant" avec PostgreSQL et Node.js : résolution de tenant, routage des connexions, exécution de migrations par tenant, et bonnes pratiques performance/sécurité. Public : CTO / lead dev qui doit livrer un SaaS multitenant solide et maintenable.

Pourquoi choisir "schema per tenant" ? (rappel rapide)

La séparation par schéma offre un compromis entre isolation (données séparées) et économie de ressources (même instance PostgreSQL). Avantages : sauvegardes / restauration par tenant, évolutions de schéma locales, moins d'impact sur requêtes cross-tenant. Inconvénients : attention au pool de connexions, complexité des migrations et opérations DBA. Comparez aussi avec row-level tenancy (colonne tenant_id) ou base par tenant selon vos contraintes.

Architecture ciblée

  • Backend : Node.js (Express ou équivalent)
  • DB : PostgreSQL unique, schémas distincts par tenant
  • Pool : PgBouncer / connection pooling (recommandé)
  • Migrations : exécution par tenant via script ou outil de migration

Étapes pratiques

1) Définir la résolution de tenant

Décidez comment identifier le tenant : sous-domaine (tenant.example.com), header HTTP (X-Tenant-ID), ou token dans JWT. Exemple : sous-domaine. Mettez en place un middleware qui extrait l'identifiant tenant et le charge (ex : tenant = "acme_inc").

// express middleware simple (résolution par host)
function tenantResolver(req, res, next) {
  const host = req.hostname; // ex: acme.example.com
  const tenant = host.split('.')[0];
  if (!tenant) return res.status(400).send('tenant missing');
  req.tenant = tenant;
  next();
}

2) Stratégie de connexion : SET search_path

Pour isoler les requêtes, on peut définir le search_path de la session PostgreSQL au schéma du tenant :

-- SQL (exécuté sur la connexion/session)
SET search_path TO tenant_acme, public;

En Node.js, utilisez node-postgres (pg) : acquérir un client, exécuter SET search_path, puis vos requêtes. Exemple :

const { Pool } = require('pg');
const pool = new Pool({ connectionString: process.env.PG });

async function withTenant(client, tenant, fn) {
  await client.query('BEGIN');
  await client.query('SET search_path TO $1, public', [tenant]);
  try {
    const result = await fn(client);
    await client.query('COMMIT');
    return result;
  } catch (err) {
    await client.query('ROLLBACK');
    throw err;
  }
}

// utilisation dans une route
app.get('/items', async (req, res) => {
  const tenant = `tenant_${req.tenant}`; // convention de nommage
  const client = await pool.connect();
  try {
    const rows = await withTenant(client, tenant, async (c) => {
      const r = await c.query('SELECT * FROM items WHERE archived = false');
      return r.rows;
    });
    res.json(rows);
  } finally {
    client.release();
  }
});

Tip : ne pas concaténer le nom de schéma directement dans SQL non paramétré (risque d'injection). Validez le nom de schéma côté application contre une liste autorisée.

3) Gérer le pool de connexions et PgBouncer

Problème classique : pour chaque session vous changez le search_path ; un pool “session-stateful” peut réutiliser une connexion avec search_path déjà modifié. Solutions :

  • Utiliser PgBouncer en mode transaction pooling (évite fuite d’état de session). Voir la doc officielle PgBouncer pour options.
  • Réinitialiser explicitement le search_path au début de chaque transaction (comme dans withTenant). Toujours exécuter SET search_path à chaque prise de client.

4) Migrations : comment appliquer des migrations par tenant

On distingue deux familles :

  • Migrations globales (tables communes au public) : exécuter une fois.
  • Migrations tenant-specific (création de tables dans chaque schéma) : exécuter pour chaque nouveau tenant.

Exemple minimal de script Node.js pour exécuter un fichier SQL de migration dans chaque schéma :

const fs = require('fs');
const { Pool } = require('pg');
const pool = new Pool({ connectionString: process.env.PG });

async function applyMigrationToTenant(tenant, sql) {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    await client.query('SET search_path TO $1, public', [tenant]);
    await client.query(sql);
    await client.query('COMMIT');
  } catch (e) {
    await client.query('ROLLBACK');
    throw e;
  } finally {
    client.release();
  }
}

(async () => {
  const tenants = ['tenant_acme','tenant_foo']; // recuperer dynamiquement
  const sql = fs.readFileSync('./migrations/2026__create_items_table.sql', 'utf8');
  for (const t of tenants) {
    await applyMigrationToTenant(t, sql);
    console.log('migrated', t);
  }
})();

Alternatives : certains outils de migration (Flyway, Sqitch) permettent d'automatiser l'exécution par schéma. Dans tous les cas, testez le rollback et la reprise en cas d'échec.

Erreurs fréquentes et comment les corriger

  • Erreur : résultats "vides" ou tables introuvables — vérifiez le search_path et que la migration a été appliquée au schéma attendu.
  • Fuites de données cross-tenant — cause : connexion réutilisée sans RESET du search_path. Toujours SET search_path à l’entrée de transaction.
  • Pool saturé — cause : trop de connexions simultanées (chaque tenant augmente la charge). Solution : PgBouncer, réduire connexions par app, batcher tâches longues.

Bonnes pratiques sécurité & confidentialité

  • Validez et normalisez les identifiants de schéma côté serveur (pas d’entrée directe depuis l’URL sans validation).
  • Utilisez des requêtes paramétrées pour éviter l’injection SQL. Voir les recommandations OWASP sur les injections SQL : OWASP - SQL Injection.
  • Limitez les permissions des rôles PostgreSQL : le rôle applicatif ne devrait pouvoir opérer que dans le schéma attendu.
  • Journalisation et audit : loggez les opérations critiques par tenant pour faciliter incident response.

Comparaison rapide des approches

ApprocheIsolationComplexité migrationsScalabilité
Schema per tenantMoyenne à élevéeMoyenne (exécution par schéma)Bonne si pool géré
Row-level (tenant_id)FaibleFaibleTrès bonne (moins de pools)
DB per tenantTrès élevéeÉlevéeCoûteuse à grande échelle

Conseils de performance

  • Indexez sur les colonnes utilisées fréquemment et rebâtissez les index par schéma si nécessaire.
  • Surveillez la latence de connexion et le taux de churn des connexions : les outils APM et les métriques PgBouncer sont utiles.
  • Pour des tâches batch/ETL, utilisez des connexions dédiées pour éviter d’affecter la latence des requêtes utilisateurs.

Ressources officielles utiles

  • PostgreSQL — schémas et search_path : documentation PostgreSQL.
  • PgBouncer (connection pooling) : consultez la doc officielle de PgBouncer pour le mode transaction pooling.

Conclusion

La stratégie "schema per tenant" est solide pour un SaaS qui recherche un bon compromis entre isolation et coût opérationnel. Les points critiques : résolution fiable du tenant, SET search_path systématique, gestion fine du pool et procédures de migration. En adoptant ces patterns vous aurez une base maintenable et évolutive pour votre produit.

Pour approfondir l’architecture de votre SaaS (choix multi-tenant, migrations, déploiement), voyez nos pages techniques sur Node.js et PostgreSQL, ou découvrez nos services développement SaaS.

Besoin d’un audit technique ou d’un prototype multitenant ? Contactez-nous discrètement : novane — contact.

Image de CRM pour PME : 7 signaux qui montrent qu'il est temps de s'équiper

CRM pour PME : 7 signaux qui montrent qu'il est temps de s'équiper

CRM pour PME : 7 signaux concrets qui montrent qu'il est temps de structurer votre suivi commercial, centraliser vos données et mieux piloter votre activité.
Image de HubSpot CRM : connecter vos outils métiers via API, intégrations natives et solutions sur-mesure

HubSpot CRM : connecter vos outils métiers via API, intégrations natives et solutions sur-mesure

HubSpot CRM et intégrations : comment connecter vos outils métiers via API, connecteurs natifs, Zapier, Make ou développement sur-mesure pour fiabiliser vos process.
Image de 10 tactiques concrètes pour augmenter le MRR d’un SaaS sans budget pub en 2026

10 tactiques concrètes pour augmenter le MRR d’un SaaS sans budget pub en 2026

10 tactiques actionnables et testables en quelques jours pour augmenter le MRR d’un SaaS sans pub en 2026 : onboarding, micro-offres, rétention, parrainage
DEVIS GRATUIT

Un projet en tête ? Vous avez des questions ?

Contactez nous pour recevoir un devis gratuitement, des réponses à vos questions ou une séance de consulting offerte avec l'un de nos experts :

Nous contacter