Chiamate HTTP in JavaScript: da Fetch ad Axios fino a RxJS e React Query con QueryClientProvider

Fare una chiamata HTTP in JavaScript può sembrare una delle operazioni più elementari per uno sviluppatore web. Inizialmente, una riga di codice con fetch('/api/users') è sufficiente per ottenere i dati dal server e visualizzarli sull’interfaccia. Tuttavia, nella pratica quotidiana, anche le applicazioni più piccole si trovano a gestire situazioni più complesse: caricamento, gestione degli errori, scadenza dei token, richieste duplicate, caching, refetch al ritorno su una tab, sincronizzazione tra componenti, retry automatici, debounce per ricerche live e concorrenza tra richieste.
Questa evoluzione trasforma una “semplice chiamata” in un’intera architettura di gestione del dato nel tempo. Il vero problema non è più il trasporto dei dati, ma la gestione dello stato e la coerenza delle informazioni nella tua applicazione. Per affrontare queste sfide, è fondamentale comprendere le varie soluzioni disponibili e sapere quando è opportuno adottarle.
Nel seguito, esploreremo una progressione logica che parte da strumenti nativi e leggeri, per arrivare a librerie avanzate che offrono una gestione completa del server state. Analizzeremo Fetch API, Axios, RxJS (stile Angular) e il moderno approccio di React con TanStack Query e QueryClientProvider, evidenziando vantaggi, limitazioni e scenari d’uso ideali per ciascuna opzione.
Fetch API: il trasporto puro
Fetch rappresenta lo standard nativo dei browser per effettuare richieste HTTP. È basato su Promise, il che lo rende moderno, leggibile e facilmente integrabile con le funzionalità asincrone di JavaScript. Grazie alla sua semplicità, permette di scrivere codice pulito e minimalista, focalizzandosi esclusivamente sul recupero dei dati senza introdurre dipendenze esterne.
async function loadUsers() {
const response = await fetch('/api/users');
if (!response.ok) throw new Error('Errore HTTP');
return response.json();
}Questa semplicità è un vero punto di forza quando l’applicazione è di piccole dimensioni, non richiede meccanismi di caching avanzati e non ha bisogno di orchestrare uno stato complesso. Inoltre, se desideri zero dipendenze, Fetch è la scelta ideale, poiché è già presente in tutti i moderni ambienti browser.
Tuttavia, Fetch è volutamente minimale e non offre funzionalità avanzate come timeout nativi, retry automatici, intercettori globali o una cache intelligente. In pratica, è come un coltello da cucina: può svolgere molti compiti, ma se ti servono strumenti più sofisticati dovrai costruirli da solo o affidarti a librerie dedicate.
Axios: il pragmatico
Axios è una libreria HTTP molto popolare che aggiunge una serie di caratteristiche ergonomiche rispetto a Fetch. Tra le più utili troviamo il supporto integrato per timeout, la possibilità di definire intercettori per richieste e risposte, e una gestione semplificata degli errori. Queste funzionalità rendono Axios una scelta pratica per progetti di media complessità.
axios.get('/api/users', { timeout: 5000 });
axios.interceptors.request.use(config => {
config.headers.Authorization = `Bearer ${token}`;
return config;
});Gli intercettori permettono di inserire logica comune, come l’attacco di token JWT a tutte le richieste, senza dover ripetere il codice in ogni chiamata. Inoltre, Axios gestisce sia ambienti browser che Node.js, offrendo una coerenza tra le piattaforme, il che è particolarmente utile in team di sviluppo dove si lavora su più stack.
Nonostante non rivoluzioni il modo di fare richieste, Axios è estremamente comodo e riduce il boilerplate. Per le applicazioni che richiedono una gestione centralizzata degli errori, l’uso di token e una coerenza tra client e server, Axios rappresenta un compromesso ideale tra semplicità e funzionalità avanzate.
Angular e RxJS: quando l’HTTP diventa reattivo
In Angular, le chiamate HTTP non avvengono tramite Fetch o Axios, ma mediante l’HttpClient che restituisce Observable basati su RxJS. Questo approccio trasforma la tradizionale chiamata Promise in un flusso di dati nel tempo, consentendo di gestire scenari più complessi come cancellazioni, debounce, combinazioni di flussi e retry sofisticati.
this.http.get('/api/users')
.pipe(
retry(2),
catchError(() => of([]))
)
.subscribe(data => console.log(data));Con RxJS, è possibile cancellare richieste precedenti usando switchMap, applicare debounce per ridurre il traffico di rete durante ricerche live, o combinare più flussi di dati per creare logiche UI avanzate. La potenza di RxJS è evidente quando l’interfaccia richiede reattività elevata e la gestione dei flussi di eventi è centrale.
Tuttavia, questa potenza porta un costo: la curva di apprendimento è più ripida e il codice può diventare verboso se non ben strutturato. Per semplici operazioni CRUD, l’utilizzo di RxJS può risultare eccessivo, ma quando la UI è altamente interattiva e le richieste devono essere orchestrate, RxJS rappresenta uno strumento indispensabile.
React: il punto di svolta
React non impone alcun metodo per effettuare richieste HTTP: puoi utilizzare Fetch, Axios o qualsiasi altra libreria. Il problema nasce quando si tenta di gestire lo server state manualmente con useEffect, generando codice complesso e difficile da mantenere. È qui che entra in gioco TanStack Query, ora noto come React Query, una libreria specificamente progettata per la gestione dei dati provenienti dal server.
TanStack Query non è semplicemente una wrapper per HTTP; è una soluzione completa per il server state, che si occupa di caching, sincronizzazione, refetch, retry e invalidazione. Integrare TanStack Query in un’app React richiede l’uso di un QueryClientProvider, che funge da cervello centrale per tutta l’applicazione.
Questa architettura centralizzata permette di dichiarare le dipendenze dei dati a livello di componenti, evitando duplicazioni di richieste e garantendo che tutti i componenti condividano lo stesso stato di cache. Il risultato è una code‑base più pulita, meno bug legati a richieste multiple e una migliore esperienza utente grazie a caricamenti più rapidi e a un’interfaccia più reattiva.
QueryClientProvider: il cervello centrale
Il QueryClientProvider avvolge l’intera applicazione React e fornisce al contesto un’istanza di QueryClient, responsabile di gestire la cache globale, coordinare i refetch, eseguire retry automatici e sapere quando un dato è stale. Questa configurazione è più di un semplice dettaglio tecnico: rappresenta la base di un’architettura scalabile.
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<Main />
</QueryClientProvider>
);
}Grazie al provider, tutti i componenti possono accedere allo stesso client, sincronizzando le loro richieste e condividendo i risultati in modo trasparente. Senza questo livello centrale, ogni componente vivrebbe in isolamento, replicando logiche di fetching e aumentando la probabilità di errori e richieste duplicate.
Il vantaggio principale è la capacità di orchestrare le chiamate in modo coerente, riducendo il lavoro di boilerplate e migliorando la manutenibilità del progetto, specialmente quando l’applicazione cresce in complessità e numero di componenti.
Configurazione intelligente
Una delle caratteristiche più potenti di TanStack Query è la possibilità di definire opzioni di default a livello globale, in modo da uniformare il comportamento di tutte le query dell’applicazione. Questo include impostazioni come il numero di retry, il tempo di considerazione di un dato come “fresco” (staleTime) e la gestione del refetch al focus della finestra.
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 2,
staleTime: 60000,
refetchOnWindowFocus: false,
},
},
});Il parametro staleTime determina per quanto tempo un dato è considerato aggiornato prima di essere considerato “stale”, riducendo le richieste inutili. Il retry aumenta la resilienza dell’applicazione contro errori temporanei di rete, mentre refetchOnWindowFocus evita chiamate superflue quando l’utente ritorna nella scheda del browser.
Queste scelte non sono semplici dettagli tecnici, ma decisioni strategiche che influenzano le prestazioni, la user experience e il consumo di risorse del server. Configurare correttamente queste opzioni sin dall’inizio consente di mantenere un comportamento prevedibile e ottimizzato man mano che l’applicazione evolve.
useQuery: dichiarare dati, non richiederli
Il hook useQuery è il fulcro di TanStack Query: consente di dichiarare che un componente dipende da una certa fonte di dati, piuttosto che avviare manualmente una chiamata. Questo approccio sposta la mentalità da “fare una richiesta” a “definire la dipendenza”.
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(r => r.json())
});Quando più componenti utilizzano la stessa queryKey, TanStack Query condivide la cache, evitando richieste duplicate e assicurando che ogni parte dell’interfaccia rifletta lo stesso stato di dati. Inoltre, la sincronizzazione avviene automaticamente: se un componente aggiorna la cache, gli altri componenti reagiscono subito senza codice aggiuntivo.
Questo modello rappresenta un salto concettuale significativo rispetto a fetch o Axios puro, poiché riduce drasticamente il boilerplate legato alla gestione dello stato, migliora la coerenza dei dati e consente di concentrarsi maggiormente sulla logica di business della propria applicazione.
Mutations e invalidazione
Le operazioni di mutazione (POST, PUT, DELETE) sono gestite da useMutation, che offre un flusso chiaro per aggiornare i dati sul server e mantenere la cache sincronizzata. Dopo una mutazione di successo, è possibile invalidare le query correlate, costringendo TanStack Query a refetchare i dati più recenti.
const mutation = useMutation({
mutationFn: addUser,
onSuccess: () => {
queryClient.invalidateQueries(['users']);
}
});Il processo è lineare: si effettua il POST, si invalida la cache della query users, e TanStack Query esegue automaticamente una nuova GET per aggiornare l’interfaccia. Questo elimina la necessità di usare useEffect per gestire aggiornamenti manuali, riducendo errori come richieste duplicate o stato incoerente.
L’approccio di TanStack Query alla gestione delle mutazioni costituisce una soluzione enterprise per applicazioni React moderne, garantendo che le operazioni di scrittura e lettura siano sempre allineate e che l’esperienza utente rimanga fluida e reattiva.
Confronto finale
Fetch è ideale per semplicità e zero dipendenze, ma richiede una gestione manuale dello stato.
Axios offre un compromesso pratico con timeout e intercettori integrati, adatto a applicazioni di media complessità.
RxJS è potente per flussi di dati complessi e paradigmi reattivi, ma ha una curva di apprendimento più alta.
TanStack Query fornisce una gestione completa del server state, con caching intelligente, invalidazione automatica e un’architettura pulita per React moderno.
Questo confronto evidenzia che la scelta dello strumento dipende dal livello di caos che si è disposti a gestire manualmente. Iniziare con Fetch può bastare per progetti piccoli, mentre per applicazioni scalabili e reattive è consigliabile adottare soluzioni più strutturate.
Conclusioni
Il vero problema non è come fai la chiamata, ma come governi il dato nel tempo. Se la tua applicazione è semplice, Fetch è sufficiente; se cresce in complessità, Axios ti semplifica la vita. Per interfacce altamente reattive, RxJS diventa una scelta strategica, mentre in ambienti React moderni TanStack Query con QueryClientProvider rappresenta l’opzione architetturalmente più solida.
La maturità di un progetto si misura nella capacità di scegliere lo strumento giusto non per effetto di moda, ma perché si comprende il livello di caos che si vuole evitare. Meno lavoro manuale di gestione delle richieste, meno notti insonni a domandarsi perché una chiamata si attiva tre volte senza motivo. Con la giusta strategia, le chiamate HTTP diventano un elemento trasparente e affidabile della tua architettura.