Atelier — Construire un serveur HTTP pas à pas
Partir d'un serveur minimal et l'améliorer progressivement : routes multiples, parsing de corps, fichiers statiques, gestion d'erreurs.
Cet atelier met en pratique les acquis des 4 pages Apport. Vous partez d'un serveur HTTP minimal et l'améliorez pas à pas : routes multiples, parsing de corps JSON, serveur de fichiers statiques, gestion d'erreurs propre.
Point de départ — un serveur minimal
Voici le serveur vu dans la page Runtime et modules :
import http from "node:http"
import type { IncomingMessage, ServerResponse } from "node:http"
const PORT = 3000
const server = http.createServer((req: IncomingMessage, res: ServerResponse) => {
res.writeHead(200, { "Content-Type": "text/plain" })
res.end(`Bonjour, méthode ${req.method}, chemin ${req.url}`)
})
server.listen(PORT, () => {
console.log(`Serveur écoute sur http://localhost:${PORT}`)
})Lancez-le avec npx tsx src/index.ts et testez avec curl :
curl -i http://localhost:3000/api/ping
# → 200 Bonjour, méthode GET, chemin /api/pingÉtape 1 — Routes multiples
Remplacez la réponse générique par un vrai routage. Répondez différemment selon l'URL :
import http from "node:http"
import type { IncomingMessage, ServerResponse } from "node:http"
const PORT = 3000
const server = http.createServer((req: IncomingMessage, res: ServerResponse) => {
const url = req.url.split("?")[0]
const method = req.method
if (url === "/api/ping" && method === "GET") {
res.writeHead(200, { "Content-Type": "application/json" })
res.end(JSON.stringify({ status: "ok", timestamp: Date.now() }))
return
}
if (url === "/api/health" && method === "GET") {
res.writeHead(200, { "Content-Type": "application/json" })
res.end(JSON.stringify({ status: "healthy" }))
return
}
// Route inconnue
res.writeHead(404, { "Content-Type": "application/json" })
res.end(JSON.stringify({ error: "Not found" }))
})
server.listen(PORT, () => {
console.log(`Serveur écoute sur http://localhost:${PORT}`)
})Testez :
curl -s http://localhost:3000/api/ping # → {"status":"ok","timestamp":...}
curl -s http://localhost:3000/api/health # → {"status":"healthy"}
curl -s http://localhost:3000/unknown # → {"error":"Not found"}
curl -s -X POST http://localhost:3000/api/ping # → {"error":"Not found"} ( méthod e non gérée)Pour l'instant, les méthodes non gérées sur une route connue renvoient aussi 404. L'étape 3 améliorera cela avec un 405.
Étape 2 — Parser le corps d'une requête POST
Pour lire un corps JSON envoyé par le client, il faut accumuler les chunks dès la page Serveur HTTP natif :
import http from "node:http"
import type { IncomingMessage, ServerResponse } from "node:http"
function lireCorps(req: IncomingMessage): Promise<string> {
return new Promise((resolve, reject) => {
let corps = ""
req.on("data", (chunk: string) => { corps += chunk })
req.on("end", () => resolve(corps))
req.on("error", (err) => reject(err))
})
}
const PORT = 3000
const server = http.createServer(async (req: IncomingMessage, res: ServerResponse) => {
const url = req.url.split("?")[0]
const method = req.method
if (url === "/api/items" && method === "POST") {
try {
const corps = await lireCorps(req)
const data = JSON.parse(corps) as { name: string; qty: number }
res.writeHead(201, { "Content-Type": "application/json" })
res.end(JSON.stringify({ id: Date.now(), ...data }))
} catch {
res.writeHead(400, { "Content-Type": "application/json" })
res.end(JSON.stringify({ error: "Corps JSON invalide" }))
}
return
}
if (url === "/api/items" && method === "GET") {
res.writeHead(200, { "Content-Type": "application/json" })
res.end(JSON.stringify({ items: [] }))
return
}
res.writeHead(404, { "Content-Type": "application/json" })
res.end(JSON.stringify({ error: "Not found" }))
})
server.listen(PORT, () => {
console.log(`Serveur écoute sur http://localhost:${PORT}`)
})Testez avec curl :
curl -s -X POST http://localhost:3000/api/items \
-H "Content-Type: application/json" \
-d '{"name":"Visa", "qty": 5}'
# → {"id":1735648291234,"name":"Visa","qty":5}
curl -s -X POST http://localhost:3000/api/items \
-H "Content-Type: application/json" \
-d 'pas du json'
# → {"error":"Corps JSON invalide"}Étape 3 — Codes HTTP précis (404 vs 405)
Séparez les routes inconnues (404) des méthodes non gérées sur une route connue (405) :
import http from "node:http"
import type { IncomingMessage, ServerResponse } from "node:http"
function lireCorps(req: IncomingMessage): Promise<string> {
return new Promise((resolve, reject) => {
let corps = ""
req.on("data", (chunk: string) => { corps += chunk })
req.on("end", () => resolve(corps))
req.on("error", (err) => reject(err))
})
}
const server = http.createServer(async (req: IncomingMessage, res: ServerResponse) => {
const url = req.url.split("?")[0]
const method = req.method
const routesConnues = ["/api/ping", "/api/health", "/api/items"]
if (!routesConnues.includes(url)) {
// Route totalement inconnue → 404
res.writeHead(404, { "Content-Type": "application/json" })
res.end(JSON.stringify({ error: "Not found" }))
return
}
// Route connue mais méthode non gérée → 405
if (url === "/api/items" && method !== "GET" && method !== "POST") {
res.writeHead(405, { "Content-Type": "application/json" })
res.end(JSON.stringify({ error: `Method ${method} not allowed` }))
return
}
if (url === "/api/ping" && method !== "GET") {
res.writeHead(405, { "Content-Type": "application/json" })
res.end(JSON.stringify({ error: `Method ${method} not allowed` }))
return
}
// ... gestion GET/POST normales (voir code étape 2)
res.writeHead(200, { "Content-Type": "application/json" })
res.end(JSON.stringify({ status: "ok" }))
})
server.listen(3000)curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/inconnu
# → 404
curl -s -o /dev/null -w "%{http_code}" -X DELETE http://localhost:3000/api/ping
# → 405Étape 4 — Servir des fichiers statiques
Ajoutez une route qui sert des fichiers since la page Système de fichiers :
import http from "node:http"
import type { IncomingMessage, ServerResponse } from "node:http"
import { readFile } from "node:fs/promises"
import { resolve, extname } from "node:path"
const MIME_TYPES = {
".html": "text/html",
".css": "text/css",
".js": "application/javascript",
".json": "application/json",
".png": "image/png",
".svg": "image/svg+xml",
}
async function servirFichierStatique(res: ServerResponse, cheminFichier: string) {
try {
const content = await readFile(cheminFichier)
const ext = extname(cheminFichier)
const mime = MIME_TYPES[ext] ?? "application/octet-stream"
res.writeHead(200, { "Content-Type": mime })
res.end(content)
} catch {
res.writeHead(404, { "Content-Type": "application/json" })
res.end(JSON.stringify({ error: "Fichier non trouvé" }))
}
}
const server = http.createServer(async (req: IncomingMessage, res: ServerResponse) => {
const url = req.url.split("?")[0]
if (url === "/") {
await servirFichierStatique(res, resolve("public/index.html"))
return
}
if (url.startsWith("/assets/")) {
await servirFichierStatique(res, resolve(`.${url}`))
return
}
res.writeHead(404, { "Content-Type": "application/json" })
res.end(JSON.stringify({ error: "Not found" }))
})
server.listen(3000)Récapitulatif de l'atelier
| Étape | Concept appliqué | Page source |
|---|---|---|
| 1. Routes multiples | req.url, req.method, conditionnement | Serveur HTTP natif |
| 2. Parsing POST | Accumulator de chunks, JSON.parse | Serveur HTTP natif |
| 3. 404 vs 405 | Codes HTTP précis | Serveur HTTP natif |
| 4. Fichiers statiques | fs.readFile, types MIME | Système de fichiers |
🎯 Exercice final de l'atelier
Étendez le serveur pour :
- Ajouter une route
GET /api/items/:idqui renvoie un item par son ID (stocké en mémoire dans un tableau) - Ajouter une route
DELETE /api/items/:idqui supprime un item - Utiliser
URLpour parser proprementreq.url(au lieu desplit("?"))
→ Passez à l'exercice évalué quand vous êtes prêt.
Node.js
Terminez le quiz du chapitre pour le marquer comme complété.
Premier serveur HTTP en Node.js
Créer un serveur HTTP natif avec le module http, gérer les requêtes et réponses, et retourner du JSON — le point de départ de toute API Node.js.
Exercice — Serveur Express + lecture de fichier (et Git)
Construisez un petit serveur Express qui lit un fichier via fs, puis versionnez le tout avec Git, en appliquant Node.js, NPM et les bonnes pratiques.