GraphQL con Laravel Lighthouse: quando conviene rispetto a REST e come implementarlo

GraphQL con Laravel Lighthouse: quando conviene rispetto a REST e come implementarlo

Il 14 febbraio 2025 mi ha contattato il CTO di una startup milanese che sviluppa un'app mobile di produttività per professionisti autonomi e microimprese - 10.400 utenti attivi mensili su iOS e Android, fatturato mensile ricorrente di circa 84.000 euro in modello freemium con conversione a paid del 4,1%, team tecnico di 7 persone. L'app comunicava con il backend Laravel 11 tramite un'API REST tradizionale strutturata con circa 60 endpoint distinti, documentata con OpenAPI Specification e servita da un dominio dedicato api.esempio.app. Il CTO aveva ricevuto nell'ultima settimana tre segnalazioni indipendenti dai suoi sviluppatori mobile (un senior iOS interno e due contractor Android esterni) che chiedevano "di passare a GraphQL" per risolvere alcuni problemi ricorrenti: la homepage dell'app mobile richiedeva 14 chiamate REST separate per caricare tutti i dati necessari al rendering iniziale (profilo utente, notifiche recenti, task pianificati, statistiche settimanali, raccomandazioni personalizzate, banner commerciali), con tempo totale di oltre 3 secondi su rete 4G mediocre; le app mobile spesso avevano bisogno di campi diversi fra iOS e Android, ma la REST API restituiva sempre tutto, causando overfetching; ogni nuova funzionalità mobile richiedeva coordinamento con il team backend per creare endpoint dedicati o modificare esistenti.

Il CTO mi ha chiamato per una second-opinion tecnica prima di avviare un progetto di riscrittura completa dell'API in GraphQL, che il senior iOS stimava in 4-5 mesi di lavoro coordinato fra backend e mobile. Dopo due giornate di analisi tecnica dell'architettura esistente e interviste con i tre sviluppatori mobile, la mia valutazione è stata più articolata di un semplice sì/no: i problemi reali che il team mobile stava sperimentando erano oggettivi e meritavano soluzione, ma GraphQL come sostituzione completa dell'API REST era una soluzione sproporzionata rispetto al problema. In tre settimane di intervento distribuite in un mese abbiamo implementato un approccio ibrido: mantenuto la REST API esistente per il 90% delle operazioni (dove funzionava bene e non presentava problemi), introdotto GraphQL via Laravel Lighthouse esclusivamente per tre endpoint aggregatori di alto traffico (homepage mobile, schermata task, schermata statistiche), con la stessa autenticazione JWT esistente. Il risultato al go-live è stato: tempo di caricamento della homepage mobile sceso da 3,1 secondi a 680 millisecondi (riduzione del 78%), numero di chiamate HTTP per il first-load sceso da 14 a 1 sulle tre schermate che lo richiedevano davvero, zero disruption sul resto dell'applicazione che continua a usare REST. Costo consulenziale: 11.400 euro. Alternativa evitata (riscrittura completa API): circa 85.000 euro stimati e 4-5 mesi di coordinamento fra team backend e mobile.

Questo articolo descrive il framework di decisione che applico quando un cliente PMI mi chiede di valutare GraphQL rispetto a REST, basato sull'esperienza di circa 8 progetti reali negli ultimi tre anni. Il principio guida è uno: GraphQL è uno strumento specializzato per risolvere un problema specifico - l'aggregazione di dati eterogenei richiesti da client multipli con esigenze variabili - e va adottato chirurgicamente dove quel problema esiste, non come sostituzione ideologica di REST. L'adozione massiva di GraphQL fatta per moda architetturale produce sistematicamente più complessità di quanta ne risolve.

Perché GraphQL non è "REST in meglio" - è uno strumento diverso con trade-off specifici

