Non capisci cosa succede dietro le quinte di Node.js?
Hai scritto JavaScript per il frontend, ma quando passi al backend con Node.js ti senti come se avessi perso il controllo. L'event loop gira in testa, i moduli NPM sembrano un labirinto e il primo server HTTP non risponde come vorresti. Tranquillo, è normale. Noi, di Meteora Web, abbiamo iniziato esattamente così. Ma con un po' di metodo — e qualche esempio concreto — la nebbia si dirada in fretta.
In questa guida pratica non ti diamo la solita teoria accademica. Partiamo dal codice e dal funzionamento reale. Alla fine, avrai un server funzionante e capirai perché Node.js gestisce migliaia di richieste senza impazzire.
L'event loop di Node.js: il cuore asincrono
Prima di scrivere una riga di server, devi capire come Node.js esegue il codice. JavaScript è single-thread, ma Node.js non si ferma mai. Il segreto è l'event loop: un ciclo che gestisce le operazioni asincrone (lettura file, richieste HTTP, timer) senza bloccare il thread principale.
Immagina una cucina con un solo cuoco (il thread). Invece di aspettare che l'acqua bolle, il cuoco inizia altre preparazioni, poi torna a controllare. L'event loop funziona così: esegue il codice, mette in coda le callback, e le esegue quando pronte.
Come gira l'event loop
L'event loop ha sei fasi (timers, I/O callbacks, idle, poll, check, close). Non serve memorizzarle tutte. Quello che conta è sapere che le operazioni asincrone (setTimeout, fs.readFile, http.request) non bloccano. Ecco un esempio che chiarisce subito:
console.log('1 - sincrono');
setTimeout(() => {
console.log('2 - timer 0ms');
}, 0);
Promise.resolve().then(() => {
console.log('3 - microtask');
});
console.log('4 - sincrono');
Esegui questo script. L'output sarà:
1 - sincrono
4 - sincrono
3 - microtask
2 - timer 0ms
Perché? Le microtask (Promise) hanno priorità sulle callback del timer. L'event loop prima svuota lo stack sincrono, poi esegue tutte le microtask, poi passa alla fase timer. Se non capisci questo, il debug dei tuoi server sarà un incubo.
Errore comune: credere che async = immediato
Molti sviluppatori si aspettano che un setTimeout con 0ms esegua subito. Invece va in coda. Lo stesso vale per le richieste di rete. Node.js non aspetta: registra la callback e passa oltre. Quando la risposta arriva, l'event loop la raccoglie al ciclo successivo.
Cosa fare adesso: Crea un file event-loop-demo.js con il codice sopra e eseguilo con node event-loop-demo.js. Gioca con Promise e setTimeout per vedere l'ordine. È il primo passo per fidarti dell'asincronia.
NPM: gestire le dipendenze senza impazzire
Node Package Manager (NPM) è il gestore di pacchetti più usato al mondo. Ma spesso lo usiamo in modo passivo: npm install e via. Senza capire cosa succede sotto. Noi abbiamo visto progetti con node_modules da 500 MB perché qualcuno ha installato tutto senza guardare. Ecco come usare NPM con consapevolezza.
Il cuore: package.json
Ogni progetto Node.js ha un package.json. Contiene nome, versione, dipendenze, script. Creane uno con npm init -y. Poi installa un pacchetto, ad esempio express:
npm init -y
npm install express
Ora guarda package.json: sotto dependencies trovi "express": "^4.18.2". Il caret (^) permette aggiornamenti minori. node_modules contiene il codice di express e le sue dipendenze. Mai modificare node_modules a mano.
Dependencies vs DevDependencies
Un errore comune: mettere tutto in dependencies. Usa --save-dev per strumenti di sviluppo (test, linter, compilatori). Esempio:
npm install --save-dev jest
In produzione, npm install --production installa solo le dipendenze di runtime. Riduci la superficie d'attacco e lo spazio su disco.
Script personalizzati
In package.json, aggiungi script per avviare il server, testare, eseguire migrazioni. Esempio:
"scripts": {
"start": "node server.js",
"dev": "node --watch server.js",
"test": "jest"
}
Poi esegui npm run dev. Node.js 18+ supporta --watch per riavviare automaticamente ai cambiamenti.
Cosa fare adesso: In un progetto nuovo, inizializza NPM, installa Express come dipendenza, aggiungi uno script "start" e verifica che funzioni con npm start (di default esegue node server.js).
Il tuo primo server HTTP
Ora mettiamo tutto insieme. Creeremo un server HTTP che risponde a richieste GET e POST. Useremo il modulo nativo http di Node.js, senza framework. Capirai esattamente cosa succede, poi potrai passare a Express.
Server base con modulo http
// server.js
const http = require('http');
const server = http.createServer((req, res) => {
console.log(`Richiesta: ${req.method} ${req.url}`);
if (req.method === 'GET' && req.url === '/') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Ciao da Meteora Web!');
} else if (req.method === 'GET' && req.url === '/api') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ messaggio: 'Funziona!' }));
} else {
res.writeHead(404);
res.end('Pagina non trovata');
}
});
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`Server in ascolto su http://localhost:${PORT}`);
});
Salva come server.js, esegui node server.js e apri http://localhost:3000 nel browser. Funziona.
Gestire richieste POST e body
Per ricevere dati da un form o da una chiamata API, devi leggere lo stream del body. Ecco come:
if (req.method === 'POST' && req.url === '/submit') {
let body = '';
req.on('data', chunk => { body += chunk; });
req.on('end', () => {
console.log('Body ricevuto:', body);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ ricevuto: body }));
});
}
Nota: senza framework devi gestire tu il parsing. Per JSON usa JSON.parse(body). Per URL-encoded, querystring o new URLSearchParams.
Errori comuni da evitare
- Non chiamare res.end(): la richiesta rimane aperta e il browser aspetta. Sempre chiudere la risposta.
- Leggere il body due volte: lo stream si consuma; una volta letto, non puoi rileggerlo.
- Non impostare Content-Type: il browser interpreta come text/html di default. Per JSON devi specificarlo.
- Porta occupata: se ricevi
EADDRINUSE, cambia porta o termina il processo.
Cosa fare adesso: Estendi il server con una rotta POST che riceve un nome e risponde con un saluto personalizzato. Testalo con curl o Postman.
Dal server nativo a Express
Ora che capisci il meccanismo sottostante, puoi usare Express senza paura. Sapere cosa fa app.get() sotto il cofano ti evita bug misteriosi. Express non è magia: è una libreria che incapsula http.createServer e astrae il routing, il body parsing, i middleware.
// Con Express
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Ciao da Express!');
});
app.listen(3000);
Pulito, vero? Ma se non capisci l'event loop e il modulo http, quando arriva un errore di middlewar ordinato male o di stream non consumato, perdi ore.
In sintesi — cosa fare adesso
- Esegui il demo dell'event loop: crea un file con setTimeout, Promise, console.log. Osserva l'ordine.
- Crea un server HTTP nativo: scrivi il codice sopra, testalo con curl. Poi aggiungi una rotta POST e testala.
- Inizializza NPM in un progetto:
npm init -y, installa Express, crea uno scriptstart. - Leggi il body di una richiesta: nel server nativo, gestisci una POST con dati JSON. Parsa il body e restituisci una risposta.
- Approfondisci il routing: se vuoi un frontend lato client, guarda la nostra guida a Next.js App Router.
Node.js non è più un mistero. Hai gli strumenti per capire come gira il motore. Ora tocca a te scrivere il prossimo server.
Sponsored Protocol