1. Expliquez l'event loop de Node.js et ses différentes phases.
L'event loop est le cœur de Node.js, permettant les opérations asynchrones non-bloquantes sur un seul thread. Il comporte six phases : Timers (exécution des callbacks setTimeout/setInterval), Pending callbacks (callbacks d'I/O reportes), Idle/Prepare (usage interne), Poll (recuperation des événements I/O, exécution des callbacks), Check (exécution des callbacks setImmediate), Close (callbacks de fermeture comme socket.on close). Chaque phase à une file FIFO de callbacks. Entre chaque phase, Node execute les microtasks (process.nextTick, Promises resolues). process.nextTick a priorité sur les Promises.
2. Quelle est la différence entre require() et import en Node.js ?
require() est le système CommonJS (CJS) historique de Node. Il est synchrone, charge les modules au runtime, et les met en cache. import/export est le système ECMAScript Modules (ESM), standardise. Il est asynchrone, permet le tree-shaking, et supporte les imports nommés. Pour utiliser ESM : ajouter "type": "module" dans package.json ou utiliser l'extension .mjs. CJS utilise .cjs. Les deux systèmes ne sont pas directement interchangeables : on peut importer du CJS depuis ESM, mais pas l'inverse sans import() dynamique. La tendance est vers ESM pour les nouveaux projets.
3. Comment gérer les erreurs dans une application Node.js ?
Stratégie multi-niveaux : try/catch pour le code synchrone et async/await. Callbacks avec le pattern error-first (premier argument est l'erreur). Promises avec .catch() ou try/catch autour d'await. Event emitters avec l'événement error. Erreurs non capturees : process.on('uncaughtException') et process.on('unhandledRejection') comme filet de sécurité, mais il faut redemarrer le processus après. Utiliser des classes d'erreurs personnalisées pour categoriser (ValidationError, NotFoundError, AuthError). En Express, utiliser un middleware d'erreur centralise (4 paramètres : err, req, res, next). Logger toutes les erreurs avec un outil structure.
4. Expliquez le concept de streams en Node.js.
Les streams permettent de traiter des données par morceaux (chunks) plutôt que de tout charger en mémoire. Quatre types : Readable (lecture, ex: fs.createReadStream), Writable (écriture, ex: fs.createWriteStream), Duplex (lecture et écriture, ex: TCP socket), Transform (modification des données en transit, ex: zlib compression). Les streams implementent le pattern backpressure : si le consommateur est plus lent que le producteur, le flux est mis en pause automatiquement. On peut chaîner les streams avec pipe(). Essentiels pour traiter de gros fichiers, des flux réseau ou des transformations de données sans exploser la mémoire.
5. Comment fonctionne le clustering en Node.js ?
Le module cluster permet de créer des processus worker qui partagent le même port serveur. Le processus master fork des workers (généralement un par cœur CPU). Deux modes de distribution : round-robin (par défaut sur Linux/Mac) ou via le système d'exploitation. Chaque worker est un processus Node indépendant avec sa propre mémoire et event loop. La communication inter-processus se fait par IPC (process.send/process.on message). En production, privilegier PM2 qui gere le clustering, le redémarrage automatique, le zero-downtime reload, et le monitoring. Alternative moderne : les worker threads pour le parallélisme CPU-intensif dans un même processus.
6. Qu'est-ce que le middleware pattern dans Express.js ?
Un middleware Express est une fonction avec accès à la requête (req), la réponse (res) et la fonction next(). Les middleware s'exécutent dans l'ordre d'enregistrement et peuvent : modifier req/res, exécuter du code, terminer le cycle requete-reponse, ou appeler next() pour passer au middleware suivant. Types : middleware d'application (app.use), de routeur (router.use), d'erreur (4 params), tiers (cors, helmet, morgan). Le pattern permet de decomposer le traitement en couches : logging, authentification, validation, parsing du body, gestion des erreurs. L'ordre est crucial : authentification avant les routes protégées, erreurs en dernier.
7. Comment optimiser les performances d'une application Node.js ?
Optimisations clés : profiling avec clinic.js ou le profiler V8 integre pour identifier les bottlenecks. Caching avec Redis pour les données fréquemment accédées. Connection pooling pour les bases de données. Éviter les opérations synchrones (fs.readFileSync) en production. Utiliser les streams pour les gros volumes de données. Compression gzip/brotli des réponses. Load balancing avec cluster ou un reverse proxy (Nginx). Éviter les memory leaks (closures, event listeners non supprimes, caches sans limite). Utiliser les worker threads pour les tâches CPU-intensives. Monitorer avec les métriques process (memory, CPU, event loop lag).
8. Expliquez la différence entre process.nextTick() et setImmediate().
process.nextTick() place le callback dans la file nextTick, exécutée après l'opération en cours et avant que l'event loop ne continue à la phase suivante. setImmediate() place le callback dans la phase Check de l'event loop. process.nextTick est donc execute en priorité. Attention : un usage excessif de process.nextTick peut bloquer l'event loop (I/O starvation) car les callbacks nextTick sont traites récursivement avant de passer à la phase suivante. setImmediate est généralement préfère pour le code asynchrone car il laisse l'event loop respirer. Les Promises resolues (.then) s'exécutent dans la file microtask, après nextTick.
9. Comment sécuriser une API Node.js/Express ?
Sécurité en couches : helmet pour les en-tetes de sécurité HTTP. Rate limiting avec express-rate-limit contre les attaques brute force. Validation des entrées avec Joi ou Zod (ne jamais faire confiance aux données client). Authentification avec JWT ou sessions sécurisées. CORS configure strictement. Protection CSRF pour les formulaires. Paramètres de requête sanitises contre l'injection NoSQL. HTTPS obligatoire en production. Dependencies vérifiées avec npm audit. Éviter d'exposer les stack traces en production (NODE_ENV=production). Logger les accès suspects. Utiliser des Content Security Policies strictes.
10. Comment gérer la scalabilité d'une application Node.js ?
Scalabilité horizontale et verticale : horizontale avec un load balancer (Nginx, HAProxy, ALB) distribuant le trafic entre plusieurs instances. Verticale avec le module cluster pour exploiter tous les coeurs CPU. Architecture microservices pour decouple les composants et les scaler indépendamment. Message queues (RabbitMQ, Redis, SQS) pour les tâches asynchrones et le decouplage. CDN pour les assets statiques. Base de données : read réplicas, sharding, caching Redis. Containerisation avec Docker et orchestration Kubernetes pour le déploiement et l'auto-scaling. Serverless (Lambda, Cloud Functions) pour les charges variables. Monitorer les métriques pour anticiper les besoins de scaling.