GraphQL è un query language per API sviluppato originariamente da Facebook nel 2012 per risolvere un problema specifico del loro mobile app: l'app mobile aveva bisogno di dati aggregati da fonti eterogenee e la REST API esistente richiedeva troppe chiamate separate per costruire una singola schermata. La documentazione ufficiale GraphQL descrive in dettaglio il linguaggio, i type, le query, le mutation e le subscription, e rappresenta il punto di partenza conoscitivo per qualunque team che valuti l'adozione. Il beneficio principale di GraphQL sul lato client è il fetch-what-you-need: il client dichiara esplicitamente quali campi gli servono e il server restituisce solo quelli, eliminando l'overfetching tipico di REST dove l'endpoint restituisce sempre l'intero modello. Il secondo beneficio è l'aggregation naturale: un singolo query GraphQL può recuperare dati da risorse diverse in una sola round-trip, eliminando il chained fetching tipico di REST.

Ma GraphQL ha trade-off specifici che spesso le descrizioni entusiaste non enfatizzano abbastanza. Primo trade-off: il caching è molto più complesso rispetto a REST. REST ha caching HTTP nativo (Cache-Control, ETag, CDN edge caching) che funziona trasparentemente. GraphQL tipicamente fa tutto via POST con corpo variabile, quindi il caching HTTP standard non funziona; serve cache applicativa con strategie custom di invalidation. Secondo trade-off: l'autorizzazione fine-grained è più costosa da implementare. REST ha un endpoint per operazione: l'autorizzazione si controlla al middleware level in modo uniforme. GraphQL permette query dinamiche: l'autorizzazione va controllata per campo e per riga, richiedendo logica più sofisticata nel resolver layer. Terzo trade-off: il rischio N+1 è strutturale e amplificato. In REST il problema N+1 emerge dentro un singolo endpoint e si risolve con eager loading consapevole. In GraphQL il problema N+1 emerge dinamicamente in funzione della query arbitraria del client, richiede DataLoader pattern per risolverlo sistematicamente, e debugging performance diventa significativamente più complesso.

Il risultato pratico di questi trade-off è che GraphQL si giustifica chiaramente in contesti specifici (backend per multiple client con esigenze divergenti, aggregatori di fonti eterogenee, API pubbliche dove la flessibilità del client è un valore di prodotto), e si giustifica molto meno in altri (API interne fra componenti controllati, backend per singolo client, API con cacheabilità importante per performance). Il caso del cliente milanese rientrava nella prima categoria solo per tre schermate specifiche - non per l'intera applicazione. La decisione giusta era intervenire chirurgicamente.

Se stai valutando un progetto di adozione GraphQL sul tuo backend Laravel o Symfony e vuoi un'analisi tecnica indipendente che distingua fra benefici reali e sovraingegneria, nel mio profilo professionale trovi il dettaglio degli interventi di design di API e decisioni architetturali che ho guidato in contesti PMI italiane, sempre con approccio calibrato sui problemi concreti del cliente specifico.

Laravel Lighthouse: l'implementazione PHP matura e production-ready di GraphQL

Per un backend Laravel, l'implementazione GraphQL di riferimento è Laravel Lighthouse, un pacchetto open-source maturo con community attiva e documentazione estensiva. Lighthouse integra nativamente con Eloquent ORM, supporta schema-first development (si definisce lo schema GraphQL in file .graphql dichiarativi e Lighthouse genera i resolver partendo dalle convenzioni), offre funzionalità avanzate come subscriptions via Laravel Echo, batching automatico via DataLoader, autorizzazione tramite policy Laravel native. La curva di apprendimento è ragionevole per un team che già conosce Laravel - l'API sembra naturale e integrata.

Il setup iniziale sul cliente milanese è stato questo. Ho installato Lighthouse con composer require nuwave/lighthouse e pubblicato la configurazione di default. Ho creato lo schema GraphQL in graphql/schema.graphql definendo solo i tre oggetti aggregatori necessari (HomepageData, TasksScreenData, StatsScreenData) con i relativi campi e relazioni. Ho implementato i resolver custom per i campi che richiedevano logica applicativa complessa (statistiche aggregate, raccomandazioni personalizzate). Per l'autenticazione, ho integrato con il middleware JWT esistente del progetto, in modo che i client mobile potessero continuare a usare gli stessi token già emessi dalla REST API. Per la sicurezza, ho attivato query complexity limit e query depth limit per prevenire query DoS - un rischio reale in API GraphQL pubbliche dove il client può costruire query arbitrariamente complesse. Il risultato è stato un'API GraphQL limitata in scope ma robusta in architettura, conviviale con l'API REST esistente sullo stesso dominio backend.

