Créer un serveur Node rapide pour servir des fichiers statiques
Dans de nombreux projets web, nous avons simplement besoin d’un petit serveur local pour tester rapidement nos développements. Ouvrir un fichier directement dans le navigateur avec le protocole file:// fonctionne pour afficher une page HTML, mais cette approche montre vite ses limites dès que le projet devient un peu plus dynamique.
Certaines fonctionnalités du navigateur exigent en effet d’être exécutées dans un véritable contexte HTTP : c’est le cas des modules JavaScript, des appels AJAX ou encore du chargement de fichiers JSON. Sans serveur, ces mécanismes peuvent provoquer des erreurs de chargement ou des blocages liés aux règles de sécurité du navigateur. Plutôt que d’installer immédiatement une infrastructure complète comme Apache ou Nginx, il est souvent plus simple de mettre en place un petit serveur Node.js capable de servir des fichiers statiques. Quelques lignes de JavaScript suffisent pour disposer d’un environnement local léger, rapide à démarrer et parfaitement adapté aux phases de développement.
Dans cet article, nous allons construire pas à pas un serveur Node minimal capable de servir des fichiers HTML, CSS, JavaScript, images ou JSON. L’objectif n’est pas de reproduire toutes les fonctionnalités d’un serveur web complet, mais de comprendre les mécanismes essentiels qui permettent au navigateur de récupérer les ressources dont il a besoin. Afin de faciliter la lecture et les expérimentations, l’ensemble des fichiers utilisés dans cet article peut être téléchargé. Vous pourrez ainsi retrouver la structure du projet, les différents scripts JavaScript ainsi que les exemples de fichiers JSON employés tout au long des explications, et les tester directement sur votre propre environnement.
Pourquoi Node peut servir de serveur minimal
Lorsqu’on parle de serveur web, on pense souvent à des environnements complets comme Apache, Nginx ou à des stacks intégrant PHP et une base de données. Pourtant, pour de nombreux besoins de développement, une solution beaucoup plus simple suffit. Nous avons seulement besoin d’un processus capable d’écouter une requête HTTP et de renvoyer un fichier.
Node possède précisément cette capacité. Si vous n’avez pas encore installé Node ou créé votre premier projet, vous pouvez commencer par lire l’article Prendre en main Node.js, qui explique comment installer l’environnement et préparer un dossier de travail. Une fois cette base en place, le module natif http permet de créer un serveur en quelques lignes de code. Aucun logiciel supplémentaire n’est nécessaire, aucune configuration complexe n’est requise. Le script Node devient lui‑même le serveur.
Avant d’écrire ce script, il est utile de disposer d’un petit projet Node initialisé avec un fichier package.json. Ce fichier décrit le projet et permet à Node de comprendre certaines options de fonctionnement. Dans notre cas, nous utilisons la syntaxe moderne des modules JavaScript avec import. Il faut donc indiquer à Node que le projet utilise ce mode. Un fichier package.json minimal peut par exemple ressembler à ceci :
{
"name": "node-static-server",
"version": "1.0.0",
"type": "module"
}La propriété type: "module" indique à Node que les fichiers .js doivent être interprétés comme des modules ES. Sans cela, Node attendrait la syntaxe plus ancienne basée sur require(). Une fois ce fichier en place, nous pouvons écrire un serveur très simple comme celui‑ci :
// server.js v.01
import http from 'http';
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Serveur Node actif');
});
server.listen(3000, () => {
console.log('Serveur démarré sur http://localhost:3000');
});Une fois ce script lancé depuis la console avec node server.js, Node démarre immédiatement un serveur local capable de répondre aux requêtes du navigateur.

Si vous ouvrez http://localhost:3000 dans le navigateur, vous verrez simplement le texte « Serveur Node actif ». C’est normal : ce premier exemple ne sert encore aucun fichier. Il se contente de montrer le principe d’un serveur HTTP minimal qui reçoit une requête et renvoie une réponse. Dans l’étape suivante, nous allons faire évoluer ce script pour qu’il puisse servir de vrais fichiers (HTML, CSS, JavaScript, images…) depuis un dossier du projet.

