• 1. architecture multitenant PostgreSQL (RLS) avec Node.js et TypeORM : guide pas à pas

  • 1.1. Pourquoi choisir RLS pour un SaaS partagé (shared-schema)

  • 1.2. Stratégie retenue

  • 1.3. Étape 1 — modifier le schéma et activer RLS

  • 1.4. Étape 2 — intégrer côté Node.js + TypeORM (pattern transaction + QueryRunner)

  • 1.5. Erreurs fréquentes et debugging

  • 1.6. Performance et montée en charge

  • 1.7. Sécurité complémentaire

  • 1.8. Alternatives & choix

  • 1.9. Ressources officielles et lectures complémentaires

  • 1.10. Conclusion — checklist rapide avant mise en production

architecture multitenant PostgreSQL (RLS) avec Node.js et TypeORM : guide pas à pas pour un SaaS sécurisé

Image de architecture multitenant PostgreSQL (RLS) avec Node.js et TypeORM : guide pas à pas pour un SaaS sécurisé

architecture multitenant PostgreSQL (RLS) avec Node.js et TypeORM : guide pas à pas

Vous développez un SaaS et vous hésitez entre isolation par base, par schéma ou partage de schéma ? Cet article technique explique comment implémenter une isolation sécurisée et performante avec PostgreSQL Row Level Security (RLS) et une intégration propre côté Node.js / TypeORM. Public : CTO, lead dev et développeurs backend. Résultat attendu : à la fin vous saurez activer RLS, écrire des policies sûres, et intégrer RLS dans TypeORM sans risque de fuite de données entre locataires.

Pourquoi choisir RLS pour un SaaS partagé (shared-schema)