Il pattern ibrido REST + GraphQL: quando e come combinarli senza frammentazione

L'architettura ibrida che ho implementato sul cliente milanese è un pattern che applico in molti progetti: REST per la maggioranza delle operazioni, GraphQL per endpoint aggregatori specifici. Il pattern ha tre vantaggi concreti. Primo, adozione incrementale: non richiede riscrittura dell'intera API, il team può introdurre GraphQL gradualmente dove porta valore. Secondo, preservazione dei benefici di REST: caching HTTP, idempotenza delle operazioni, documentazione OpenAPI, compatibilità con tooling esterno - tutto questo continua a funzionare per la parte REST. Terzo, focalizzazione della complessità: i trade-off di GraphQL (caching, autorizzazione fine-grained, N+1) si concentrano sui pochi endpoint GraphQL specifici, invece di propagarsi su tutto il backend.

La regola operativa che applico per decidere se un endpoint giustifica GraphQL è una checklist di tre criteri. Primo criterio: l'endpoint aggrega dati da almeno 4-5 risorse diverse? Se un endpoint serve solo una risorsa (es. "lista task del utente"), REST è quasi sempre meglio. Secondo criterio: client multipli con esigenze campo-selettive divergenti consumano lo stesso endpoint? Se l'endpoint è consumato da un unico client (es. app iOS dedicata), la flessibilità di GraphQL è sovraingegneria. Terzo criterio: l'endpoint è chiamato ad alta frequenza dal client? La latenza di network è un costo fisso significativo per ogni chiamata - aggregare via GraphQL si ripaga molto per endpoint hot, molto meno per endpoint usati raramente. Sul cliente milanese, solo tre endpoint su circa sessanta hanno passato tutti e tre i criteri. Quei tre sono stati migrati a GraphQL, i restanti cinquantasette sono rimasti REST come prima.

Il problema N+1 in GraphQL: come DataLoader previene il disastro di performance

Il problema architetturale più insidioso di GraphQL è la versione amplificata del classico N+1 query problem. In REST, un endpoint è scritto dal backend developer che conosce le sue query e può applicare eager loading consapevole. In GraphQL, il client costruisce la query dinamicamente a runtime, e una query come users { id name posts { id title comments { id body author { name } } } } genera nativamente un disastro N+1 - una query per lista utenti, N query per post degli utenti, M query per commenti di ogni post, P query per autore di ogni commento. Se servi 20 utenti con 10 post ciascuno e ogni post ha 5 commenti, stai emettendo oltre 1000 query SQL per un singolo request GraphQL. Senza mitigazione, GraphQL è inutilizzabile su qualunque dataset non banale.

La soluzione canonica è il pattern DataLoader, introdotto originariamente da Facebook per la loro implementazione GraphQL e ora standard de facto per tutte le implementazioni in ogni linguaggio. Il DataLoader è una batching layer che accumula le richieste di caricamento di risorse durante la singola elaborazione di un request GraphQL e le esegue in blocco con una singola query IN-clausola SQL. Ad esempio, invece di N query separate per recuperare N post, il DataLoader raccoglie tutti gli ID richiesti durante il resolve della query e fa una sola query SELECT * FROM posts WHERE id IN (?, ?, ?, ...). La documentazione di Lighthouse sul suo DataLoader integrato descrive in dettaglio il pattern e le sue opzioni di configurazione. Sul cliente milanese, l'applicazione corretta di DataLoader sulle relazioni Eloquent del tre schermate ha ridotto il numero di query per request da oltre 200 (nel primo prototipo senza DataLoader) a 3-4 query totali (con DataLoader configurato correttamente). Il pattern concettualmente deriva dagli stessi principi di eager loading disciplinato che ho descritto nel mio articolo sui 10 pattern Eloquent che rallentano le query senza che tu lo sappia, applicati alla semantica di aggregazione dinamica di GraphQL.