Cette simplicité est particulièrement utile dans un contexte de développement. Nous pouvons lancer un petit serveur local en quelques secondes, servir un dossier contenant nos fichiers et reproduire le comportement d’un véritable site web. Les modules JavaScript, les requêtes AJAX ou certains mécanismes liés à la sécurité du navigateur peuvent alors fonctionner correctement.
Node devient ainsi un outil léger et très pratique pour créer un environnement de travail rapide, sans dépendre d’une infrastructure plus lourde. C’est cette approche minimaliste que nous allons mettre en place dans la suite de cet article.
Organiser un petit projet pour servir des fichiers
Pour qu’un serveur puisse réellement servir des fichiers, il faut d’abord organiser le projet de manière claire. Une pratique courante consiste à placer tous les fichiers destinés au navigateur dans un dossier dédié. On utilise souvent un dossier nommé public. La structure du projet peut par exemple ressembler à ceci :
mon-projet-node/
│
├─ server.js
├─ package.json
│
└─ public/
├─ index.html
├─ style.css
├─ script.js
└─ images/Le rôle de cette organisation est simple :
- les fichiers du projet Node restent à la racine
- les fichiers accessibles par le navigateur sont placés dans public
Ainsi, lorsque le serveur recevra une requête comme :
http://localhost:3000/index.htmlIl pourra rechercher le fichier correspondant dans le dossier public et le renvoyer au navigateur. Cette séparation présente plusieurs avantages. Elle permet de garder le projet lisible, d’éviter d’exposer accidentellement des fichiers internes et de reproduire une organisation proche de celle que l’on retrouve sur de nombreux serveurs web. Dans la section suivante, nous allons modifier notre script Node afin qu’il puisse lire un fichier dans ce dossier et le renvoyer au navigateur.
Servir des fichiers depuis le serveur Node
Dans les sections précédentes, notre serveur répondait simplement par un texte. Pour qu’il devienne réellement utile dans un projet web, il doit maintenant être capable de renvoyer des fichiers au navigateur : une page HTML, une feuille CSS, un script JavaScript ou une image. Le principe est simple. Lorsqu’une requête arrive sur le serveur, Node peut analyser l’URL demandée, retrouver le fichier correspondant dans le projet, puis envoyer son contenu au navigateur.
Pour cela, nous allons utiliser deux modules natifs de Node :
- path pour construire correctement le chemin vers un fichier
- fs pour lire le fichier sur le disque
Avant de tester cette version, il est utile de préparer rapidement l’environnement. Si un serveur Node tourne déjà dans la console, commencez par l’arrêter avec Ctrl + C.

Créez ensuite un fichier HTML très simple nommé index.html, placez-le dans le dossier public,
<!-- v.02 -->
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Node Server 02</title>
</head>
<body>
<p>Node serveur v.0.2</p>
</body>
</html>… puis arrêtez le serveur dans la console avec Ctrl + C, puis relancez‑le avec la commande node server.js. Nous pouvons alors tester une première version très simple d’un serveur capable de renvoyer ce fichier HTML.
// server.js v.02
import http from 'http';
import fs from 'fs';
import path from 'path';
const root = path.resolve('./public');
const server = http.createServer((req, res) => {
const filePath = path.join(root, 'index.html');
fs.readFile(filePath, (err, content) => {
if (err) {
res.writeHead(500);
res.end('Erreur serveur');
return;
}
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(content);
});
});
server.listen(3000, () => {
console.log('Serveur démarré sur http://localhost:3000');
});Dans cette version, le serveur renvoie systématiquement le fichier index.html placé dans le dossier public. Ce n’est pas encore un serveur complet, mais cela permet déjà de comprendre le mécanisme essentiel :
- le navigateur envoie une requête
- le serveur recherche un fichier
- le fichier est lu sur le disque
- le contenu est renvoyé au navigateur

