A cura di Prof. Giagnotti — Sviluppo Web Full-Stack
L08 - Connettere frontend e backend
Hai frontend React e backend Express separati. In questa lezione li colleghi con il proxy di Vite, costruisci un layer API pulito, gestisci form POST, DELETE e stati di loading/errore come in produzione. Niente più chiamate a localhost:3001 nel codice: solo /api/…
✓Capire perché Vite :5173 e Express :3001 sono due origini diverse e come la Same-Origin Policy blocca fetch senza proxy o CORS.
✓Configurare server.proxy in vite.config.js con /api → localhost:3001 e changeOrigin, riavviando il dev server correttamente.
✓Creare un layer API dedicato (api/notesApi.js) con GET, POST, DELETE che usa percorsi relativi e controlla sempre res.ok.
✓Costruire useNotes e NoteForm con stati loading, submitting, error/success, validazione client e aggiornamento ottimistico della lista.
✓Gestire errori fullstack con middleware Express a 4 parametri, avvio parallelo con concurrently e checklist di verifica dell'integrazione.
2. Il problema: due origini diverse e CORS
Quando sviluppi in locale, Vite gira su http://localhost:5173 e il server Express su http://localhost:3001. Per il browser sono due origini distinte: host diverso, porta diversa. Il browser applica la Same-Origin Policy e blocca le richieste cross-origin se il server non invia header CORS corretti.
Strategia
Come funziona
Quando usarla
Proxy Vite
Vite inoltra /api/* al backend. Per il browser è tutto localhost:5173
✅ Sviluppo locale — scelta corretta
CORS headers lato server
Express risponde con Access-Control-Allow-Origin
✅ Produzione (domini diversi)
📘 Concetto chiave — Same-Origin Policy
La Same-Origin Policy impedisce a uno script su origine A di leggere la risposta di una richiesta verso origine B, a meno che B non autorizzi esplicitamente. Origine = protocollo + host + porta. Per questo 5173 e 3001 sono diverse.
💡 Consiglio del docente
Non mettere mai http://localhost:3001 hardcoded nei componenti. Usa sempre percorsi relativi /api/.... Domani quando cambi porta o vai in produzione, cambi una riga sola nella config, non 20 file.
3. Configurare il proxy in vite.config.js
Apri vite.config.js nella root del frontend e aggiungi server.proxy:
frontend/vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'export default defineConfig({
plugins: [react()],server: {
proxy: {
// Tutte le richieste che iniziano con /api
// vengono inoltrate al backend Express
'/api': {
target: 'http://localhost:3001',
changeOrigin: true,
}
}
}
})
Come funziona il proxy, passo per passo
Il browser fa fetch('/api/notes') verso localhost:5173/api/notes
Vite intercetta perché il path inizia con /api
Vite re-invia a http://localhost:3001/api/notes
Express risponde a Vite, Vite re-invia al browser
Per il browser tutto sembra venire da localhost:5173 → nessun CORS
changeOrigin: true sostituisce l'header Host da localhost:5173 a localhost:3001, evitando problemi con virtual-host.
✅ Riavvio obbligatorio
Dopo aver modificato vite.config.js devi riavviare con Ctrl+C e npm run dev. HMR non rileva modifiche alla config Vite: serve restart completo.
⚠️ Errore comune — Proxy non funziona
Se apri /api/notes e vedi l'HTML di Vite invece del JSON, hai dimenticato di avviare il backend o hai scritto target sbagliato. Controlla che Express sia su 3001 e che il path inizi proprio con /api anche lato Express (app.use('/api/notes', ...)).
4. Struttura del progetto fullstack
Con L07 il progetto è già diviso in due cartelle. Questa è la struttura che usiamo in L08:
Separare la logica in api/notesApi.js è best practice: se cambiano URL o header, modifichi un file solo.
💡 Consiglio del docente — Organizzazione
Crea subito la cartella src/api/ anche se il progetto è piccolo. Quando aggiungerai auth, upload, paginazione, avrai già il posto giusto. Un file = una risorsa (notes, auth, users). Evita il file unico api.js da 500 righe.
5. Il modulo API: notesApi.js e il controllo res.ok
Centralizza tutte le chiamate. Usa percorsi relativi grazie al proxy.
frontend/src/api/notesApi.js
const API_BASE = '/api/notes'export async function fetchNotes() {
const res = await fetch(API_BASE)
if (!res.ok) throw new Error(`Errore GET note: ${res.status}`)
return res.json()
}export async function createNote(nota) {
const res = await fetch(API_BASE, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(nota)
})
if (!res.ok) {
const errore = await res.json().catch(() => ({}))
throw new Error(errore.messaggio || `Errore POST: ${res.status}`)
}
return res.json()
}export async function deleteNote(id) {
const res = await fetch(`${API_BASE}/${id}`, { method: 'DELETE' })
if (!res.ok) throw new Error(`Errore DELETE ${id}: ${res.status}`)
}
📘 Regola d'oro di fetch
fetch() NON lancia eccezione per 404/500. Risolve la promise con ok: false. Devi sempre controllare: if (!res.ok) throw new Error(...). Altrimenti l'errore passa in silenzio e rompi la UI più avanti.
6. Custom hook useNotes - incapsulare la logica
Il hook espone dati, stati UI e operazioni. I componenti non sanno nulla di fetch.
Disabilita sempre il bottone durante submitting per evitare doppi click. Mostra validazione client-side senza andare in rete, poi gestisci errore server. Reset del form solo dopo successo, mai prima. Così l'utente non perde testo se la rete fallisce.
8. Gestione errori fullstack lato client e server
In fullstack gli errori nascono in 4 punti diversi:
In produzione non inviare mai err.stack al client: espone dettagli interni. Logga lato server, rispondi con messaggio generico. Usa NODE_ENV per distinguere sviluppo e produzione.
9. Avviare frontend e backend insieme + verifica e riepilogo
Opzione A — Due terminali separati
Terminale
# Terminale 1 — backend
cd devnotes/backend
node server.js# Terminale 2 — frontend
cd devnotes/frontend
npm run dev
Opzione B — Script concurrently nella root
Root mono-repo
# nella cartella devnotes/ (root)
npm init -y
npm install -D concurrently# package.json
{
"scripts": {
"dev": "concurrently \"npm run dev --prefix frontend\" \"node backend/server.js\"",
"dev:watch": "concurrently \"npm run dev --prefix frontend\" \"npx nodemon backend/server.js\""
}
}npm run dev
💡 Nodemon — riavvio automatico
Installa nodemon nel backend (npm install -D nodemon) e usa "start": "nodemon server.js". Come HMR per il frontend, ma sul server: ogni modifica .js riavvia automaticamente.
Configura proxy /api → localhost:3001 e verifica che /api/notes da Vite ritorni JSON vero.
Crea notesApi.js con controllo res.ok e testalo con backend spento (deve lanciare errore leggibile).
Implementa useNotes con addNote e removeNote ottimistici, poi integra NoteForm con validazione titolo.
✅ Soluzione ragionata
1. Se apri localhost:5173/api/notes e vedi JSON, proxy OK. Altrimenti errore configurazione o backend spento. 2. Con backend spento fetch rifiuta: il catch in loadNotes imposta error e UI mostra bottone Riprova. 3. Validazione trim() prima di POST evita richiesta inutile; setNotes(prev => [nuova, ...prev]) è aggiornamento ottimistico senza ricaricare dal server.
Riepilogo e concetti chiave
✓Same-Origin Policy — blocca cross-origin; proxy Vite la aggira in sviluppo, in produzione serve CORS su Express.
✓Layer API separato — centralizza fetch, usa percorsi relativi, controlla sempre !res.ok.
✓useNotes + NoteForm — custom hook per stato e CRUD, form con submitting, err/success, validazione client.
✓Middleware errori Express + concurrently — 4 parametri dopo route, JSON strutturato, avvio unico con npm run dev.
📚 Prossimo passo — L09 Autenticazione JWT
Ora che frontend e backend si parlano, è il momento di proteggerli. Nella prossima lezione aggiungerai login, token JWT, bcrypt e protected routes. Porta DevNotes fullstack funzionante con proxy già configurato.