Autorizzazione fine-grained e sicurezza: il livello più critico di GraphQL in produzione

L'ultimo aspetto critico di GraphQL in produzione è la sicurezza e autorizzazione. In REST, l'autorizzazione è un problema risolto da decenni: middleware sull'endpoint, controllo del ruolo utente sull'operazione, policy per risorsa. In GraphQL, la query del client può attraversare arbitrariamente il grafo di oggetti, e l'autorizzazione deve essere applicata a ogni nodo del grafo indipendentemente - altrimenti un client malintenzionato può costruire query che attraversano relazioni per accedere a dati che non avrebbe dovuto vedere.

Il pattern operativo che applico in Lighthouse è l'integrazione con le Policy native di Laravel tramite la direttiva @can sullo schema. Ogni campo sensibile dello schema ha una policy dichiarata che Lighthouse esegue automaticamente al resolve: se la policy rifiuta, il campo viene escluso dal risultato con errore di autorizzazione esplicito. Parallelamente, uso query complexity analysis per calcolare il costo complessivo di ogni query prima dell'esecuzione - query che superano una soglia (tipicamente 100 punti) vengono rifiutate senza esecuzione, prevenendo attacchi di DoS che costruiscono query profondamente nidificate per saturare il database. La configurazione di rate limiting per utente autenticato completa il layer di sicurezza, limitando il numero di query per minuto per prevenire scraping. L'insieme di questi controlli - documentati nelle OWASP API Security Top 10 come rischio API4 "Unrestricted Resource Consumption" - va configurato esplicitamente in ogni deploy GraphQL di produzione, con valori tarati sul carico atteso del sistema.

Il risultato finale dell'intervento sul cliente milanese, misurato a sei mesi dal go-live dell'architettura ibrida, è stato il seguente. Tempo di caricamento della homepage mobile: sceso da 3,1 secondi a 680 millisecondi (riduzione del 78%). Numero di chiamate HTTP per il first-load delle tre schermate migrate: sceso da 14-18 a 1 per schermata. Consumo di banda dati mobile per il first-load: sceso del 62% grazie al field-selective fetching. Tasso di abbandono della homepage mobile in fase di caricamento: sceso dal 14% al 3,2%. Complessità aggiuntiva del codebase backend: limitata a un singolo package (Lighthouse) e a un file schema dedicato di 180 righe - gestibile dal team senza formazione significativa. Numero di endpoint REST esistenti che hanno dovuto essere modificati: zero. Costo consulenziale dell'intervento: 11.400 euro. Alternativa evitata di riscrittura completa API: circa 85.000 euro stimati. ROI dell'intervento mirato vs riscrittura completa: oltre 7x, considerando l'identico beneficio funzionale del singolo problema che davvero importava al team mobile.

Se guidi un team mobile che si trova di fronte a problemi di latency o overfetching sulle API REST del backend, e stai valutando l'adozione di GraphQL come soluzione, vale la pena fare prima un'analisi precisa di quali schermate davvero soffrono del problema e quali funzionano bene. Nella maggioranza dei casi PMI, la soluzione corretta è un approccio ibrido mirato invece di una riscrittura completa - con ROI radicalmente superiore e rischio operativo molto inferiore. Se vuoi confrontarti sul tuo caso specifico con una valutazione tecnica indipendente, contattami per una consulenza preliminare: in una giornata di analisi guidata produciamo insieme una mappatura dei tuoi endpoint attuali con identificazione precisa dei candidati GraphQL, una stima di ROI calibrata sul tuo traffico reale, e un piano di implementazione incrementale che non richiede fermo del tuo ciclo di delivery esistente.

Ultima modifica: