Il livello API moderno: GraphQL, REST e tRPC
Il dibattito è finito. Hai bisogno di Type Safety. Come progettare un livello API scalabile che non rompa il frontend quando il backend starnutisce.
Perché Maison Code ne parla
In Maison Code Paris, agiamo come la coscienza architettonica dei nostri clienti. Spesso ereditiamo stack “moderni” costruiti senza una comprensione fondamentale della scala.
Discutiamo di questo argomento perché rappresenta un punto di svolta critico nella maturità ingegneristica. Implementarlo correttamente differenzia un MVP fragile da una piattaforma resiliente di livello aziendale.
Il problema “Non definito non è una funzione”.
Per 20 anni, gli sviluppatori frontend e backend hanno vissuto in silos.
Sviluppatore backend: “Ho aggiornato l’API utente. Restituisce fullName invece di first_name.”
Sviluppatore front-end: “Okay.” (Dimentica di aggiornare il codice).
Produzione: Crash. “user.first_name” non è definito.
Questo è chiamato Gap di integrazione.
La documentazione (Swagger/OpenAPI) aiuta, ma la documentazione mente. Diventa obsoleto.
La soluzione moderna è la Sicurezza di tipo end-to-end.
Il codice stesso impedisce questi errori di mancata corrispondenza.
Perché Maison Code parla dell’architettura API
Noi di Maison Code ereditiamo progetti in cui il livello API è un caos di endpoint spaghetti.
/api/v1/get-user-final-final-2.
Ciò rallenta la velocità delle funzionalità fino a raggiungere una scansione per indicizzazione.
Implementiamo Architetture type-safe.
Abilitiamo l’Autonomia Frontend. Il team frontend non dovrebbe dover disturbare il team backend per aggiungere un campo a una risposta JSON.
Ne scriviamo perché i team scalabili funzionano con contratti scalabili.
Se il tuo schema è allentato, il tuo prodotto è allentato.
1. I contendenti
1. REST (con OpenAPI)
Il classico.
- Pro: semplice, memorizzabile nella cache (HTTP 200), universale.
- Contro: recupero eccessivo (recupero di troppi dati) e recupero insufficiente (n+1 richieste).
- Modern Twist: utilizza OpenAPI (Swagger) per generare automaticamente tipi TypeScript.
npx openapi-typescript schema.json -o schema.d.ts. Ora, se il backend cambia, la compilazione del frontend fallisce.
2. GraphQL
L’utente esperto.
- Pro: il cliente chiede esattamente ciò di cui ha bisogno.
query { utente { nome, avatar (dimensione: PICCOLO) } } - Contro: caching complesso (tutto è POST 200), complessità (resolver, dataloader).
- Ideale per: app complesse, ricche di grafici (social network, dashboard) e integrazioni CMS headless (Shopify, Contentful).
3. tRPC (Chiamata di procedura remota TypeScript)
Il demone della velocità.
-
Contesto: se possiedi sia il frontend che il backend (ad esempio, Next.js Monorepo).
-
Magia: importi la funzione backend direttamente nel codice frontend. “dattiloscritto”. // Backend esporta const appRouter = router({ getUser: publicProcedure.query(() => { id: 1, nome: ‘Alex’ }) });
// Frontend const utente = trpc.getUser.useQuery(); console.log(utente.dati.nome); // Digitato!
-
Non esiste uno schema API. Il codice è lo schema.
-
Contro: bloccato nei monorepos TypeScript.
2. Modello: The BFF (Backend per Frontend)
Nelle architetture Microservice, non vuoi che il frontend chiami 10 servizi distinti (Servizio utente, Servizio carrello, Servizio prodotto). È lento (10 viaggi di andata e ritorno) e disordinato. Soluzione: un livello BFF (solitamente percorsi API GraphQL o Next.js). Il frontend chiama la migliore amica una volta. Il migliore amico chiama i 10 servizi, aggrega i dati, rimuove i segreti e restituisce un JSON pulito. Ciò consente al frontend di essere “stupido” e al backend di essere “complesso” senza compromettere le prestazioni.
3. Convalida degli input: Zod
Non fidarsi mai del cliente. Sia che utilizzi REST o GraphQL, devi convalidare gli input. Zod è lo standard.
“dattiloscritto”. importa { z } da ‘zod’;
const SchemaUtente = z.oggetto({ email: z.string().email(), età: z.number().min(18) });
app.post(‘/utente’, (req, res) => { risultato const = UserSchema.safeParse(req.body); if (!risultato.successo) { restituisce res.status(400).json(risultato.errore); } // È sicuro procedere });
Questo schema Zod può essere condiviso con il frontend per generare automaticamente la logica di convalida del modulo (usando `react-hook-form`).
## 4. Lo strato della Federazione (Apollo)
Per le app aziendali, un server GraphQL non è sufficiente.
Hai il "Product Team" a Berlino e il "Checkout Team" a New York.
Non possono condividere una codebase.
**Soluzione**: **Federazione GraphQL**.
Ogni squadra costruisce il proprio sottografo.
Un "Gateway" li unisce in un "Supergrafo".
Il Frontend interroga il Gateway. Sembra un'API, ma è alimentata da 50 microservizi.
Questa è l’architettura di Netflix e Airbnb.
## 5. Strategie di gestione degli errori
"200 OK" ma "errori: ["Non trovato"]". Questa è la trappola di GraphQL.
**Strategia**:
1. **Errori di rete**: (DNS, 500 s). Riprovare con backoff esponenziale.
2. **Errori logici**: (Utente non trovato). Restituisce un tipo nullable o un tipo di unione.
```graphql
unione UserResult = Utente | UtenteNonTrovato | Autorizzazione negata
```
Ciò impone al frontend di gestire esplicitamente il caso di errore nel codice.
`if (result.__typename === 'UserNotFound') ...`
Gestione degli errori indipendente dai tipi.
## 6. Strategie di memorizzazione nella cache (Stale-While-Revalidate)
REST memorizza facilmente nella cache tramite intestazioni HTTP (`Cache-Control: max-age=3600`).
GraphQL è più difficile.
Utilizziamo **Stale-While-Revalidate (SWR)** sul client (TanStack Query).
1. Mostra immediatamente i dati memorizzati nella cache (Stale).
2. Recupera nuovi dati in background (Riconvalida).
3. Aggiorna l'interfaccia utente se modificata.
Ciò fa sì che l'app sembri "istantanea", anche se la rete è lenta.
## 7. Il livello di sicurezza (limitazione della velocità e JWT)
Un'API senza sicurezza è una porta aperta.
**Limitazione della velocità**: previene gli attacchi DDoS.
* Utilizza `upstash/ratelimit` (Redis) su Edge.
* "10 richieste ogni 10 secondi per IP".
**Autenticazione**:
* Smetti di usare i cookie per le API. Utilizza **JWT (JSON Web Tokens)** nell'intestazione "Autorizzazione: Portatore".
*Apolide. Scalabile.
* Assicurati però di gestire la rotazione dei token (Aggiorna token) in modo sicuro.
**Convalida**:
* Disinfetta gli input da SQL Injection (usa ORM).
* Disinfetta le uscite contro XSS.
## 8. Integrazione della migrazione legacy (The Strangler Fig)
Hai un'API legacy monolitica (Java/PHP). Desideri una moderna API Node.js.
Non riscriverlo tutto in una volta. Fallirai.
Usa il **Schema del fico strangolatore**.
1. Metti un Proxy (Nginx / Cloudflare) davanti.
2. Instradare `/api/v1/users` alla nuova API.
3. Instrada tutto il resto all'API legacy.
4. Migrare lentamente gli endpoint uno per uno.
5. Disattiva l'API legacy quando il traffico scende a zero.
Ciò ti consente di spedire immediatamente valore senza una riscrittura "Big Bang".
## 9. Documenti come codice (Stripe Standard)
In che modo Stripe mantiene i propri documenti così affidabili?
Li generano dal codice.
Non scrivere documenti API in Word o Confluence.
Scrivili nello **Schema**.
* **OpenAPI**: aggiungi i campi `descrizione` al tuo YAML.
* **GraphQL**: aggiungi `""" docstrings """` al tuo schema.
* **Strumenti**: usa Scalar o Redoc per eseguire il rendering di bellissimi documenti HTML dallo schema.
* **CI/CD**: distribuisci i documenti automaticamente a ogni unione.
I documenti obsoleti sono peggio di nessun documento.
## 10. Il punto di vista dello scettico
"GraphQL è morto. Basta usare fetch."
**Contropunto**:
GraphQL è vivo e vegeto nell'Enterprise.
Shopify, GitHub e Facebook funzionano su di esso.
"Just recupero" funziona per i blog.
Non funziona per una pagina di prodotto e-commerce che richiede lo stato di prezzo, varianti, inventario, recensioni, consigli e lista dei desideri dell'utente in un'unica richiesta.
Se lo fai con REST, hai 6 richieste (lente) o un mostruoso endpoint `/get-product-page-data` (non gestibile).
GraphQL risolve il problema dell'**orchestrazione**.
## 11. Traffico interno: buffer di protocollo (gRPC)
JSON è leggibile dall'uomo. È anche lento.
Ripete le chiavi: `{"name": "alex", "age": 10}`. "nome" viene inviato via cavo ogni volta.
**gRPC** utilizza **Protobuf** (binario).
Invia `0x12 0x04 0x61 0x6c 0x65 0x78`.
È più piccolo del 30% e 5 volte più veloce da analizzare.
Utilizziamo gRPC per la comunicazione interna da servizio a servizio (microservizi).
Utilizziamo GraphQL/JSON per la comunicazione da client a server.
Lo strumento giusto per il lavoro giusto.
## 12. Il problema N+1 (caricatori di dati)
Il classico killer GraphQL.
Query: `utenti { post { titolo } }`.
* 1 query per gli utenti (SELECT * FROM utenti).
* 100 query per i post (SELECT * FROM post WHERE user_id = 1... 100).
* Totale: 101 chiamate DB.
**Soluzione**: **DataLoader**.
Raggruppa le 100 richieste in **ONE**.
`SELECT * FROM post DOVE user_id IN (1, 2, ... 100)`.
Ciò riduce il carico del DB del 99%.
Se non utilizzi DataLoaders, il tuo server GraphQL si scioglierà.
## 13. Conclusione
Il livello API è il sistema nervoso della tua applicazione.
Se è debole (non tipizzato, non testato), il corpo fallisce.
Se è forte (tRPC, GraphQL Federation), il corpo si muove con agilità.
Smetti di indovinare correttamente i nomi. Inizia a farli rispettare.
Il contratto innanzitutto. Codice Secondo.
<hr style="margin: 1rem 0" />
### L'API interrompe il frontend?
Progettiamo livelli API Type-Safe utilizzando GraphQL e tRPC per garantire l'assenza di bug di integrazione.
**[Correggi la mia API](/services/tech)**.
**[Assumi i nostri architetti](/contact)**.