Voici la cheatsheet d'entretien technique Node.js en français :
Questions théoriques fréquentes
Q: Qu'est-ce que Node.js ?
Node.js est un environnement d'exécution JavaScript côté serveur construit sur le moteur V8 de Chrome. Il permet d'écrire des applications asynchrones et événementielles, ce qui lui donne une performance élevée.
const http = require('http');
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
Q: Différences entre callback et promesse en Node.js ?
- Callback : Fonction passée en paramètre d'une autre fonction pour être exécutée à un moment donné.
- Promesse : Objet représentant une valeur qui peut être disponible maintenant, dans le futur ou jamais.
// Callback
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
});
// Promesse
const readFilePromise = new Promise((resolve, reject) => {
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) reject(err);
resolve(data);
});
});
readFilePromise.then(data => console.log(data)).catch(err => console.error(err));
Q: Que sont les modules en Node.js ?
Les modules sont des fichiers contenant du code JavaScript. Ils permettent de structurer le code et de réutiliser du code entre différents fichiers.
// math.js
module.exports = {
add: (a, b) => a + b,
subtract: (a, b) => a - b
};
// app.js
const math = require('./math');
console.log(math.add(5, 3)); // Output : 8
Q: Explication de la fonction setTimeout et setInterval en Node.js ?
setTimeout: Exécute une fonction après un certain délai.setInterval: Exécute une fonction à intervalles réguliers.
setTimeout(() => {
console.log('Exécuté une fois');
}, 2000);
setInterval(() => {
console.log('Exécuté toutes les secondes');
}, 1000);
Q: Qu'est-ce que process en Node.js ?
Le module process fournit des informations et des outils sur le processus courant node.js. Il est accessible de n'importe où dans le code.
console.log(process.argv); // Affiche les arguments de la ligne de commande
Q: Explication de require et import en Node.js ?
require: Importe des modules CommonJS.import: Importe des modules ES6 (nécessite un fichierpackage.jsonavec"type": "module").
// CommonJS
const math = require('./math');
// ES6
import math from './math.js';
Q: Qu'est-ce que les middlewares en Express ?
Les middlewares sont des fonctions qui ont accès à l'objet de requête (req), à l'objet de réponse (res) et à la chaîne de middlewares suivante dans le cycle de traitement de la requête. Ils peuvent exécuter du code, modifier les objets req et res, terminer le cycle de requête-réponse, ou appeler le middleware suivant.
app.use((req, res, next) => {
console.log('Middleware');
next();
});
Q: Que sont les streams en Node.js ?
Les streams représentent la transmission continue de données. Ils permettent d'optimiser l'utilisation des ressources en traitant les données de manière incrémentielle.
const fs = require('fs');
const readStream = fs.createReadStream('file.txt');
const writeStream = fs.createWriteStream('output.txt');
readStream.pipe(writeStream);
Q: Qu'est-ce que le système d'exploitation abstrait en Node.js ?
Le système d'exploitation abstrait (OS) est une couche qui fournit des interfaces unifiées pour interagir avec le système d'exploitation sous-jacent. Il permet à Node.js de fonctionner sur différents systèmes d'exploitation.
Q: Que sont les événements en Node.js ?
Les événements sont des objets qui représentent diverses actions qui peuvent être déclenchées dans l'environnement Node.js. Ils sont utilisés pour gérer la communication entre différentes parties du code.
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
console.log('un événement a été déclenché');
});
myEmitter.emit('event');
Exercices de code classiques
Exercice 1 : Compteur asynchrone
Ecrire une fonction qui prend un nombre en argument et affiche le comptage des nombres de 0 à ce nombre avec un délai de 1 seconde entre chaque affichage.
function countNumbers(num) {
for (let i = 0; i <= num; i++) {
setTimeout(() => {
console.log(i);
}, i * 1000);
}
}
countNumbers(5);
Exercice 2 : Promesse de lecture de fichier
Ecrire une fonction qui retourne une promesse qui résout le contenu d'un fichier texte.
const fs = require('fs').promises;
function readFileContent(filePath) {
return new Promise((resolve, reject) => {
fs.readFile(filePath, 'utf8')
.then(data => resolve(data))
.catch(err => reject(err));
});
}
readFileContent('file.txt')
.then(content => console.log(content))
.catch(err => console.error(err));
Exercice 3 : Middleware d'authentification
Ecrire un middleware Express qui vérifie si une clé d'API est présente dans les en-têtes de la requête.
const express = require('express');
const app = express();
function authMiddleware(req, res, next) {
const apiKey = req.headers['x-api-key'];
if (apiKey === 'my-secret-key') {
next();
} else {
res.status(401).send('Unauthorized');
}
}
app.use(authMiddleware);
app.get('/protected', (req, res) => {
res.send('Accès protégé');
});
app.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
Exercice 4 : Gestion des erreurs avec try...catch
Ecrire une fonction qui divise deux nombres et gère les erreurs potentielles.
function divide(a, b) {
try {
if (b === 0) throw new Error('Division par zéro');
return a / b;
} catch (err) {
console.error(err.message);
return null;
}
}
console.log(divide(10, 2)); // Output : 5
console.log(divide(10, 0)); // Output : Division par zéro
Exercice 5 : Streams de données
Ecrire un programme qui lit un fichier texte et écrit son contenu dans un autre fichier en utilisant des streams.
const fs = require('fs');
const readStream = fs.createReadStream('file.txt');
const writeStream = fs.createWriteStream('output.txt');
readStream.pipe(writeStream);
Pieges courants en entretien
Piege 1 : Confusion entre Node.js et JavaScript
Node.js utilise JavaScript, mais il s'exécute côté serveur. Il existe une différence importante entre le code exécuté dans un navigateur et celui exécuté avec Node.js.
Piege 2 : Ignorer les erreurs asynchrones
Les erreurs asynchrones ne sont pas traitées automatiquement. Il est important de gérer les erreurs en utilisant des callbacks, des promesses ou des try...catch.
fs.readFile('non-existent-file.txt', 'utf8', (err, data) => {
if (err) throw err; // Erreur non traitée
console.log(data);
});
Piege 3 : Non-compréhension des événements et de la non-blocage
Node.js est conçu pour être asynchrone et événementiel. Il ne bloque pas l'exécution du code même lorsqu'il attend une opération longue.
const fs = require('fs');
console.log('Début');
fs.readFile('file.txt', 'utf8', (err, data) => {
console.log(data);
});
console.log('Fin'); // Exécuté avant la lecture du fichier
Piege 4 : Utilisation de let et const dans les boucles for
Dans certains cas, l'utilisation de let et const peut causer des problèmes en raison de la portée des variables.
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i); // Affiche 5, 5, 5, 5, 5
}, 100);
}
Piege 5 : Compréhension faible des streams
Les streams sont souvent sous-estimés. Ils permettent de gérer les données en morceaux plutôt que tout en une fois.
Complexité algorithmique
Q: Complexité O(1) : Opération constante, le temps d'exécution ne change pas avec la taille de l'entrée.
const arr = [1, 2, 3];
console.log(arr[0]); // Accès constant
Q: Complexité O(n) : Opération linéaire, le temps d'exécution augmente proportionnellement à la taille de l'entrée.
const arr = [1, 2, 3];
arr.forEach(item => {
console.log(item); // Temps linéaire
});
Q: Complexité O(n²) : Opération quadratique, le temps d'exécution augmente en fonction du carré de la taille de l'entrée.
const arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length; j++) {
console.log(arr[i], arr[j]); // Temps quadratique
}
}
Q: Complexité O(log n) : Opération logarithmique, le temps d'exécution augmente logarithmiquement avec la taille de l'entrée.
function binarySearch(arr, target) {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) {
return mid; // Temps logarithmique
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
Q: Complexité O(n log n) : Opération linéaireithme, le temps d'exécution augmente en fonction de la taille de l'entrée multiplié par le logarithme de cette taille.
function mergeSort(arr) {
if (arr.length <= 1) return arr;
const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
return merge(left, right);
}
function merge(left, right) {
let result = [];
let i = 0;
let j = 0;
while (i < left.length && j < right.length) {
if (left[i] < right[j]) {
result.push(left[i]);
i++;
} else {
result.push(right[j]);
j++;
}
}
return [...result, ...left.slice(i), ...right.slice(j)]; // Temps linéaireithme
}
Concepts avancés à connaitre
Q: Middleware Express
Les middlewares sont des fonctions qui ont accès à l'objet de requête (req), à l'objet de réponse (res) et à la chaîne de middlewares suivante dans le cycle de traitement de la requête. Ils peuvent exécuter du code, modifier les objets req et res, terminer le cycle de requête-réponse, ou appeler le middleware suivant.
Q: Gestion des erreurs avec try...catch
Les erreurs asynchrones ne sont pas traitées automatiquement. Il est important de gérer les erreurs en utilisant des callbacks, des promesses ou des try...catch.
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (err) {
console.error(err.message);
}
}
Q: Gestion des erreurs avec process.on('uncaughtException')
Les erreurs non traitées peuvent causer le crash de l'application. Il est important de gérer ces erreurs en utilisant process.on('uncaughtException').
process.on('uncaughtException', (err) => {
console.error(`Uncaught Exception: ${err.message}`);
process.exit(1);
});
Q: Utilisation des streams avec les fichiers
Les streams sont souvent sous-estimés. Ils permettent de gérer les données en morceaux plutôt que tout en une fois.
const fs = require('fs');
const readStream = fs.createReadStream('file.txt');
const writeStream = fs.createWriteStream('output.txt');
readStream.pipe(writeStream);
Q: Utilisation des streams avec les requêtes HTTP
Les streams peuvent également être utilisés pour traiter les données en flux continu lors de la gestion des requêtes HTTP.
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
const readStream = fs.createReadStream('file.txt');
readStream.pipe(res);
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
Conseils pratiques
Conseil 1 : Comprendre les principes de base
Avant tout autre chose, assurez-vous de comprendre les principes de base de Node.js, comme la programmation asynchrone et le non-blocage.
Conseil 2 : Utiliser des outils de débogage
L'utilisation d'outils de débogage peut aider à identifier les erreurs et à comprendre le comportement du code.
node inspect app.js
Conseil 3 : Écrire des tests
La rédaction de tests unitaires aide à vérifier la qualité du code et à prévenir les bugs futurs.
Conseil 4 : Utiliser un linter
Un linter peut aider à détecter des erreurs de syntaxe et à promouvoir une meilleure pratique de codage.
npm install --save-dev eslint
Conseil 5 : Connaître les meilleures pratiques
Apprendre les meilleures pratiques en matière de développement Node.js peut améliorer la performance et la sécurité de l'application.