Lezione 10 – Deploy: Vercel + Render

React + Vite + Node.js — Lezione 7 di 10

Backend con Node.js ed Express

Costruisci il server per DevNotes da zero: routing CRUD completo, middleware, CORS e gestione degli errori con Express.

// obiettivi di apprendimento
Capire il ruolo di Node.js come runtime JavaScript lato server
Creare un server Express con route CRUD complete per le note
Comprendere e usare i middleware per parsing JSON, logging e CORS
Implementare una corretta gestione degli errori con status code HTTP
// cosa costruiremo

Creeremo una cartella separata server/ nella root del progetto con un’applicazione Express completa. Esporrà le route /api/notes con tutti i metodi CRUD, userà dati in-memory per ora e sarà pronto a ricevere le richieste dal frontend React nella prossima lezione.


Node.js — JavaScript senza il browser

Node.js è un runtime JavaScript costruito sul motore V8 di Chrome. Permette di eseguire JavaScript al di fuori del browser: su un server, in un terminale, come script di build. La caratteristica chiave è il suo modello event loop + I/O non bloccante: invece di aspettare che una lettura dal disco o una query al database finiscano, Node.js esegue il resto del codice e torna sul risultato quando è pronto.

// perché Node.js per il backend

Usando JavaScript sia sul frontend (React) che sul backend (Node.js) condividi la stessa lingua, gli stessi strumenti (npm, ESLint, Prettier) e lo stesso modello mentale. Non devi cambiare contesto mentale quando passi dal componente React alla route Express.


Express — il framework minimalista

Express è un framework HTTP per Node.js che aggiunge tre cose fondamentali al modulo http nativo: routing dichiarativo, middleware componibili e API semplificate per leggere richieste e inviare risposte.

// ciclo di vita di una richiesta Express
Client
HTTP request
Middleware 1
cors()
Middleware 2
express.json()
Middleware 3
logger
Route handler
GET /api/notes
Client
res.json()

Struttura del progetto server

Il backend vivrà in una cartella server/ separata dalla cartella src/ di React. Questo mantiene le responsabilità ben divise:

devnotes/                ← root del monorepo
├── src/                 ← frontend React (già esistente)
├── server/              ← backend Express (NUOVO)
│   ├── index.js         ← entry point del server
│   ├── routes/
│   │   └── notes.js     ← router dedicato alle note
│   └── data/
│       └── notes.js     ← dati in-memory (array)
├── package.json         ← root package (frontend)
└── vite.config.js

Setup del server Express

Prima di tutto inizializziamo il package del server e installiamo le dipendenze:

# dalla root del progetto
cd server
npm init -y
npm install express cors

Poi creiamo il file principale server/index.js:

// server/index.js
const express = require('express');
const cors = require('cors');
const notesRouter = require('./routes/notes');

const app = express();
const PORT = process.env.PORT || 3001;

// ─── MIDDLEWARE GLOBALI ────────────────────────────────────────

// 1. CORS: consente al frontend (localhost:5173) di chiamare il backend
app.use(cors({
  origin: 'http://localhost:5173', // porta default di Vite
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
}));

// 2. Body parser JSON: popola req.body con il payload JSON della richiesta
app.use(express.json());

// 3. Logger minimale: stampa metodo e URL di ogni richiesta
app.use((req, res, next) => {
  console.log(`${new Date().toISOString()} | ${req.method} ${req.url}`);
  next(); // passa al middleware/route successivo
});

// ─── ROUTE ────────────────────────────────────────────────────
app.use('/api/notes', notesRouter);

// Route di health check — utile per verificare che il server sia vivo
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

// ─── ERRORE 404 (route non trovata) ───────────────────────────
app.use((req, res) => {
  res.status(404).json({ error: 'Route non trovata' });
});

// ─── GESTORE ERRORI GLOBALE ────────────────────────────────────
// Firma speciale con 4 parametri: Express riconosce automaticamente i middleware di errore
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(err.status || 500).json({ error: err.message || 'Errore interno del server' });
});

app.listen(PORT, () => {
  console.log(`Server DevNotes in ascolto su http://localhost:${PORT}`);
});

I middleware — il cuore di Express

Un middleware è una funzione con la firma (req, res, next). Ogni richiesta attraversa in sequenza tutti i middleware registrati finché uno di essi invia una risposta o chiama next() per passare al successivo.

MiddlewareCosa faQuando usarlo
cors()Aggiunge gli header Access-Control-Allow-* alla rispostaSempre — il browser blocca le richieste cross-origin senza questi header
express.json()Parsa il body JSON e lo mette in req.bodyPer tutte le route che ricevono POST/PUT/PATCH
express.static()Serve file statici da una cartellaPer servire la build React in produzione
Logger personalizzatoStampa metodo + URL + timestampIn sviluppo per il debug; in produzione usa `morgan`
Error handler (4 params)Intercetta errori lanciati con next(err)Sempre — va registrato per ultimo
// ordine dei middleware

L’ordine con cui chiami app.use() è l’ordine di esecuzione. I middleware globali (CORS, body parser) devono venire prima delle route. Il gestore degli errori deve essere sempre l’ultimo.


CORS — perché è necessario

Il browser implementa la Same-Origin Policy: blocca le richieste JavaScript verso origini diverse da quella della pagina. Origine = schema + dominio + porta. localhost:5173 (Vite) e localhost:3001 (Express) hanno porte diverse → origini diverse → il browser blocca le richieste senza gli header CORS.

❌ SENZA cors()

Il browser lancia: Access to fetch at 'http://localhost:3001/api/notes' from origin 'http://localhost:5173' has been blocked by CORS policy

