# Node.js Backend Sollicitatievragen: Volledige Gids 2026 > De 25 meest gestelde Node.js backend sollicitatievragen. Event loop, async/await, streams, clustering en performance uitgelegd met gedetailleerde antwoorden. - Published: 2026-01-22 - Updated: 2026-04-10 - Author: SharpSkill - Tags: nodejs, interview, backend, javascript, technical interview - Reading time: 18 min --- Node.js backend-sollicitatiegesprekken toetsen het begrip van runtime-internals, de beheersing van asynchrone patronen en het vermogen om performante applicaties te ontwerpen. Deze gids behandelt de meest gestelde vragen, van de basis tot geavanceerde productieconcepten. > **Sollicitatietip** > > Recruiters waarderen antwoorden die theorie combineren met praktische voorbeelden. Bij elke vraag toont het illustreren met code of een concreet gebruiksscenario daadwerkelijke praktijkervaring. ## Node.js Basiskennis ### Vraag 1: Wat is de Event Loop en hoe werkt deze? De Event Loop is het centrale mechanisme waarmee Node.js asynchrone operaties non-blocking kan verwerken, ondanks dat de uitvoering op een enkele thread plaatsvindt. Het orkestreert de uitvoering van JavaScript-code, callbacks en events. ```javascript // event-loop-demo.js // Demonstration of Event Loop execution order console.log('1. Script start (synchronous)'); // setTimeout goes to the Timer Queue setTimeout(() => { console.log('5. setTimeout callback (Timer Queue)'); }, 0); // setImmediate goes to the Check Queue setImmediate(() => { console.log('6. setImmediate callback (Check Queue)'); }); // Promise goes to the Microtask Queue (priority) Promise.resolve().then(() => { console.log('3. Promise.then (Microtask Queue)'); }); // process.nextTick has the highest priority process.nextTick(() => { console.log('2. process.nextTick (nextTick Queue)'); }); console.log('4. Script end (synchronous)'); // Output order: 1, 4, 2, 3, 5, 6 ``` De Event Loop volgt een precieze volgorde bij het verwerken van de queues: eerst synchrone code, dan nextTick, microtasks (Promises), timers, I/O-callbacks, setImmediate en ten slotte close-callbacks. ### Vraag 2: Wat is het verschil tussen process.nextTick() en setImmediate()? Deze vraag toetst het gedetailleerde begrip van uitvoeringsprioriteiten in de Event Loop. ```javascript // nextTick-vs-immediate.js // Behavior comparison // process.nextTick executes BEFORE the next Event Loop phase process.nextTick(() => { console.log('nextTick 1'); process.nextTick(() => { console.log('nextTick 2 (nested)'); }); }); // setImmediate executes in the Check phase of the Event Loop setImmediate(() => { console.log('setImmediate 1'); setImmediate(() => { console.log('setImmediate 2 (nested)'); }); }); // Output: nextTick 1, nextTick 2, setImmediate 1, setImmediate 2 ``` `process.nextTick()` wordt direct na de huidige operatie verwerkt, voordat de Event Loop verdergaat. Overmatig gebruik kan de Event Loop blokkeren. `setImmediate()` is voorspelbaarder en wordt aanbevolen om uitvoering uit te stellen. > **Pas op voor Starvation** > > Recursieve aanroepen van process.nextTick() kunnen de Event Loop uithongeren en I/O-verwerking voorkomen. Voor niet-kritieke operaties verdient setImmediate() de voorkeur. ### Vraag 3: Hoe gaat Node.js om met fouten in asynchrone code? Asynchrone foutafhandeling verschilt fundamenteel van synchrone code. Zonder correcte afhandeling kan een fout de gehele applicatie laten crashen. ```javascript // error-handling.js // Asynchronous error handling patterns // Pattern 1: Callbacks with error-first convention function readFileCallback(path, callback) { const fs = require('fs'); fs.readFile(path, 'utf8', (err, data) => { if (err) { // Error is ALWAYS the first argument return callback(err, null); } callback(null, data); }); } // Pattern 2: Promises with catch async function readFilePromise(path) { const fs = require('fs').promises; try { const data = await fs.readFile(path, 'utf8'); return data; } catch (err) { // Centralized error handling console.error(`File read error: ${err.message}`); throw err; // Re-throw for propagation } } // Pattern 3: Global handling of unhandled rejections process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled rejection:', reason); // In production: log and graceful shutdown }); // Pattern 4: Handling uncaught exceptions process.on('uncaughtException', (err) => { console.error('Uncaught exception:', err); // CRITICAL: always terminate the process after process.exit(1); }); ``` In productie moet elke Promise een `.catch()` hebben of zich binnen een try/catch-blok bevinden. Globale handlers dienen als vangnet, niet als primaire oplossing. ## Asynchrone programmering en concurrency ### Vraag 4: Leg het verschil uit tussen parallellisme en concurrency in Node.js Node.js is standaard concurrent maar niet parallel. Dit onderscheid is fundamenteel voor het begrijpen van performance. ```javascript // concurrency-vs-parallelism.js // CONCURRENCY: multiple tasks progress by alternating (single-thread) async function concurrentTasks() { console.time('concurrent'); // These calls are concurrent, not parallel const results = await Promise.all([ fetch('https://api.example.com/users'), // Non-blocking I/O fetch('https://api.example.com/products'), // Non-blocking I/O fetch('https://api.example.com/orders'), // Non-blocking I/O ]); console.timeEnd('concurrent'); // ~time of the longest request return results; } // PARALLELISM: with Worker Threads for CPU-bound tasks const { Worker, isMainThread, parentPort } = require('worker_threads'); if (isMainThread) { // Main thread delegates CPU-intensive work 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 executed in the Worker Thread const { workerData } = require('worker_threads'); let sum = 0; for (let i = workerData.start; i < workerData.end; i++) { sum += Math.sqrt(i); // CPU-intensive calculation } parentPort.postMessage(sum); } ``` Voor I/O-gebonden operaties (netwerk, bestanden) volstaat de native concurrency. Voor CPU-gebonden taken (zware berekeningen, cryptografie) maken Worker Threads echt parallellisme mogelijk. ### Vraag 5: Hoe werkt de Cluster-module? De Cluster-module maakt het mogelijk om meerdere Node.js-processen aan te maken die dezelfde poort delen, waardoor alle beschikbare CPU-kernen benut worden. ```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 one worker per CPU core for (let i = 0; i < numCPUs; i++) { cluster.fork(); } // Handle crashing workers cluster.on('exit', (worker, code, signal) => { console.log(`Worker ${worker.process.pid} died (${signal || code})`); console.log('Starting a new worker...'); cluster.fork(); // Automatic restart }); // Inter-process communication cluster.on('message', (worker, message) => { console.log(`Message from worker ${worker.id}:`, message); }); } else { // Workers share the TCP port http.createServer((req, res) => { res.writeHead(200); res.end(`Handled by worker ${process.pid}\n`); // Send stats to primary process.send({ type: 'request', pid: process.pid }); }).listen(8000); console.log(`Worker ${process.pid} started`); } ``` De load balancing wordt automatisch afgehandeld door het besturingssysteem (round-robin op Linux/macOS). In productie vereenvoudigt PM2 dit beheer met de ingebouwde clustermodus. ## Streams en Buffers ### Vraag 6: Wanneer moeten Streams worden gebruikt in plaats van klassieke methoden? Streams maken het mogelijk om data in stukken te verwerken in plaats van alles in het geheugen te laden. Essentieel bij grote bestanden en streamingscenario's. ```javascript // streams-comparison.js const fs = require('fs'); // ❌ BAD: loads entire file into memory async function readEntireFile(path) { const data = await fs.promises.readFile(path); // Blocks if file > RAM return processData(data); } // ✅ GOOD: chunk-based processing with Stream function readWithStream(path) { return new Promise((resolve, reject) => { const chunks = []; const readStream = fs.createReadStream(path, { highWaterMark: 64 * 1024, // 64KB per chunk }); readStream.on('data', (chunk) => { // Progressive processing, constant memory chunks.push(processChunk(chunk)); }); readStream.on('end', () => resolve(chunks)); readStream.on('error', reject); }); } // ✅ BEST: pipeline for chaining 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 ); // Automatic error handling and backpressure management } ``` Streams dienen te worden gebruikt wanneer de datagrootte enkele MB's kan overschrijden, of bij realtime verwerking (uploads, logs, netwerkdata). ### Vraag 7: Leg het concept van backpressure uit Backpressure treedt op wanneer de dataproducent sneller is dan de consument. Zonder afhandeling explodeert het geheugengebruik. ```javascript // backpressure-demo.js const fs = require('fs'); // ❌ Problem: no backpressure handling function badCopy(src, dest) { const readable = fs.createReadStream(src); const writable = fs.createWriteStream(dest); readable.on('data', (chunk) => { // If write() returns false, the internal buffer is full // But here reading continues anyway → memory leak writable.write(chunk); }); } // ✅ Solution: respect the writable signal 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 reading until buffer drains readable.pause(); } }); writable.on('drain', () => { // Buffer drained, resume reading readable.resume(); }); readable.on('end', () => writable.end()); } // ✅ BEST: pipe() handles everything automatically function bestCopy(src, dest) { const readable = fs.createReadStream(src); const writable = fs.createWriteStream(dest); // pipe() handles backpressure natively readable.pipe(writable); } ``` De methode `pipe()` of `pipeline()` handelt backpressure automatisch af. Bij complexe gevallen kan de pause/resume-logica handmatig worden geïmplementeerd. ## Performance en optimalisatie ### Vraag 8: Hoe worden memory leaks geïdentificeerd en opgelost? Memory leaks komen vaak voor in Node.js. Weten hoe ze te detecteren en op te lossen is essentieel in productie. ```javascript // memory-leak-patterns.js // ❌ Leak 1: closures that retain references function createLeakyHandler() { const hugeData = Buffer.alloc(100 * 1024 * 1024); // 100MB return function handler(req, res) { // hugeData remains in memory as long as handler exists res.end('Hello'); }; } // ✅ Fix: limit the scope function createSafeHandler() { return function handler(req, res) { // Data created and released on each request const data = fetchData(); res.end(data); }; } // ❌ Leak 2: event listeners not cleaned up class LeakyClass { constructor() { // Added on each instantiation, never removed process.on('message', this.handleMessage); } handleMessage(msg) { /* ... */ } } // ✅ Fix: explicit cleanup class SafeClass { constructor() { this.boundHandler = this.handleMessage.bind(this); process.on('message', this.boundHandler); } handleMessage(msg) { /* ... */ } destroy() { // Mandatory cleanup process.removeListener('message', this.boundHandler); } } // Diagnostics with native tools 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`, }); } // Enable manual garbage collector for testing // node --expose-gc app.js if (global.gc) { global.gc(); diagnoseMemory(); } ``` In productie worden tools ingezet zoals `clinic.js`, heap snapshots vanuit Chrome DevTools of APM-oplossingen (Application Performance Monitoring) zoals DataDog of New Relic. ### Vraag 9: Hoe wordt de performance van een Node.js API geoptimaliseerd? Deze vraag toetst kennis van optimalisatietechnieken op meerdere niveaus. ```javascript // performance-optimization.js // 1. CACHING: reduce expensive calls const NodeCache = require('node-cache'); const cache = new NodeCache({ stdTTL: 300 }); // 5-minute TTL 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: reuse DB connections const { Pool } = require('pg'); const pool = new Pool({ max: 20, // Max simultaneous connections idleTimeoutMillis: 30000, connectionTimeoutMillis: 2000, }); // 3. COMPRESSION: reduce response size const compression = require('compression'); app.use(compression({ filter: (req, res) => { // Only compress if > 1KB return compression.filter(req, res); }, threshold: 1024, })); // 4. BATCHING: group operations 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: load on demand async function getUserWithPosts(userId, includePosts = false) { const user = await db.users.findById(userId); if (includePosts) { user.posts = await db.posts.findByUserId(userId); } return user; } ``` Optimalisaties dienen te worden gestuurd door profiling. Eerst meten voordat er wordt geoptimaliseerd, om de werkelijke knelpunten te identificeren. > **De 80/20-regel** > > 80% van de performanceproblemen komt voort uit 20% van de code. Profiling helpt deze kritieke gebieden te identificeren voordat er blind wordt geoptimaliseerd. ## Beveiliging ### Vraag 10: Hoe wordt een Node.js API beschermd tegen veelvoorkomende aanvallen? Beveiliging is een terugkerend onderwerp bij sollicitatiegesprekken. Kennis van OWASP-kwetsbaarheden wordt verwacht. ```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. SECURITY HEADERS with Helmet app.use(helmet()); // 2. RATE LIMITING against brute-force attacks const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // 100 requests per IP message: 'Too many requests, please try again later', standardHeaders: true, legacyHeaders: false, }); app.use('/api/', limiter); // 3. SANITIZATION against NoSQL injections app.use(mongoSanitize()); // 4. XSS PROTECTION app.use(xss()); // 5. STRICT INPUT VALIDATION 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() }); } // Continue processing } ); // 6. SQL INJECTION PROTECTION (with parameters) async function safeQuery(userId) { // ✅ Parameterized query const result = await pool.query( 'SELECT * FROM users WHERE id = $1', [userId] ); return result.rows; } // ❌ NEVER string concatenation async function unsafeQuery(userId) { // Vulnerable to SQL injection const result = await pool.query( `SELECT * FROM users WHERE id = ${userId}` ); } ``` In productie dienen daarnaast te worden toegevoegd: restrictieve CORS, verplicht HTTPS, beveiligingslogging, rotatie van geheimen en regelmatige dependency-audits (`npm audit`). ## Architectuur en Design Patterns ### Vraag 11: Leg het Repository pattern uit in Node.js Het Repository pattern abstraheert de datatoegang en vergemakkelijkt testen en onderhoudbaarheid. ```javascript // repository-pattern.js // Abstract interface (for TypeScript, or 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'); } } // Concrete implementation with 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 } }); } } // Implementation for testing 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; } // ... other methods } // Service using the repository (dependency injection) 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; } } ``` Dit pattern maakt het mogelijk om de persistentie-implementatie te wijzigen zonder de bedrijfslogica aan te passen. ### Vraag 12: Hoe wordt een job queue-systeem geïmplementeerd? Queues maken het mogelijk om zware taken uit te stellen en de betrouwbare uitvoering ervan te garanderen. ```javascript // job-queue.js const Queue = require('bull'); // Create queue with Redis as backend const emailQueue = new Queue('email', { redis: { host: 'localhost', port: 6379, }, defaultJobOptions: { attempts: 3, // Number of attempts backoff: { type: 'exponential', delay: 2000, // Initial delay between attempts }, removeOnComplete: 100, // Keep last 100 completed jobs }, }); // Producer: add jobs to the queue async function sendWelcomeEmail(userId, email) { await emailQueue.add('welcome', { userId, email, template: 'welcome', }, { priority: 1, // High priority delay: 5000, // 5-second delay }); } // Consumer: process jobs emailQueue.process('welcome', async (job) => { const { userId, email, template } = job.data; // Update progress job.progress(10); const html = await renderTemplate(template, { userId }); job.progress(50); await sendEmail(email, 'Welcome!', html); job.progress(100); return { sent: true, email }; }); // Event handling 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); }); // Recurring jobs (cron) emailQueue.add('newsletter', { type: 'weekly' }, { repeat: { cron: '0 9 * * MON', // Every Monday at 9am }, }); ``` Bull met Redis is de meest populaire oplossing. Voor eenvoudigere behoeften zijn `agenda` of `bee-queue` lichtgewicht alternatieven. ## Geavanceerde vragen ### Vraag 13: Hoe werkt de native N-API-module? N-API maakt het mogelijk om native modules in C/C++ te maken met een stabiele API over Node.js-versies heen. ```cpp // native-module.cpp // Native module for CPU-intensive calculations #include // Synchronous function exposed to JavaScript Napi::Number Fibonacci(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); // Argument validation 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(); // Iterative Fibonacci calculation 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)); } // Module initialization 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 // Usage from JavaScript const native = require('./build/Release/native_module'); // 10x faster than JavaScript equivalent const result = native.fibonacci(50); ``` Native modules zijn nuttig voor rekenintensieve operaties, integratie van bestaande C/C++-bibliotheken of toegang tot systeem-API's. ### Vraag 14: Leg de V8 Garbage Collector uit Begrip van de GC helpt bij het schrijven van code die pauzes en geheugenverbruik minimaliseert. ```javascript // gc-optimization.js // V8 GC uses two spaces: Young and Old Generation // 1. Young Generation: short-lived objects function shortLivedObjects() { for (let i = 0; i < 1000; i++) { const temp = { data: i }; // Allocated then collected quickly } // Minor GC (Scavenge) very fast } // 2. Old Generation: objects that survive multiple GCs const cache = new Map(); // Survives, promoted to Old Generation // ❌ Problematic pattern: many promoted objects function createManyLongLived() { const objects = []; for (let i = 0; i < 100000; i++) { objects.push({ id: i, data: new Array(100).fill(0) }); } return objects; // All promoted to Old Gen = slow major GC } // ✅ Optimized pattern: object reuse 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 and return to pool Object.keys(obj).forEach(k => obj[k] = null); this.available.push(obj); } } // GC monitoring 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`, }; } ``` De flag `--max-old-space-size` maakt het mogelijk om de limiet van de Old Generation te verhogen voor geheugenintensieve applicaties. ### Vraag 15: Hoe wordt een graceful shutdown geïmplementeerd? Een graceful shutdown maakt het mogelijk om lopende verzoeken af te ronden en verbindingen correct te sluiten voordat de server wordt gestopt. ```javascript // graceful-shutdown.js const http = require('http'); const server = http.createServer((req, res) => { // Simulate a long request setTimeout(() => { res.writeHead(200); res.end('Done'); }, 2000); }); // Tracking active connections let connections = new Set(); server.on('connection', (conn) => { connections.add(conn); conn.on('close', () => connections.delete(conn)); }); // Graceful shutdown function async function shutdown(signal) { console.log(`${signal} received, starting graceful shutdown...`); // 1. Stop accepting new connections server.close(() => { console.log('HTTP server closed'); }); // 2. Close idle connections for (const conn of connections) { conn.end(); } // 3. Close DB connections, queues, etc. await Promise.all([ database.disconnect(), redisClient.quit(), messageQueue.close(), ]); // 4. Safety timeout setTimeout(() => { console.error('Forced shutdown after timeout'); process.exit(1); }, 30000); console.log('Graceful shutdown completed'); process.exit(0); } // Listen for termination signals process.on('SIGTERM', () => shutdown('SIGTERM')); process.on('SIGINT', () => shutdown('SIGINT')); // Start server server.listen(3000, () => { console.log('Server running on port 3000'); }); ``` In productie met containers (Docker, Kubernetes) is een graceful shutdown cruciaal voor deployments zonder downtime. ## Gedragsvragen ### Vraag 16: Beschrijf een performanceprobleem dat is opgelost Deze vraag beoordeelt praktische ervaring. Het antwoord dient te worden gestructureerd met de STAR-methode (Situation, Task, Action, Result). **Voorbeeld van een gestructureerd antwoord:** ``` Situation: Een reporting-API had timeouts bij verzoeken met meer dan 100.000 records. Task: De responstijd van 45s naar minder dan 5s terugbrengen. Action: 1. Profiling met clinic.js → JSON-serialisatie als knelpunt geïdentificeerd 2. Streaming geïmplementeerd met Transform streams 3. Paginering aan de databasekant 4. Redis-caching voor frequente queries Result: Responstijd teruggebracht naar 2s, geheugengebruik met factor 10 verminderd. ``` ### Vraag 17: Hoe worden dependencies en hun updates beheerd? ```javascript // package.json - Versioning best practices { "dependencies": { // ✅ Exact versions for production "express": "4.18.2", // ✅ Caret for compatible minor updates "lodash": "^4.17.21", // ❌ Avoid latest or * // "some-lib": "*" }, "devDependencies": { // Quality tools "npm-check-updates": "^16.0.0" }, "scripts": { // Vulnerability check "audit": "npm audit --audit-level=moderate", // Interactive update "update:check": "ncu", "update:apply": "ncu -u && npm install" }, "engines": { // Specify required Node.js version "node": ">=20.0.0" } } ``` Belangrijk is het gebruik van `package-lock.json`, Dependabot of Renovate voor automatisering en regressietests voor elke grotere update. ## Conclusie Node.js backend-sollicitatiegesprekken beoordelen zowel het theoretische begrip van interne mechanismen als het vermogen om praktische productieproblemen op te lossen. De beheersing van de Event Loop, asynchrone patronen en optimalisatietechnieken vormt de verwachte basis voor senior backend-ontwikkelaarposities. ### Voorbereidingschecklist - ✅ De werking van de Event Loop en de fasen ervan begrijpen - ✅ Verschillen tussen callbacks, Promises en async/await beheersen - ✅ Patronen voor asynchrone foutafhandeling kennen - ✅ Weten wanneer Streams in plaats van klassieke methoden worden ingezet - ✅ Memory leaks kunnen identificeren en oplossen - ✅ OWASP-beveiligings-best-practices toepassen - ✅ Clustering en graceful shutdown implementeren - ✅ Profiling-tools gebruiken (clinic.js, Chrome DevTools) Technische voorbereiding dient te worden aangevuld met praktische projecten. Het bouwen van een productie-API, bijdragen aan Node.js open-sourceprojecten of het oplossen van uitdagingen op platforms zoals LeetCode helpt deze kennis te verankeren. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/nl/blog/node-nestjs/nodejs-backend-interview-questions