RLS déplace la responsabilité d'isolation de l'application vers la base de données : PostgreSQL filtre les lignes au niveau du moteur, quelle que soit la source de la requête (ORM, SQL direct, migrations). C’est un garde-fou contre les oublis "where tenant_id = ..." dans le code. La fonctionnalité est native à PostgreSQL et activée table par table via ALTER TABLE ... ENABLE ROW LEVEL SECURITY. ([postgresql.org](https://www.postgresql.org/docs/17/ddl-rowsecurity.html?utm_source=openai))

Stratégie retenue

  1. Schéma partagé (une table par entité) avec colonne tenant_id.
  2. RLS + policies basées sur une variable de session définie par l’application (ex. app.current_tenant).
  3. Côté Node.js/TypeORM : démarquer une transaction par requête (QueryRunner), exécuter SELECT set_config(..., true) puis toutes les requêtes utilisent la même session.

Cette technique évite de multiplier les connexions/chaînes de connexion et fonctionne avec la plupart des ORMs parce que l’application n’a plus besoin d’ajouter manuellement des WHERE tenant_id à chaque requête. Les fonctions système set_config / current_setting sont prévues pour cela. ([postgresql.org](https://www.postgresql.org/docs/19/functions-admin.html?utm_source=openai))

Étape 1 — modifier le schéma et activer RLS

Exemple SQL minimal : créer la colonne tenant_id, activer RLS et définir une policy de lecture/écriture.

-- ajout de la colonne
ALTER TABLE orders ADD COLUMN tenant_id int NOT NULL;

-- index pour la performance
CREATE INDEX idx_orders_tenant_id ON orders(tenant_id);

-- activer RLS
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

-- policy qui permet accès si tenant_id correspond à la variable de session app.current_tenant
CREATE POLICY orders_isolation ON orders
  USING (tenant_id = current_setting('app.current_tenant', true)::int)
  WITH CHECK (tenant_id = current_setting('app.current_tenant', true)::int);

Remarques :

  • On utilise current_setting('app.current_tenant', true) pour éviter une erreur si la variable n’existe pas (retourne NULL si second paramètre à true). ([postgresql.org](https://www.postgresql.org/docs/19/functions-admin.html?utm_source=openai))
  • Le WITH CHECK protège les INSERT/UPDATE pour empêcher qu’un client crée ou modifie une ligne pour un autre tenant. ([postgresql.org](https://www.postgresql.org/docs/17/ddl-rowsecurity.html?utm_source=openai))

Étape 2 — intégrer côté Node.js + TypeORM (pattern transaction + QueryRunner)

Principe : pour chaque requête HTTP, créer un QueryRunner (connexion dédiée depuis le pool), démarrer une transaction, définir la variable de session via set_config (local à la transaction) puis exécuter la logique métier via ce QueryRunner. À la fin commit/rollback et release. Ce pattern évite les fuites de variable entre requêtes quand on utilise un pool. ([dev.typeorm.io](https://dev.typeorm.io/docs/query-runner/?utm_source=openai))

// middleware TypeScript / Express (esquisse)
import { DataSource } from 'typeorm';

export function tenantMiddleware(dataSource: DataSource) {
  return async (req, res, next) => {
    const tenantId = req.headers['x-tenant-id'] || getTenantFromJwt(req);
    const queryRunner = dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();
    try {
      // set_config(..., true) : local to this transaction
      await queryRunner.query(
        "SELECT set_config('app.current_tenant', $1, true)", [String(tenantId)]
      );

      // attacher le manager au request pour utilisation dans services
      (req as any).dbManager = queryRunner.manager;
      await next();
      await queryRunner.commitTransaction();
    } catch (err) {
      await queryRunner.rollbackTransaction();
      throw err;
    } finally {
      await queryRunner.release();
    }
  };
}

Points clés :

  • Utiliser set_config('app.current_tenant', ..., true) pour que la valeur soit locale à la transaction et remise à zéro après commit/rollback. ([postgresql.org](https://www.postgresql.org/docs/19/functions-admin.html?utm_source=openai))
  • Passer systématiquement par queryRunner.manager pour que toutes les opérations utilisent la même connexion/transaction. ([dev.typeorm.io](https://dev.typeorm.io/docs/query-runner/?utm_source=openai))
  • Attention à ne pas appeler des librairies ou middlewares qui ouvrent d’autres connexions en parallèle (elles n’auront pas la variable de session).

Erreurs fréquentes et debugging

  • Oubli de ALTER TABLE ... ENABLE ROW LEVEL SECURITY : la policy ne s'applique pas. Vérifier avec pg_rows ou la doc. ([postgresql.org](https://www.postgresql.org/docs/17/ddl-rowsecurity.html?utm_source=openai))
  • current_setting error : si vous n’utilisez pas le paramètre true sur current_setting, une variable absente provoque une erreur. Utilisez current_setting('app.current_tenant', true).
  • Utiliser un pool sans pattern transaction/QueryRunner peut laisser des connexions réutilisées sans variable définie. Toujours set_config sur la connexion avant les requêtes du tenant. ([telerik.com](https://www.telerik.com/blogs/how-to-build-multi-tenant-saas-api-nestjs-postgres-row-level-security?utm_source=openai))
  • Politiques trop complexes empêchent l'optimiseur d'utiliser certains index : privilégiez des tests simples (égalité sur tenant_id) et indexez la colonne. Voir les sections performance dans la doc Postgres. ([postgresql.org](https://www.postgresql.org/docs/17/ddl-rowsecurity.html?utm_source=openai))

Performance et montée en charge

Bonnes pratiques :

  • Indexer tenant_id et les colonnes utilisées dans les requêtes. Les partial indexes peuvent aider si un petit pourcentage de tenants réalise des requêtes lourdes.
  • Pour des tables à centaines de millions de lignes ou des tenants “hot”, envisagez la partition par tenant ou la séparation par base pour les très gros comptes. RLS reste pratique et simple pour la majorité des cas, mais la partition permet de réduire le scan. Ne changez d’architecture que quand vous avez des métriques claires.
  • Mesurez : latence moyenne des requêtes, taux de hit du cache, IOPS et temps de verrouillage. Automatiser les tests de charge sur une version identique à prod est indispensable.

Sécurité complémentaire

Quelques points de durcissement :

  • Ne donnez pas à l’utilisateur de la connexion base de données le droit BYPASSRLS. Si un rôle a BYPASSRLS, il contourne les policies. ([postgresql.org](https://www.postgresql.org/docs/17/ddl-rowsecurity.html?utm_source=openai))
  • Surveillez et auditez les fonctions SECURITY DEFINER si vous en utilisez pour la mise en place d’un helper ; elles peuvent contourner RLS si mal codées.
  • Tests d’intégration : simulez requêtes malveillantes pour vérifier qu’un tenant ne peut ni lire ni écrire sur un autre tenant.

Alternatives & choix

RLS (shared schema) est excellent pour commencer et réduit la dette technique. Pour des clients très sensibles ou des besoins de scalabilité extrême, considérez :

  • Schéma par tenant (plus simple à isoler mais plus lourd à opérer).
  • Base par tenant (isolation totale, meilleure pour conformité, mais coûteuse en orchestration).

Ressources officielles et lectures complémentaires

  • PostgreSQL — Row Security Policies (doc officielle). ([postgresql.org](https://www.postgresql.org/docs/17/ddl-rowsecurity.html?utm_source=openai))
  • PostgreSQL — set_config / current_setting (fonctions système). ([postgresql.org](https://www.postgresql.org/docs/19/functions-admin.html?utm_source=openai))
  • Guide pratique d’intégration RLS + Node/ORM : articles tutoriels (ex. NestJS + RLS) montrant le pattern transaction + set_config. ([telerik.com](https://www.telerik.com/blogs/how-to-build-multi-tenant-saas-api-nestjs-postgres-row-level-security?utm_source=openai))
  • TypeORM — QueryRunner et transactions pour attacher une connexion/transaction à la requête. ([dev.typeorm.io](https://dev.typeorm.io/docs/query-runner/?utm_source=openai))

Conclusion — checklist rapide avant mise en production

  1. Tables avec tenant_id indexée et ALTER TABLE ... ENABLE ROW LEVEL SECURITY.
  2. Policies USING et WITH CHECK basées sur current_setting(..., true).
  3. Middleware/Intercepteur créant un QueryRunner/transaction par requête et exécutant SELECT set_config('app.current_tenant', $1, true) avant toute requête métier.
  4. Tests d’intégration & simulations d’attaque pour valider l’isolation.
  5. Surveillance des plans et index, décisions de partitionnement si nécessaire.

Ce pattern vous protège contre la majorité des fuites de données entre locataires tout en restant opérable pour un SaaS classique. Pour un accompagnement technique ou une revue d’architecture RLS / multitenant sur votre projet, vous pouvez en savoir plus sur nos services SaaS et demander une expertise via nos services SaaS ou nous contacter. Vous pouvez aussi consulter nos réalisations pour voir des cas concrets. Réalisations

Call to action discret : Besoin d’une revue d’architecture multitenant ? Obtenez un diagnostic rapide.

Image de Zapier vs Make vs n8n en 2026 : lequel choisir pour automatiser votre PME sans exploser le budget

Zapier vs Make vs n8n en 2026 : lequel choisir pour automatiser votre PME sans exploser le budget

Comparatif 2026 : Zapier, Make ou n8n pour PME, qui choisir, checklist, plan d'action et comment automatiser sans exploser le budget
Image de GhostLock (CVE-2026-43499) : que doivent décider les dirigeants de SaaS et d’ERP après la divulgation d’un root/exploit conteneur (juillet 2026)

GhostLock (CVE-2026-43499) : que doivent décider les dirigeants de SaaS et d’ERP après la divulgation d’un root/exploit conteneur (juillet 2026)

GhostLock (CVE-2026-43499) : guide pour dirigeants SaaS/ERP — impacts, priorités 48-72h, checklist patching, isolation conteneurs et communication client.
Image de Comment implémenter l’authentification et l’autorisation pour un assistant IA dans votre ERP/CRM (OIDC + RBAC, exemple Node.js)

Comment implémenter l’authentification et l’autorisation pour un assistant IA dans votre ERP/CRM (OIDC + RBAC, exemple Node.js)

Guide pratique pour sécuriser un assistant IA dans votre ERP/CRM : OIDC + RBAC expliqué pas à pas avec exemple Node.js, vérif JWT/JWKS et multitenant.
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