# Questions d'entretien Node.js backend : Guide complet 2026 > Les 25 questions d'entretien Node.js backend les plus fréquentes. Event loop, async/await, streams, clustering et performance expliqués avec des réponses détaillées. - Published: 2026-01-22 - Updated: 2026-03-28 - Author: SharpSkill - Tags: nodejs, interview, backend, javascript, entretien technique - Reading time: 18 min --- Les entretiens Node.js backend évaluent la compréhension des mécanismes internes du runtime, la maîtrise de l'asynchrone et la capacité à concevoir des applications performantes. Ce guide couvre les questions les plus fréquentes, des fondamentaux jusqu'aux concepts avancés de production. > **Conseil pour l'entretien** > > Les recruteurs apprécient les réponses qui combinent théorie et exemples pratiques. Pour chaque question, illustrer avec du code ou un cas d'usage concret démontre une expérience réelle. ## Fondamentaux de Node.js ### Question 1 : Qu'est-ce que l'Event Loop et comment fonctionne-t-elle ? L'Event Loop est le mécanisme central qui permet à Node.js de gérer des opérations asynchrones de manière non-bloquante malgré son exécution single-threaded. Elle orchestre l'exécution du code JavaScript, des callbacks et des événements. ```javascript // event-loop-demo.js // Démonstration de l'ordre d'exécution de l'Event Loop console.log('1. Script start (synchrone)'); // setTimeout va dans la Timer Queue setTimeout(() => { console.log('5. setTimeout callback (Timer Queue)'); }, 0); // setImmediate va dans la Check Queue setImmediate(() => { console.log('6. setImmediate callback (Check Queue)'); }); // Promise va dans la Microtask Queue (prioritaire) Promise.resolve().then(() => { console.log('3. Promise.then (Microtask Queue)'); }); // process.nextTick a la plus haute priorité process.nextTick(() => { console.log('2. process.nextTick (nextTick Queue)'); }); console.log('4. Script end (synchrone)'); // Sortie dans l'ordre : 1, 4, 2, 3, 5, 6 ``` L'Event Loop suit un ordre précis de traitement des queues : d'abord le code synchrone, puis nextTick, les microtasks (Promises), les timers, les I/O callbacks, setImmediate, et enfin les close callbacks. ### Question 2 : Quelle est la différence entre process.nextTick() et setImmediate() ? Cette question évalue la compréhension fine des priorités d'exécution dans l'Event Loop. ```javascript // nextTick-vs-immediate.js // Comparaison des comportements // process.nextTick s'exécute AVANT la prochaine phase de l'Event Loop process.nextTick(() => { console.log('nextTick 1'); process.nextTick(() => { console.log('nextTick 2 (imbriqué)'); }); }); // setImmediate s'exécute dans la phase Check de l'Event Loop setImmediate(() => { console.log('setImmediate 1'); setImmediate(() => { console.log('setImmediate 2 (imbriqué)'); }); }); // Sortie : nextTick 1, nextTick 2, setImmediate 1, setImmediate 2 ``` `process.nextTick()` est traité immédiatement après l'opération courante, avant que l'Event Loop ne continue. Un usage excessif peut bloquer l'Event Loop. `setImmediate()` est plus prévisible et recommandé pour différer l'exécution. > **Attention au starvation** > > Un appel récursif à process.nextTick() peut affamer l'Event Loop et empêcher le traitement des I/O. Préférer setImmediate() pour les opérations non critiques. ### Question 3 : Comment Node.js gère-t-il les erreurs dans le code asynchrone ? La gestion des erreurs asynchrones diffère fondamentalement du code synchrone. Sans gestion appropriée, une erreur peut crasher l'application. ```javascript // error-handling.js // Patterns de gestion d'erreurs asynchrones // Pattern 1 : Callbacks avec error-first function readFileCallback(path, callback) { const fs = require('fs'); fs.readFile(path, 'utf8', (err, data) => { if (err) { // L'erreur est TOUJOURS le premier argument return callback(err, null); } callback(null, data); }); } // Pattern 2 : Promises avec catch async function readFilePromise(path) { const fs = require('fs').promises; try { const data = await fs.readFile(path, 'utf8'); return data; } catch (err) { // Gestion centralisée des erreurs console.error(`Erreur lecture fichier: ${err.message}`); throw err; // Re-throw pour propagation } } // Pattern 3 : Gestion globale des rejections non gérées process.on('unhandledRejection', (reason, promise) => { console.error('Rejection non gérée:', reason); // En production : logger et graceful shutdown }); // Pattern 4 : Gestion des exceptions non catchées process.on('uncaughtException', (err) => { console.error('Exception non catchée:', err); // CRITIQUE : toujours terminer le process après process.exit(1); }); ``` En production, chaque Promise doit avoir un `.catch()` ou être dans un bloc try/catch. Les gestionnaires globaux servent de filet de sécurité, pas de solution principale. ## Asynchrone et Concurrence ### Question 4 : Expliquez la différence entre parallélisme et concurrence en Node.js Node.js est concurrent mais pas parallèle par défaut. Cette distinction est fondamentale pour comprendre les performances. ```javascript // concurrency-vs-parallelism.js // CONCURRENCE : plusieurs tâches progressent en alternance (single-thread) async function concurrentTasks() { console.time('concurrent'); // Ces appels sont concurrents, pas parallèles const results = await Promise.all([ fetch('https://api.example.com/users'), // I/O non-bloquant fetch('https://api.example.com/products'), // I/O non-bloquant fetch('https://api.example.com/orders'), // I/O non-bloquant ]); console.timeEnd('concurrent'); // ~temps de la requête la plus longue return results; } // PARALLÉLISME : avec Worker Threads pour le CPU-bound const { Worker, isMainThread, parentPort } = require('worker_threads'); if (isMainThread) { // Le thread principal délègue le travail CPU-intensive async function parallelComputation() { console.time('parallel'); const workers = [ createWorker({ start: 0, end: 1000000 }), createWorker({ start: 1000000, end: 2000000 }), createWorker({ start: 2000000, end: 3000000 }), ]; const results = await Promise.all(workers); console.timeEnd('parallel'); return results.reduce((a, b) => a + b, 0); } function createWorker(data) { return new Promise((resolve, reject) => { const worker = new Worker(__filename, { workerData: data }); worker.on('message', resolve); worker.on('error', reject); }); } } else { // Code exécuté dans le Worker Thread const { workerData } = require('worker_threads'); let sum = 0; for (let i = workerData.start; i < workerData.end; i++) { sum += Math.sqrt(i); // Calcul CPU-intensive } parentPort.postMessage(sum); } ``` Pour les opérations I/O-bound (réseau, fichiers), la concurrence native suffit. Pour le CPU-bound (calculs lourds, crypto), Worker Threads permettent le vrai parallélisme. ### Question 5 : Comment fonctionne le module Cluster ? Le module Cluster permet de créer plusieurs processus Node.js partageant le même port, exploitant ainsi tous les cœurs CPU disponibles. ```javascript // cluster-example.js const cluster = require('cluster'); const http = require('http'); const numCPUs = require('os').cpus().length; if (cluster.isPrimary) { console.log(`Primary ${process.pid} is running`); console.log(`Forking ${numCPUs} workers...`); // Fork un worker par cœur CPU for (let i = 0; i < numCPUs; i++) { cluster.fork(); } // Gestion des workers qui crashent cluster.on('exit', (worker, code, signal) => { console.log(`Worker ${worker.process.pid} died (${signal || code})`); console.log('Starting a new worker...'); cluster.fork(); // Redémarrage automatique }); // Communication inter-processus cluster.on('message', (worker, message) => { console.log(`Message from worker ${worker.id}:`, message); }); } else { // Les workers partagent le port TCP http.createServer((req, res) => { res.writeHead(200); res.end(`Handled by worker ${process.pid}\n`); // Envoyer des stats au primary process.send({ type: 'request', pid: process.pid }); }).listen(8000); console.log(`Worker ${process.pid} started`); } ``` Le load balancing est géré automatiquement par le système d'exploitation (round-robin sur Linux/macOS). En production, PM2 simplifie cette gestion avec son mode cluster intégré. ## Streams et Buffers ### Question 6 : Quand utiliser les Streams plutôt que les méthodes classiques ? Les Streams permettent de traiter des données par morceaux (chunks) au lieu de charger l'intégralité en mémoire. Essentiels pour les fichiers volumineux et le streaming. ```javascript // streams-comparison.js const fs = require('fs'); // ❌ MAUVAIS : charge tout le fichier en mémoire async function readEntireFile(path) { const data = await fs.promises.readFile(path); // Bloque si fichier > RAM return processData(data); } // ✅ BON : traitement par chunks avec Stream function readWithStream(path) { return new Promise((resolve, reject) => { const chunks = []; const readStream = fs.createReadStream(path, { highWaterMark: 64 * 1024, // 64KB par chunk }); readStream.on('data', (chunk) => { // Traitement progressif, mémoire constante chunks.push(processChunk(chunk)); }); readStream.on('end', () => resolve(chunks)); readStream.on('error', reject); }); } // ✅ MEILLEUR : pipeline pour chaîner les transformations const { pipeline } = require('stream/promises'); const zlib = require('zlib'); async function compressFile(input, output) { await pipeline( fs.createReadStream(input), // Source zlib.createGzip(), // Transform fs.createWriteStream(output) // Destination ); // Gestion automatique des erreurs et du backpressure } ``` Utiliser les Streams dès que la taille des données peut dépasser quelques MB, ou pour le traitement temps réel (uploads, logs, données réseau). ### Question 7 : Expliquez le concept de backpressure Le backpressure survient quand le producteur de données est plus rapide que le consommateur. Sans gestion, la mémoire explose. ```javascript // backpressure-demo.js const fs = require('fs'); // ❌ Problème : pas de gestion du backpressure function badCopy(src, dest) { const readable = fs.createReadStream(src); const writable = fs.createWriteStream(dest); readable.on('data', (chunk) => { // Si write() retourne false, le buffer interne est plein // Mais ici on continue à lire quand même → memory leak writable.write(chunk); }); } // ✅ Solution : respecter le signal du writable function goodCopy(src, dest) { const readable = fs.createReadStream(src); const writable = fs.createWriteStream(dest); readable.on('data', (chunk) => { const canContinue = writable.write(chunk); if (!canContinue) { // Pause la lecture jusqu'à ce que le buffer se vide readable.pause(); } }); writable.on('drain', () => { // Buffer vidé, reprendre la lecture readable.resume(); }); readable.on('end', () => writable.end()); } // ✅ MEILLEUR : pipe() gère tout automatiquement function bestCopy(src, dest) { const readable = fs.createReadStream(src); const writable = fs.createWriteStream(dest); // pipe() gère le backpressure nativement readable.pipe(writable); } ``` La méthode `pipe()` ou `pipeline()` gère automatiquement le backpressure. Pour les cas complexes, implémenter la logique pause/resume manuellement. ## Performance et Optimisation ### Question 8 : Comment identifier et résoudre les fuites mémoire ? Les fuites mémoire sont fréquentes en Node.js. Savoir les détecter et les corriger est essentiel en production. ```javascript // memory-leak-patterns.js // ❌ Fuite 1 : closures qui retiennent des références function createLeakyHandler() { const hugeData = Buffer.alloc(100 * 1024 * 1024); // 100MB return function handler(req, res) { // hugeData reste en mémoire tant que handler existe res.end('Hello'); }; } // ✅ Correction : limiter la portée function createSafeHandler() { return function handler(req, res) { // Données créées et libérées à chaque requête const data = fetchData(); res.end(data); }; } // ❌ Fuite 2 : event listeners non nettoyés class LeakyClass { constructor() { // Ajout à chaque instanciation, jamais retiré process.on('message', this.handleMessage); } handleMessage(msg) { /* ... */ } } // ✅ Correction : cleanup explicite class SafeClass { constructor() { this.boundHandler = this.handleMessage.bind(this); process.on('message', this.boundHandler); } handleMessage(msg) { /* ... */ } destroy() { // Nettoyage obligatoire process.removeListener('message', this.boundHandler); } } // Diagnostic avec les outils natifs function diagnoseMemory() { const used = process.memoryUsage(); console.log({ heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)}MB`, heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)}MB`, external: `${Math.round(used.external / 1024 / 1024)}MB`, rss: `${Math.round(used.rss / 1024 / 1024)}MB`, }); } // Activer le garbage collector manuel pour les tests // node --expose-gc app.js if (global.gc) { global.gc(); diagnoseMemory(); } ``` En production, utiliser des outils comme `clinic.js`, les heap snapshots de Chrome DevTools, ou des APM (Application Performance Monitoring) comme DataDog ou New Relic. ### Question 9 : Comment optimiser les performances d'une API Node.js ? Cette question évalue la connaissance des techniques d'optimisation à plusieurs niveaux. ```javascript // performance-optimization.js // 1. CACHING : réduire les appels coûteux const NodeCache = require('node-cache'); const cache = new NodeCache({ stdTTL: 300 }); // TTL 5 minutes async function getCachedUser(id) { const cacheKey = `user:${id}`; let user = cache.get(cacheKey); if (!user) { user = await db.users.findById(id); cache.set(cacheKey, user); } return user; } // 2. CONNECTION POOLING : réutiliser les connexions DB const { Pool } = require('pg'); const pool = new Pool({ max: 20, // Connexions simultanées max idleTimeoutMillis: 30000, connectionTimeoutMillis: 2000, }); // 3. COMPRESSION : réduire la taille des réponses const compression = require('compression'); app.use(compression({ filter: (req, res) => { // Compresser seulement si > 1KB return compression.filter(req, res); }, threshold: 1024, })); // 4. BATCHING : grouper les opérations async function batchInsert(items) { const BATCH_SIZE = 1000; for (let i = 0; i < items.length; i += BATCH_SIZE) { const batch = items.slice(i, i + BATCH_SIZE); await db.items.insertMany(batch); } } // 5. LAZY LOADING : charger à la demande async function getUserWithPosts(userId, includePosts = false) { const user = await db.users.findById(userId); if (includePosts) { user.posts = await db.posts.findByUserId(userId); } return user; } ``` Les optimisations doivent être guidées par le profiling. Mesurer avant d'optimiser pour identifier les vrais goulots d'étranglement. > **Règle des 80/20** > > 80% des problèmes de performance viennent de 20% du code. Utiliser le profiling pour identifier ces zones critiques avant d'optimiser à l'aveugle. ## Sécurité ### Question 10 : Comment protéger une API Node.js contre les attaques courantes ? La sécurité est un sujet récurrent en entretien. Démontrer une connaissance des vulnérabilités OWASP est attendu. ```javascript // security-best-practices.js const express = require('express'); const helmet = require('helmet'); const rateLimit = require('express-rate-limit'); const mongoSanitize = require('express-mongo-sanitize'); const xss = require('xss-clean'); const app = express(); // 1. HEADERS DE SÉCURITÉ avec Helmet app.use(helmet()); // 2. RATE LIMITING contre les attaques brute-force const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // 100 requêtes par IP message: 'Too many requests, please try again later', standardHeaders: true, legacyHeaders: false, }); app.use('/api/', limiter); // 3. SANITIZATION contre les injections NoSQL app.use(mongoSanitize()); // 4. PROTECTION XSS app.use(xss()); // 5. VALIDATION stricte des entrées const { body, validationResult } = require('express-validator'); app.post('/api/users', [ body('email').isEmail().normalizeEmail(), body('password').isLength({ min: 8 }).escape(), body('name').trim().escape(), ], (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } // Continuer le traitement } ); // 6. PROTECTION contre les injections SQL (avec paramètres) async function safeQuery(userId) { // ✅ Requête paramétrée const result = await pool.query( 'SELECT * FROM users WHERE id = $1', [userId] ); return result.rows; } // ❌ JAMAIS de concaténation de chaînes async function unsafeQuery(userId) { // Vulnérable à l'injection SQL const result = await pool.query( `SELECT * FROM users WHERE id = ${userId}` ); } ``` En production, ajouter également : CORS restrictif, HTTPS obligatoire, logs de sécurité, rotation des secrets, et audits réguliers des dépendances (`npm audit`). ## Architecture et Design Patterns ### Question 11 : Expliquez le pattern Repository en Node.js Le pattern Repository abstrait l'accès aux données et facilite les tests et la maintenabilité. ```javascript // repository-pattern.js // Interface abstraite (pour TypeScript, ou documentation) class UserRepository { async findById(id) { throw new Error('Not implemented'); } async findByEmail(email) { throw new Error('Not implemented'); } async create(userData) { throw new Error('Not implemented'); } async update(id, userData) { throw new Error('Not implemented'); } async delete(id) { throw new Error('Not implemented'); } } // Implémentation concrète avec Prisma class PrismaUserRepository extends UserRepository { constructor(prisma) { super(); this.prisma = prisma; } async findById(id) { return this.prisma.user.findUnique({ where: { id } }); } async findByEmail(email) { return this.prisma.user.findUnique({ where: { email } }); } async create(userData) { return this.prisma.user.create({ data: userData }); } async update(id, userData) { return this.prisma.user.update({ where: { id }, data: userData, }); } async delete(id) { return this.prisma.user.delete({ where: { id } }); } } // Implémentation pour les tests class InMemoryUserRepository extends UserRepository { constructor() { super(); this.users = new Map(); this.idCounter = 1; } async findById(id) { return this.users.get(id) || null; } async create(userData) { const user = { id: this.idCounter++, ...userData }; this.users.set(user.id, user); return user; } // ... autres méthodes } // Service utilisant le repository (injection de dépendances) class UserService { constructor(userRepository) { this.userRepository = userRepository; } async getUser(id) { const user = await this.userRepository.findById(id); if (!user) throw new Error('User not found'); return user; } } ``` Ce pattern permet de changer l'implémentation de la persistance sans modifier la logique métier. ### Question 12 : Comment implémenter un système de queue de tâches ? Les queues permettent de différer les tâches lourdes et d'assurer leur exécution fiable. ```javascript // job-queue.js const Queue = require('bull'); // Création de la queue avec Redis comme backend const emailQueue = new Queue('email', { redis: { host: 'localhost', port: 6379, }, defaultJobOptions: { attempts: 3, // Nombre de tentatives backoff: { type: 'exponential', delay: 2000, // Délai initial entre les tentatives }, removeOnComplete: 100, // Garder les 100 derniers jobs complétés }, }); // Producteur : ajouter des jobs à la queue async function sendWelcomeEmail(userId, email) { await emailQueue.add('welcome', { userId, email, template: 'welcome', }, { priority: 1, // Priorité haute delay: 5000, // Délai de 5 secondes }); } // Consommateur : traiter les jobs emailQueue.process('welcome', async (job) => { const { userId, email, template } = job.data; // Mise à jour de la progression job.progress(10); const html = await renderTemplate(template, { userId }); job.progress(50); await sendEmail(email, 'Bienvenue!', html); job.progress(100); return { sent: true, email }; }); // Gestion des événements emailQueue.on('completed', (job, result) => { console.log(`Job ${job.id} completed:`, result); }); emailQueue.on('failed', (job, err) => { console.error(`Job ${job.id} failed:`, err.message); }); // Jobs récurrents (cron) emailQueue.add('newsletter', { type: 'weekly' }, { repeat: { cron: '0 9 * * MON', // Tous les lundis à 9h }, }); ``` Bull avec Redis est la solution la plus populaire. Pour des besoins plus simples, `agenda` ou `bee-queue` sont des alternatives légères. ## Questions avancées ### Question 13 : Comment fonctionne le module natif N-API ? N-API permet de créer des modules natifs en C/C++ avec une API stable entre les versions de Node.js. ```cpp // native-module.cpp // Module natif pour calculs CPU-intensifs #include // Fonction synchrone exposée à JavaScript Napi::Number Fibonacci(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); // Validation des arguments if (info.Length() < 1 || !info[0].IsNumber()) { Napi::TypeError::New(env, "Number expected") .ThrowAsJavaScriptException(); return Napi::Number::New(env, 0); } int n = info[0].As().Int32Value(); // Calcul itératif de Fibonacci long long a = 0, b = 1; for (int i = 0; i < n; i++) { long long temp = a + b; a = b; b = temp; } return Napi::Number::New(env, static_cast(a)); } // Initialisation du module Napi::Object Init(Napi::Env env, Napi::Object exports) { exports.Set( Napi::String::New(env, "fibonacci"), Napi::Function::New(env, Fibonacci) ); return exports; } NODE_API_MODULE(native_module, Init) ``` ```javascript // Utilisation depuis JavaScript const native = require('./build/Release/native_module'); // 10x plus rapide que l'équivalent JavaScript const result = native.fibonacci(50); ``` Les modules natifs sont utiles pour les calculs intensifs, l'intégration de librairies C/C++ existantes, ou l'accès à des APIs système. ### Question 14 : Expliquez le fonctionnement du V8 Garbage Collector Comprendre le GC aide à écrire du code qui minimise les pauses et la consommation mémoire. ```javascript // gc-optimization.js // Le GC V8 utilise deux espaces : Young et Old Generation // 1. Young Generation : objets à courte durée de vie function shortLivedObjects() { for (let i = 0; i < 1000; i++) { const temp = { data: i }; // Alloué puis collecté rapidement } // GC mineur (Scavenge) très rapide } // 2. Old Generation : objets qui survivent plusieurs GC const cache = new Map(); // Survit, promu en Old Generation // ❌ Pattern problématique : beaucoup d'objets promus function createManyLongLived() { const objects = []; for (let i = 0; i < 100000; i++) { objects.push({ id: i, data: new Array(100).fill(0) }); } return objects; // Tous promus en Old Gen = GC majeur lent } // ✅ Pattern optimisé : réutilisation d'objets class ObjectPool { constructor(factory, size = 100) { this.pool = Array.from({ length: size }, factory); this.available = [...this.pool]; } acquire() { return this.available.pop() || this.pool[0]; } release(obj) { // Reset et retour au pool Object.keys(obj).forEach(k => obj[k] = null); this.available.push(obj); } } // Monitoring du GC const v8 = require('v8'); function getHeapStats() { const stats = v8.getHeapStatistics(); return { totalHeap: `${Math.round(stats.total_heap_size / 1024 / 1024)}MB`, usedHeap: `${Math.round(stats.used_heap_size / 1024 / 1024)}MB`, heapLimit: `${Math.round(stats.heap_size_limit / 1024 / 1024)}MB`, }; } ``` Le flag `--max-old-space-size` permet d'augmenter la limite de la Old Generation pour les applications gourmandes en mémoire. ### Question 15 : Comment implémenter le graceful shutdown ? Un arrêt gracieux permet de terminer les requêtes en cours et de fermer proprement les connexions avant d'arrêter le serveur. ```javascript // graceful-shutdown.js const http = require('http'); const server = http.createServer((req, res) => { // Simulation d'une requête longue setTimeout(() => { res.writeHead(200); res.end('Done'); }, 2000); }); // Tracking des connexions actives let connections = new Set(); server.on('connection', (conn) => { connections.add(conn); conn.on('close', () => connections.delete(conn)); }); // Fonction de shutdown gracieux async function shutdown(signal) { console.log(`${signal} received, starting graceful shutdown...`); // 1. Arrêter d'accepter de nouvelles connexions server.close(() => { console.log('HTTP server closed'); }); // 2. Fermer les connexions idle for (const conn of connections) { conn.end(); } // 3. Fermer les connexions DB, queues, etc. await Promise.all([ database.disconnect(), redisClient.quit(), messageQueue.close(), ]); // 4. Timeout de sécurité setTimeout(() => { console.error('Forced shutdown after timeout'); process.exit(1); }, 30000); console.log('Graceful shutdown completed'); process.exit(0); } // Écoute des signaux de terminaison process.on('SIGTERM', () => shutdown('SIGTERM')); process.on('SIGINT', () => shutdown('SIGINT')); // Démarrage du serveur server.listen(3000, () => { console.log('Server running on port 3000'); }); ``` En production avec des conteneurs (Docker, Kubernetes), le graceful shutdown est critique pour les déploiements sans interruption de service. ## Questions comportementales ### Question 16 : Décrivez un problème de performance que vous avez résolu Cette question évalue l'expérience pratique. Structurer la réponse avec le format STAR (Situation, Task, Action, Result). **Exemple de réponse structurée :** ``` Situation : Une API de reporting générait des timeouts sur les requêtes dépassant 100 000 enregistrements. Task : Réduire le temps de réponse de 45s à moins de 5s. Action : 1. Profiling avec clinic.js → identifié la sérialisation JSON comme bottleneck 2. Implémentation du streaming avec Transform streams 3. Pagination côté base de données 4. Ajout de cache Redis pour les requêtes fréquentes Result : Temps de réponse réduit à 2s, utilisation mémoire divisée par 10. ``` ### Question 17 : Comment gérez-vous les dépendances et leurs mises à jour ? ```javascript // package.json - Bonnes pratiques de versioning { "dependencies": { // ✅ Versions exactes pour la production "express": "4.18.2", // ✅ Caret pour les mises à jour mineures compatibles "lodash": "^4.17.21", // ❌ Éviter latest ou * // "some-lib": "*" }, "devDependencies": { // Outils de qualité "npm-check-updates": "^16.0.0" }, "scripts": { // Vérification des vulnérabilités "audit": "npm audit --audit-level=moderate", // Mise à jour interactive "update:check": "ncu", "update:apply": "ncu -u && npm install" }, "engines": { // Spécifier la version Node.js requise "node": ">=20.0.0" } } ``` Mentionner l'utilisation de `package-lock.json`, Dependabot ou Renovate pour l'automatisation, et les tests de régression avant chaque mise à jour majeure. ## Conclusion Les entretiens Node.js backend évaluent à la fois la compréhension théorique des mécanismes internes et la capacité à résoudre des problèmes pratiques de production. Maîtriser l'Event Loop, les patterns asynchrones, et les techniques d'optimisation constitue le socle attendu pour les postes de développeur backend confirmé. ### Checklist de préparation - ✅ Comprendre le fonctionnement de l'Event Loop et ses phases - ✅ Maîtriser les différences entre callbacks, Promises et async/await - ✅ Connaître les patterns de gestion d'erreurs asynchrones - ✅ Savoir quand utiliser Streams vs méthodes classiques - ✅ Identifier et résoudre les fuites mémoire - ✅ Appliquer les bonnes pratiques de sécurité OWASP - ✅ Implémenter le clustering et le graceful shutdown - ✅ Utiliser les outils de profiling (clinic.js, Chrome DevTools) La préparation technique doit être complétée par des projets pratiques. Construire une API en production, contribuer à des projets open source Node.js, ou résoudre des challenges sur des plateformes comme LeetCode permet de consolider ces connaissances. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/fr/blog/node-nestjs/questions-entretien-nodejs-backend