Pourquoi Node.js est important
Node.js est un runtime qui permet d’exécuter JavaScript en dehors du navigateur. Lancé en 2009, il a repris le moteur V8 de Chrome pour le coupler à des API pour les fichiers, le réseau et les processus, permettant ainsi aux développeurs d’utiliser un seul langage sur l’ensemble de la stack.
Sa caractéristique principale est son I/O non bloquant et piloté par les événements. Au lieu de réserver un thread par requête et d’attendre la réponse du disque ou du réseau, Node délègue l’opération lente et continue d’exécuter d’autres tâches. Une fois l’opération terminée, un callback est exécuté. Ce modèle le rend particulièrement efficace pour les API, les applications et outils en temps réel, et il utilise la même event loop que celle que vous connaissez déjà avec JavaScript dans le navigateur.
Exécuter JavaScript côté serveur
Node vous fournit un objet global process, un système de modules et une bibliothèque standard. Un serveur HTTP minimal ne nécessite aucune dépendance.
// server.js
import { createServer } from "node:http";
const server = createServer((req, res) => {
if (req.url === "/health") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "ok" }));
return;
}
res.writeHead(404).end("Not found");
});
server.listen(process.env.PORT ?? 3000);
Les imports intégrés avec le préfixe node: permettent de savoir clairement quels modules proviennent de Node lui-même. Ce serveur traite les requêtes au fur et à mesure de leur arrivée et ne bloque jamais sur les entrées/sorties (I/O).
Modules
Le Node.js moderne utilise les ES modules, avec la même syntaxe import et export que dans le navigateur.
// math.js
export function add(a, b) {
return a + b;
}
// app.js
import { add } from "./math.js";
Configurez "type": "module" dans package.json pour traiter les fichiers .js comme des ES modules. Node.js supporte toujours CommonJS (require et module.exports), que vous rencontrerez dans d’anciens codes et de nombreux packages. Les nouveaux projets devraient utiliser les ES modules.
Le système de fichiers
L’API du système de fichiers de Node.js est disponible en variantes asynchrones et synchrones. Utilisez l’API asynchrone dans vos serveurs.
// files.js
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
const file = path.join(import.meta.dirname, "data.json");
const data = JSON.parse(await readFile(file, "utf8"));
await writeFile(file, JSON.stringify({ ...data, updated: Date.now() }, null, 2));
Le module node:path gère la jointure de chemins multiplateformes, et import.meta.dirname permet d’obtenir le répertoire du fichier actuel. Des fonctions synchrones comme readFileSync existent et sont acceptables dans des scripts ou du code de démarrage, mais ne les utilisez jamais à l’intérieur d’un gestionnaire de requêtes.
Environnement et configuration
La configuration doit se trouver dans des variables d’environnement, et non dans le code.
// env.js
const config = {
port: Number(process.env.PORT ?? 3000),
databaseUrl: process.env.DATABASE_URL,
nodeEnv: process.env.NODE_ENV ?? "development",
};
Cela permet de garder les secrets hors du dépôt et permet au même build de s’exécuter en développement, en staging et en production. En développement, chargez les valeurs locales depuis un fichier .env à l’aide d’un outil comme dotenv ou du module intégré --env-file de Node.js, et ne commitez jamais ce fichier.
Créer des API
La plupart des applications Node.js utilisent un framework pour gérer le routage, le parsing et le middleware. Express est le plus utilisé ; Fastify est plus rapide et orienté schéma.
// api.js
import express from "express";
const app = express();
app.use(express.json());
app.get("/api/users", async (req, res, next) => {
try {
const users = await db.users.findMany();
res.json(users);
} catch (error) {
next(error);
}
});
app.post("/api/users", async (req, res, next) => {
try {
const user = await db.users.create(req.body);
res.status(201).json(user);
} catch (error) {
next(error);
}
});
app.use((error, req, res, next) => {
console.error(error);
res.status(500).json({ message: "Something went wrong" });
});
app.listen(config.port);
Les gestionnaires de routes sont des fonctions async, les erreurs sont transmises au middleware, et un gestionnaire d’erreurs final transforme les exceptions en réponses. Retournez des codes de statut et des headers explicites, et consultez la section Sécurité Web pour la validation des entrées et la protection des sessions.
Streams, buffers et événements
Node gère les données sous forme de streams afin que les charges utiles volumineuses n’aient pas besoin de tenir entièrement en mémoire.
// stream.js
import { createReadStream } from "node:fs";
import { createServer } from "node:http";
createServer((req, res) => {
createReadStream("movie.mp4").pipe(res);
}).listen(3000);
Un buffer correspond à des données binaires brutes, et un event emitter permet aux objets de publier des événements auxquels d’autres peuvent s’abonner. Les streams et les événements constituent la machinerie de bas niveau derrière une grande partie de la bibliothèque standard de Node ; les comprendre permet d’expliquer comment fonctionnent les fichiers, les sockets et HTTP sous le capot.
Erreurs et signaux de processus
Les erreurs non gérées peuvent faire planter un processus Node.js, il est donc essentiel de les traiter de manière délibérée.
// signals.js
process.on("SIGTERM", () => {
server.close(() => process.exit(0));
});
process.on("unhandledRejection", (reason) => {
console.error("Unhandled rejection", reason);
});
SIGTERM est envoyé par la plupart des plateformes lorsqu’un conteneur ou un processus doit s’arrêter ; le gérer vous permet de terminer les requêtes en cours avant de quitter. Les gestionnaires globaux servent de filet de sécurité pour le logging, et non de substitut à la gestion des erreurs là où elles se produisent.
Bonnes pratiques
- Utilisez des API async dans les gestionnaires de requêtes et ne bloquez jamais l’event loop.
- Conservez la configuration dans des variables d’environnement.
- Validez et assainissez toutes les entrées côté serveur.
- Utilisez les ES modules et le préfixe
node:pour les modules intégrés. - Centralisez la gestion des erreurs dans un middleware.
- Utilisez des flux (streams) pour les fichiers volumineux au lieu de les charger en mémoire.
- Gérez
SIGTERMpour un arrêt progressif (graceful shutdown) et exposez un endpoint de santé (health endpoint).
Erreurs courantes
- Utiliser
readFileSyncà l’intérieur d’un gestionnaire de requêtes, ce qui bloque le serveur. - Effectuer des tâches lourdes pour le CPU sur le thread principal.
- Committer des fichiers
.envou coder des secrets en dur. - Faire confiance aux entrées client sans validation.
- Ignorer les rejets de promesses non gérés.
- Lire des fichiers volumineux entièrement en mémoire au lieu d’utiliser le streaming.
Et après ?
Node.js complète le panorama full-stack. Gérez vos dépendances avec npm, comprenez le protocole HTTP utilisé par votre serveur, sécurisez-le grâce à la Web Security et encapsulez-le avec Docker. Ensuite, créez une petite API avec deux routes, un appel à une base de données et un gestionnaire d’erreurs, puis déployez-la.