http.createServer() crea un web server a partire da una funzione, il request handler, che Node chiama per ogni richiesta in arrivo con due oggetti: req (metodo, URL e header inviati dal client) e res (quello che rimandi indietro). Imposta uno status code e degli header su res, poi chiama res.end() con il body. server.listen(3000) inizia ad accettare connessioni sulla porta 3000.
La prima richiesta risponde You asked for GET /hello?name=Ada, e la Console mostra lo stesso metodo e lo stesso URL. Invia DELETE /anything dalla barra della richiesta: risponde lo stesso handler, perché un server http semplice non ha route. Ogni richiesta passa per quell'unica funzione.
Inviare JSON e impostare lo status code
res.writeHead(status, headers) imposta status code e header in una sola chiamata. Per il JSON imposta Content-Type: application/json e invia il risultato di JSON.stringify(); res.end() accetta solo una stringa o un Buffer.
GET /users?limit=1 restituisce [{"id":1,"name":"Ada"}]. req.url è il path più la query string, quindi new URL(req.url, 'http://localhost') lo divide in pathname e searchParams (la base c'è solo perché URL ha bisogno di un indirizzo assoluto). Invia GET /nothing e lo status diventa 404.
Instradare le richieste con uno switch
A una piccola API basta uno switch su metodo e path. Costruisci una chiave come 'GET /users' e dai a ogni route il suo case:
Invia POST /users con il body {"name": "Grace"}: la risposta è 201 con {"id":2,"name":"Grace"}, e ora GET /users elenca entrambi gli utenti. Invia la stessa POST con il body hello e ottieni 400 con {"error":"Body must be JSON"}.
Leggere un body POST
Node non fa il parsing del body al posto tuo. req è uno stream leggibile, e il body arriva a pezzi tramite gli eventi data, poi scatta end quando è completo. readBody() qui sopra raccoglie i pezzi in una stringa e restituisce una promise, così l'handler può fare await. Dato che req è un async iterable, questo ciclo fa lo stesso lavoro:
let data = '';
for await (const chunk of req) data += chunk;
const body = JSON.parse(data);
Un server vero limita anche la dimensione (smette di leggere e risponde 413 appena data.length supera un limite) e controlla req.headers['content-type'] prima di chiamare JSON.parse.
Errori comuni
Dimenticare res.end(). Un ramo che imposta uno status ma non chiude mai la risposta lascia il client in attesa fino al timeout.
Impostare un header dopo il body. Quando res.end() o il primo res.write() sono stati eseguiti, gli header sono già partiti, e res.setHeader() lancia ERR_HTTP_HEADERS_SENT. Usa return quando invii una risposta in anticipo, come negli esempi.
Confrontare req.url con un path. req.url include la query string, quindi req.url === '/users' è falso per /users?limit=1. Confronta pathname.
Quando usare Express
Il modulo http basta per un health check, un ricevitore di webhook o per capire come funziona HTTP. Quando ti servono parametri di route come /users/:id, parsing del body JSON, middleware e gestione degli errori, finisci per scrivere un tuo piccolo framework. Express è quel framework, costruito sugli stessi oggetti req e res; il routing di Express mostra la stessa API con app.get() e app.post().
Domande frequenti
Che differenza c'è tra res.write e res.end?
res.write() invia un pezzo del body e tiene aperta la risposta, quindi puoi chiamarlo più volte. res.end() invia un ultimo pezzo opzionale e chiude la risposta. Ogni risposta ha bisogno di esattamente un res.end().
Come fermo un server HTTP di Node?
Chiama server.close(). Smette subito di accettare nuove connessioni ed esegue la sua callback quando quelle aperte hanno finito, che è quello che serve per uno shutdown pulito su SIGTERM.
Come uso HTTPS con il modulo http?
Usa invece il modulo https: https.createServer({ key, cert }, handler) riceve lo stesso handler più una chiave privata e un certificato. In produzione di solito il TLS lo gestisce un proxy come Nginx o la piattaforma di hosting, e il server Node resta in ascolto su HTTP semplice.
Su quale porta deve stare in ascolto un server Node?
Qualsiasi porta libera sopra la 1024 funziona senza permessi di amministratore; 3000 e 8080 sono comuni in sviluppo. Le piattaforme di hosting di solito indicano la porta in una variabile d'ambiente, quindi scrivi server.listen(process.env.PORT || 3000).