Dans la section suivante, nous allons améliorer ce script afin qu’il puisse servir différents types de fichiers selon l’URL demandée.
Servir différents fichiers et gérer les types MIME
Jusqu’à présent, notre serveur renvoyait toujours index.html. Pour qu’il devienne réellement utile dans un projet web, il doit être capable de servir différents fichiers selon l’URL demandée par le navigateur. Lorsqu’un navigateur charge une page, il ne demande pas seulement le fichier HTML. Il va ensuite demander d’autres ressources : feuilles CSS, scripts JavaScript, images ou encore fichiers JSON. Le serveur doit donc utiliser l’URL reçue pour retrouver le bon fichier dans le dossier public.
La première modification consiste simplement à utiliser req.url pour déterminer quel fichier doit être servi. Si l’URL correspond à la racine du site (/), nous renvoyons index.html. Dans les autres cas, nous utilisons directement l’URL demandée.
// server.js v.03
const target = req.url === '/' ? 'index.html' : req.url;Il faut ensuite construire le chemin réel du fichier dans le projet. Pour cela, nous utilisons le module path afin d’éviter les erreurs de chemin selon le système d’exploitation.
const filePath = path.join(root, target);Une fois le chemin obtenu, Node peut lire le fichier sur le disque grâce au module fs.
fs.readFile(filePath, (err, content) => {Si le fichier n’existe pas, le serveur renvoie simplement une erreur 404.
if (err) {
res.writeHead(404);
res.end('Fichier non trouvé');
return;
}Si le fichier existe, il peut être renvoyé au navigateur. Cependant, un détail important manque encore : le navigateur doit savoir quel type de fichier il reçoit. Un fichier HTML, une feuille CSS ou un script JavaScript ne sont pas interprétés de la même manière. C’est le rôle des types MIME. Nous allons donc créer une petite table de correspondance entre les extensions de fichiers et leur type.
const mimeTypes = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'text/javascript',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.svg': 'image/svg+xml'
};Il suffit ensuite d’extraire l’extension du fichier demandé et de retrouver le type correspondant.
const ext = path.extname(filePath).toLowerCase();
const contentType = mimeTypes[ext] || 'application/octet-stream';Si l’extension du fichier n’est pas présente dans notre table mimeTypes, le serveur utilise alors application/octet-stream. Ce type MIME générique indique simplement au navigateur qu’il s’agit d’un flux binaire dont le type précis n’est pas connu. Dans ce cas, le navigateur télécharge généralement le fichier ou tente de l’ouvrir comme un fichier brut. Cela permet d’éviter que le serveur renvoie une réponse incohérente lorsqu’une extension n’a pas encore été définie dans notre table.
Le serveur peut alors envoyer le fichier avec l’en‑tête HTTP approprié. Nous utilisons pour cela l’en‑tête Content-Type, qui indique au navigateur comment interpréter le contenu reçu.
res.writeHead(200, { 'Content-Type': contentType });
res.end(content);Il ne nous reste plus qu’à mettre à corriger le fichier HTML et à produire deux fichiers CSS et Javascript pour vérifier que notre mini serveur les serve bien au navigateur.
<!-- v.03 -->
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Node Server 03</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<p>Node serveur v.0.3</p>
<script src="script.js"></script>
</body>
</html>
body {
font-family: sans-serif;
margin: 40px;
background: #f5f5f5;
color: #333;
}
p {
color: #2c6fbb;
}// script.js v.03
document.addEventListener('DOMContentLoaded', () => {
console.log('Script chargé depuis le serveur Node');
});Pour tester cette nouvelle version, arrêtez le serveur dans la console avec Ctrl + C, puis relancez‑le avec :
node server.jsOuvrez ensuite http://localhost:3000 dans votre navigateur. Le fichier index.html sera chargé, puis le navigateur demandera automatiquement les ressources associées comme style.css ou script.js si elles sont présentes dans le dossier public.

Dans le chapitre suivant, nous allons améliorer encore ce serveur afin de sécuriser les chemins et optimiser la lecture des fichiers.
Sécuriser les chemins et améliorer la lecture des fichiers
Notre serveur fonctionne maintenant correctement. Il sert différents fichiers et indique leur type MIME au navigateur. Avant d’aller plus loin, deux améliorations simples permettent de le rendre plus robuste : vérifier les chemins demandés et éviter de charger entièrement les fichiers en mémoire. Lorsqu’un navigateur envoie une requête, l’URL peut parfois contenir des caractères encodés ou des chemins inattendus. Nous pouvons commencer par normaliser l’URL reçue.
// server.js v.04
const safeUrl = decodeURIComponent(req.url);Une vérification simple consiste ensuite à refuser les chemins contenant ... Cela évite qu’une requête tente d’accéder à un fichier situé en dehors du dossier public.
if (safeUrl.includes('..')) {
res.writeHead(403);
res.end('Accès refusé');
return;
}Nous pouvons alors reprendre la logique précédente pour déterminer le fichier demandé.
const target = safeUrl === '/' ? 'index.html' : safeUrl;
const filePath = path.join(root, target);Une autre amélioration concerne la lecture des fichiers. Jusqu’à présent nous utilisions fs.readFile, ce qui charge le fichier entier en mémoire. Pour de petits fichiers ce n’est pas un problème, mais pour des images ou des vidéos il est préférable d’utiliser un flux de lecture.
const stream = fs.createReadStream(filePath);Le flux peut ensuite être envoyé directement vers la réponse HTTP.
stream.on('open', () => {
res.writeHead(200, { 'Content-Type': contentType });
stream.pipe(res);
});
stream.on('error', () => {
res.writeHead(404);
res.end('Fichier non trouvé');
});Cette approche permet au serveur de transmettre le fichier progressivement, sans charger tout son contenu en mémoire. Une fois les scripts en place, pensez à arrêter et redémarrer le serveur :
node server.jsRechargez la page dans le navigateur et observez dans l’onglet Network que les fichiers HTML, CSS et JavaScript sont maintenant servis correctement.

Dans le chapitre suivant, nous verrons comment utiliser ce serveur comme outil de développement local pour tester rapidement des modules JavaScript, des appels AJAX ou de petits projets front end.
Utiliser ce serveur comme outil de développement local
Tout ce que nous avons vu jusqu’ici pourrait très bien être visualisé directement depuis le navigateur avec le protocole file://, sans serveur. Mais dès que nous devons tester des modules JavaScript, des appels AJAX ou la lecture de fichiers JSON, ce mode de lecture directe montre ses limites. Le navigateur bloque alors certaines opérations et renvoie souvent une erreur CORS ou un problème de chargement de module. C’est précisément dans ces situations que ce petit serveur devient très utile.

Pour l’illustrer, mettons en place un module JavaScript qui charge un fichier JSON à l’aide de fetch(). Commençons par créer dans le dossier public un fichier data.json contenant par exemple une petite liste d’albums de krautrock, puis ajoutons un script JavaScript chargé de lire ce fichier. Ce script peut être placé dans public/script.js. Pour rester cohérent avec l’utilisation moderne de JavaScript, nous allons le charger comme module directement depuis la page HTML. Même si ce fichier ne contient pas encore d’instruction import, l’utilisation de type="module" permet au navigateur d’appliquer le mode module et d’afficher plus clairement les éventuelles erreurs dans la console. Dans la page HTML, il suffit donc d’ajouter :
<script type="module" src="script.js"></script>Le navigateur charge alors script.js comme module JavaScript, exécute son contenu et signale immédiatement dans la console toute erreur éventuelle de chargement ou d’exécution.
// fichier : public/data.json
[
{ "artist": "Can", "album": "Tago Mago", "year": 1971 },
{ "artist": "Neu!", "album": "Neu!", "year": 1972 },
{ "artist": "Kraftwerk", "album": "Autobahn", "year": 1974 },
{ "artist": "Amon Düül II", "album": "Yeti", "year": 1970 },
{ "artist": "Faust", "album": "Faust IV", "year": 1973 },
{ "artist": "Cluster", "album": "Zuckerzeit", "year": 1974 },
{ "artist": "Harmonia", "album": "Musik von Harmonia", "year": 1974 }
]// fichier : public/utils.js
export function loadDatas(file) {
fetch(file)
.then(r => r.json())
.then(data => console.log(data))
.catch(error => console.error('Erreur AJAX :', error));
}
// fichier : public/script.js
import { loadDatas } from './utils.js';
document.addEventListener('DOMContentLoaded', () => {
console.log('Script chargé depuis le serveur Node');
loadDatas('/data.json');
});Le navigateur envoie alors une requête vers le serveur Node, qui renvoie simplement le fichier demandé. Le script JavaScript est ensuite exécuté et les données récupérées apparaissent dans la console des outils de développement.

Nous pouvons également vérifier que le serveur sert correctement les images. Pour cela, plaçons une image simple dans le dossier public, par exemple krautrock.jpg, puis appelons-la depuis la page HTML :
<img src="images/krautrock.jpg" alt="Pochette d'album krautrock" width="300">Lors du chargement de la page, le navigateur envoie une nouvelle requête vers http://localhost:3000/krautrock.jpg. Le serveur Node lit alors le fichier image sur le disque et le renvoie au navigateur avec le type MIME correspondant (image/jpeg ou image/png).

Les limites de ce serveur minimal
Le serveur que nous avons construit est volontairement simple. Dans l’exemple développé ici, il se contente de servir des fichiers statiques : HTML, CSS, JavaScript, images ou JSON. Nous pourrions bien sûr le faire évoluer pour qu’il fournisse des données dynamiques, par exemple en le connectant à une base de données. Dans cette version minimale, nous n’avons pas implémenté certaines fonctions que l’on trouve dans des serveurs plus complets, comme la compression des fichiers, la gestion du cache ou la mise en place de HTTPS. Ces mécanismes pourraient toutefois être ajoutés dans un serveur Node plus élaboré à l’aide de bibliothèques ou d’une configuration spécifique.
Cependant, certaines limites ne dépendent pas de notre implémentation mais de la nature même de Node : ce serveur n’interprète pas de langages côté serveur comme PHP. Ces fonctions existent dans des serveurs plus complets comme Apache, Nginx ou dans des outils de développement modernes, voir Installer et configurer un serveur web en local. Malgré tout, pour de nombreux besoins, ce petit serveur Node reste largement suffisant.
Il permet de comprendre le fonctionnement d’un serveur HTTP, de voir comment les fichiers sont servis au navigateur et de disposer d’un environnement local très léger pour expérimenter.
Vers un serveur capable de fournir des données
Le petit serveur que nous venons de construire remplit déjà son rôle : il permet de servir des fichiers et d’expérimenter localement avec des modules JavaScript, des appels AJAX ou des ressources JSON. Mais cette mécanique peut également servir de base à quelque chose de plus intéressant. Dans de nombreuses applications web, le serveur ne se contente pas de distribuer des fichiers. Il devient aussi une source de données pour l’interface, en renvoyant des réponses JSON générées à partir d’une base de données.
Dans l’article suivant, nous ferons évoluer ce serveur afin qu’il puisse communiquer avec MySQL et exposer une petite API capable de fournir des données dynamiques à une application web, Connecter Node.js à une base MySQL pour fournir des données JSON.