✓ CON cors()

Il server aggiunge Access-Control-Allow-Origin: http://localhost:5173 → il browser permette la richiesta.


I dati in-memory

Per ora le note vivono in un array JavaScript nel processo del server. Questo significa che si perdono ad ogni riavvio — ma è perfetto per sviluppare e testare la logica delle route prima di collegare un database.

// server/data/notes.js
let notes = [
  {
    id: 1,
    title: 'Setup del progetto',
    body: 'Installato Node.js, Vite e VS Code con le estensioni giuste.',
    tag: 'lavoro',
    createdAt: '2026-01-10T09:00:00.000Z',
  },
  {
    id: 2,
    title: 'Componenti JSX',
    body: 'Header, NoteCard e NoteList. Le props fluiscono dall\'alto verso il basso.',
    tag: 'studio',
    createdAt: '2026-01-12T10:30:00.000Z',
  },
];

// Contatore per gli ID (simula l'auto-increment del database)
let nextId = 3;

module.exports = { notes, getNextId: () => nextId++ };

Router CRUD per le note

Express permette di raggruppare route correlate in un Router separato. Crea server/routes/notes.js:

// server/routes/notes.js
const express = require('express');
const { notes, getNextId } = require('../data/notes');

const router = express.Router();

// ─── GET /api/notes ────────────────────────────────────────────
// Restituisce tutte le note. Supporta ?tag= per filtrare.
router.get('/', (req, res) => {
  const { tag } = req.query;
  const result = tag
    ? notes.filter(n => n.tag === tag)
    : notes;
  res.json(result);
});

// ─── GET /api/notes/:id ────────────────────────────────────────
router.get('/:id', (req, res) => {
  const note = notes.find(n => n.id === Number(req.params.id));
  if (!note) return res.status(404).json({ error: 'Nota non trovata' });
  res.json(note);
});

// ─── POST /api/notes ───────────────────────────────────────────
// Body atteso: { title, body, tag }
router.post('/', (req, res) => {
  const { title, body, tag = 'personale' } = req.body;

  // Validazione minima
  if (!title || !body) {
    return res.status(400).json({ error: 'title e body sono obbligatori' });
  }

  const newNote = {
    id: getNextId(),
    title,
    body,
    tag,
    createdAt: new Date().toISOString(),
  };

  notes.push(newNote);
  res.status(201).json(newNote); // 201 Created
});

// ─── PUT /api/notes/:id ────────────────────────────────────────
// Sostituzione completa della nota
router.put('/:id', (req, res) => {
  const idx = notes.findIndex(n => n.id === Number(req.params.id));
  if (idx === -1) return res.status(404).json({ error: 'Nota non trovata' });

  const { title, body, tag } = req.body;
  if (!title || !body) {
    return res.status(400).json({ error: 'title e body sono obbligatori' });
  }

  notes[idx] = { ...notes[idx], title, body, tag, updatedAt: new Date().toISOString() };
  res.json(notes[idx]);
});

// ─── DELETE /api/notes/:id ─────────────────────────────────────
router.delete('/:id', (req, res) => {
  const idx = notes.findIndex(n => n.id === Number(req.params.id));
  if (idx === -1) return res.status(404).json({ error: 'Nota non trovata' });

  notes.splice(idx, 1);
  res.status(204).send(); // 204 No Content — nessun body
});

module.exports = router;

Testare il server con curl e il browser

Avvia il server con node server/index.js e testalo prima ancora di collegarlo a React:

# Health check
curl http://localhost:3001/health

# Lista note
curl http://localhost:3001/api/notes

# Nota per ID
curl http://localhost:3001/api/notes/1

# Crea una nota (POST con body JSON)
curl -X POST http://localhost:3001/api/notes \
  -H "Content-Type: application/json" \
  -d '{"title":"Nuova nota","body":"Contenuto di test","tag":"lavoro"}'

# Elimina la nota con id 1
curl -X DELETE http://localhost:3001/api/notes/1
// suggerimento: usa REST Client per VS Code

Invece di curl puoi installare l’estensione REST Client (Huachao Mao) su VS Code: crea un file .http e invia richieste direttamente dall’editor con un click.


Gestione degli errori — pattern consigliato

Per le route async, usa un wrapper che cattura le Promise rejected e le passa a next(err) automaticamente:

// helper: avvolge un handler async e passa gli errori al gestore Express
const asyncHandler = (fn) => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next);

// utilizzo nella route (quando aggiungeremo il database)
router.get('/', asyncHandler(async (req, res) => {
  const notes = await db.find({}); // se lancia, asyncHandler lo cattura
  res.json(notes);
}));
// laboratorio pratico — lab-07-express-backend.md

Nel lab costruirai il server Express completo: struttura cartelle, middleware, router CRUD, test con curl e REST Client, middleware di validazione, nodemon per il reload automatico.

Apri lab-07-express-backend.md su GitHub →
📌 Riepilogo — Punti chiave
  • Express è un framework minimalista che aggiunge routing e middleware al modulo HTTP di Node.js.
  • I middleware sono funzioni (req, res, next) che si eseguono in sequenza — l’ordine di registrazione è l’ordine di esecuzione.
  • CORS è necessario perché il browser blocca per default le richieste cross-origin; il middleware `cors` aggiunge gli header corretti.
  • express.json() parsa il body JSON delle richieste e lo espone come req.body.
  • Ogni operazione CRUD corrisponde a un metodo HTTP: GET, POST, PUT, DELETE.
  • Il gestore degli errori ha firma speciale a 4 parametri (err, req, res, next) e va registrato per ultimo.

Lascia un commento