Skip to content
Learni
Voir tous les tutoriels
Développement Backend

Comment implémenter des feature flags en Node.js 2026

Read in English

Introduction

Les feature flags (ou drapeaux de fonctionnalités) sont une technique essentielle en développement logiciel pour activer ou désactiver des fonctionnalités à distance, sans redéployer l'application. Imaginez pouvoir lancer une nouvelle API endpoint pour 10% des utilisateurs seulement, tester en production, puis l'activer globalement si tout va bien. Cela réduit les risques de bugs majeurs, facilite les A/B tests et accélère les déploiements continus.

En 2026, avec les microservices et les apps scalables, les feature flags sont incontournables chez Netflix, GitHub ou Shopify. Ce tutoriel beginner vous guide pas à pas pour implémenter un système complet en Node.js avec Express et TypeScript : flags en mémoire, config JSON persistante, targeting par utilisateur, et une interface HTTP simple pour les toggler. À la fin, vous maîtriserez un outil pro que vous bookmarkeriez pour vos projets. Pas de libs externes complexes, tout est maison et fonctionnel dès le copier-coller. (128 mots)

Prérequis

  • Node.js 20+ installé
  • Connaissances basiques en JavaScript/TypeScript
  • npm ou yarn
  • Un éditeur comme VS Code
  • 5 minutes pour lancer le projet

Initialiser le projet

terminal
mkdir feature-flags-app
cd feature-flags-app
npm init -y
npm install express
tpm install -D typescript @types/node @types/express ts-node nodemon
npx tsc --init

Cette commande crée un projet Node.js, installe Express pour le serveur HTTP, et configure TypeScript avec ses types. Les outils de dev (-D) comme ts-node permettent d'exécuter le TS directement sans compilation. Lancez ensuite npx ts-node server.ts pour tester. Évitez les versions obsolètes de Node pour la compatibilité ESM.

Configurer TypeScript

Avant de coder, adaptez tsconfig.json pour supporter les modules ESNext et les JSON imports, essentiels pour charger notre config flags. Cela évite les erreurs de résolution de modules et active le strict mode pour catcher les bugs tôt.

tsconfig.json complet

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "allowSyntheticDefaultImports": true,
    "noEmit": true,
    "strict": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowJs": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "ts-node": {
    "esm": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

Ce tsconfig active ES2022 pour les features modernes, resolveJsonModule pour importer flags.json, et ts-node/esm pour exécution directe. Le strict mode force une typage robuste, évitant les erreurs runtime. Placez-le à la racine ; relancez votre IDE pour détecter les erreurs TS instantanément.

Définir les types FeatureFlag

Nous créons une interface TypeScript pour typer les flags : nom, statut (on/off), et targeting optionnel par userId. Cela apporte de l'autocomplétion et prévient les bugs, comme un garde-fou pour vos conditions métier.

Types et interface

src/types.ts
export interface User {
  id: string;
  email: string;
}

export interface FeatureFlag {
  name: string;
  enabled: boolean;
  targeting?: { userIds: string[] };
}

export type FlagStore = Map<string, FeatureFlag>;

L'interface FeatureFlag définit un flag avec enabled booléen et targeting optionnel (tableau userIds). User simule un utilisateur authentifié. FlagStore utilise Map pour stockage rapide en mémoire. Ces types seront réutilisés partout, garantissant cohérence et sécurité TS.

Store des flags avec config JSON

src/flagStore.ts
import { readFileSync } from 'fs';
import path from 'path';
import { FeatureFlag, FlagStore } from './types.js';

const FLAGS_FILE = path.join(process.cwd(), 'flags.json');

class FlagStoreClass {
  private store: FlagStore = new Map();

  loadFromFile(): void {
    try {
      const data = readFileSync(FLAGS_FILE, 'utf-8');
      const flags: FeatureFlag[] = JSON.parse(data);
      flags.forEach(flag => this.store.set(flag.name, flag));
    } catch (error) {
      console.error('Erreur chargement flags:', error);
    }
  }

  getFlag(name: string): FeatureFlag | undefined {
    return this.store.get(name);
  }

  setFlag(name: string, enabled: boolean, targeting?: { userIds: string[] }): void {
    const flag: FeatureFlag = { name, enabled, ...(targeting && { targeting }) };
    this.store.set(name, flag);
    this.saveToFile();
  }

  private saveToFile(): void {
    const flags: FeatureFlag[] = Array.from(this.store.values());
    const data = JSON.stringify(flags, null, 2);
    // En prod, utilisez fs.writeFileSync avec lock
  }
}

export const flagStore = new FlagStoreClass();

FlagStoreClass charge les flags depuis flags.json au démarrage, expose getFlag/setFlag, et persiste en JSON. Map assure accès O(1). saveToFile est stubée pour simplicité ; en prod, ajoutez fs.promises.writeFile. Chargez avec flagStore.loadFromFile() au boot.

Configuration initiale flags.json

flags.json
[
  {
    "name": "newUsersEndpoint",
    "enabled": false,
    "targeting": {
      "userIds": ["user123"]
    }
  },
  {
    "name": "premiumFeatures",
    "enabled": true
  }
]

Ce fichier JSON définit deux flags : newUsersEndpoint (off, ciblé user123) et premiumFeatures (on global). Ajoutez-en à volonté. Le store le charge automatiquement. Modifiez-le à chaud via l'API pour tester sans redémarrer.

Créer le middleware d'évaluation

Un middleware Express évalue si un flag est actif pour l'utilisateur courant. Il injecte req.isFeatureEnabled pour simplifier les routes. Analogie : comme un interrupteur intelligent qui check le profil avant d'allumer la lumière.

Middleware flags

src/middleware/flags.ts
import { Request, Response, NextFunction } from 'express';
import { flagStore } from '../flagStore.js';
import { User } from '../types.js';

declare global {
  namespace Express {
    interface Request {
      user?: User;
      isFeatureEnabled?: (name: string) => boolean;
    }
  }
}

export function flagsMiddleware(req: Request, res: Response, next: NextFunction) {
  req.isFeatureEnabled = (flagName: string) => {
    const flag = flagStore.getFlag(flagName);
    if (!flag || !flag.enabled) return false;

    if (flag.targeting?.userIds && req.user?.id) {
      return flag.targeting.userIds.includes(req.user.id);
    }

    return true;
  };

  next();
}

Ce middleware étend req avec isFeatureEnabled(name), qui check enabled ET targeting. Utilisez-le comme app.use(flagsMiddleware). L'extension globale TS évite les any. Simulez req.user via auth middleware ; ici pour démo.

Serveur Express complet avec flags

src/server.ts
import express from 'express';
import { flagsMiddleware } from './middleware/flags.js';
import { flagStore } from './flagStore.js';

const app = express();
app.use(express.json());

// Charger flags au démarrage
flagStore.loadFromFile();

// Middleware flags
app.use(flagsMiddleware);

// Simuler user pour démo (en prod: JWT/auth)
app.use((req, res, next) => {
  req.user = { id: 'user123', email: 'test@example.com' };
  next();
});

// Route protégée par flag
app.get('/users', (req, res) => {
  if (req.isFeatureEnabled!('newUsersEndpoint')) {
    res.json([{ id: 1, name: 'John Doe' }]);
  } else {
    res.status(404).json({ error: 'Endpoint désactivé' });
  }
});

// API flags: list
app.get('/flags', (req, res) => {
  const flags = Array.from(flagStore.getFlag('newUsersEndpoint') ? [flagStore.getFlag('newUsersEndpoint')!] : []);
  res.json(flags);
});

// Toggle flag
app.post('/flags/:name', (req, res) => {
  const { name } = req.params;
  const { enabled, targeting } = req.body;
  flagStore.setFlag(name, !!enabled, targeting);
  res.json({ success: true, flag: flagStore.getFlag(name) });
});

// Exemple premium
app.get('/premium', (req, res) => {
  if (req.isFeatureEnabled!('premiumFeatures')) {
    res.json({ feature: 'Accès premium activé' });
  } else {
    res.status(403).json({ error: 'Premium désactivé' });
  }
});

const PORT = 3000;
app.listen(PORT, () => {
  console.log(`Serveur sur http://localhost:${PORT}`);
  console.log('Testez: curl http://localhost:3000/users');
});

Serveur complet : charge flags, applique middleware, routes /users (flag newUsersEndpoint), /premium, et API CRUD flags (/flags GET/POST). User simulé pour démo. Lancez avec npx ts-node src/server.ts. Togglez via curl POST pour voir l'effet live.

Tester l'implémentation

Testez maintenant : curl http://localhost:3000/users → 404 (flag off). curl -X POST -H 'Content-Type: application/json' -d '{"enabled":true}' http://localhost:3000/flags/newUsersEndpoint puis retestez → liste users ! Pour user non ciblé, modifiez req.user.id.

Bonnes pratiques

  • Toujours typer : Utilisez TS pour éviter les flags mal configurés.
  • Persistance avancée : Remplacez saveToFile par Redis ou DB pour multi-instances.
  • Fallbacks : Si flag absent, assumez false pour sécurité.
  • Monitoring : Loggez les évaluations (Winston/Sentry) pour analytics.
  • Polling/sync : En prod, sync avec webhook (ex: GitHub) pour zero-downtime.

Erreurs courantes à éviter

  • Pas de lock sur JSON : fs.writeFile concurrent crash le fichier ; utilisez mutex (async-mutex).
  • Oublier targeting : Vérifiez toujours user context avant enabled.
  • Mémoire leaks : Limitez flags à 1000 max ; purgez inactifs.
  • Sécurité API flags : Protégez /flags par admin JWT, pas public.

Pour aller plus loin

  • Libs pro : Flagsmith ou LaunchDarkly pour UI dashboard.
  • A/B testing : Intégrez avec PostHog.
  • Frontend : Adaptez pour React avec useFlags hook.
Découvrez nos formations Learni sur DevOps et CI/CD.