Vai al contenuto principale
Torna al blog
API Architettura Scalabilità

Progettare API scalabili: lezioni da 150+ progetti

Pattern reali per API scalabili: versionamento, paginazione, rate limiting, idempotenza e altro. Esperienza da 150+ progetti in produzione.

JM
Javier Manzano
CEO & Co-founder • 28 giugno 2026

Dopo più di 150 progetti con API in produzione, abbiamo identificato pattern che si ripetono continuamente. Non sono teorie accademiche né best practice di un tutorial. Sono lezioni apprese sul campo, a volte a spese di incidenti in produzione, a volte per refactoring dolorosi che avremmo potuto evitare con decisioni iniziali migliori.

Questo articolo raccoglie i principi di design API che abbiamo distillato in Soamee dopo anni a costruire sistemi che elaborano milioni di richieste giornaliere.

Principio 1: Progetta il contratto prima di scrivere codice

L’errore più comune che vediamo è iniziare a scrivere codice senza un contratto API definito. Il risultato: endpoint incoerenti, formati di risposta diversi a seconda di chi ha programmato ogni parte, e un’esperienza di integrazione caotica.

Seguiamo l’approccio API-first:

  1. Workshop di discovery con gli stakeholder: quali dati ha bisogno chi, con quale frequenza, quali operazioni sono necessarie
  2. Specifica OpenAPI 3.1 completa: endpoint, schemi request/response, codici di errore, esempi
  3. Revisione con i consumatori: il team frontend o gli integratori validano il contratto prima dello sviluppo
  4. Sviluppo contro la spec: il codice viene validato automaticamente rispetto alla specifica in CI

Questo approccio riduce del 40% il tempo di integrazione ed elimina i “non è quello che mi aspettavo” post-sviluppo.

Principio 2: La paginazione corretta fa la differenza

Abbiamo visto API in produzione con endpoint che restituiscono array di 50.000 elementi senza paginazione. Il server regge… finché non regge più.

Paginazione cursor-based vs offset-based

La paginazione offset-based (?page=5&limit=20) è semplice ma ha problemi gravi quando i dati cambiano tra le richieste: record duplicati o persi se si inseriscono o eliminano elementi.

La paginazione cursor-based (?after=cursor_abc&limit=20) risolve questi problemi ed è più efficiente in database di grandi dimensioni (non ha bisogno di contare le righe per calcolare l’offset):

{
  "data": [...],
  "pagination": {
    "has_next": true,
    "next_cursor": "eyJpZCI6MTIzNH0=",
    "has_prev": true,
    "prev_cursor": "eyJpZCI6MTIwMH0="
  }
}

Regola: definisci sempre un limite massimo

Anche con la paginazione, definisci sempre un max_limit. Se il client chiede ?limit=1000000, la tua API deve rispondere con il massimo permesso (es. 100) e un header indicando il troncamento.

Principio 3: Idempotenza o te ne pentirai

In sistemi distribuiti, le richieste possono essere ritentate (timeout di rete, fallimenti di proxy, retry del client). Se il tuo endpoint POST /payments non è idempotente, un retry può generare un addebito duplicato.

La soluzione: Idempotency Keys.

Il client invia un header Idempotency-Key: unique-request-id. Il server:

  1. Verifica se ha già elaborato quella chiave
  2. Se sì: restituisce la risposta originale memorizzata
  3. Se no: elabora la richiesta e memorizza la risposta insieme alla chiave

Stripe ha reso popolare questo pattern, e lo applichiamo a tutti gli endpoint che hanno side effect (creazione, aggiornamento, eliminazione). Le chiavi di idempotenza scadono dopo 24-48 ore.

Principio 4: Rate limiting multi-dimensionale

Il rate limiting non è solo “100 richieste al minuto”. In API reali hai bisogno di più dimensioni:

  • Per utente/API key: limite globale del consumatore
  • Per endpoint: endpoint costosi (report, export) con limiti più bassi
  • Per tier: Free (100 req/min), Pro (1000 req/min), Enterprise (10000 req/min)
  • Per IP: protezione contro abusi anche senza autenticazione

Usiamo l’algoritmo token bucket con finestre scorrevoli implementato in Redis. Gli header di risposta includono sempre:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1719302400
Retry-After: 30

Il Retry-After appare solo quando il limite viene superato (HTTP 429). Questo permette ai client di implementare il backoff automatico senza dover indovinare quanto aspettare.

Principio 5: Errori che aiutano, non che frustrano

Un errore generico {"error": "Bad request"} è inutile per un integratore. Il nostro formato di errore standard:

{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "The request body contains validation errors",
    "details": [
      {
        "field": "email",
        "code": "INVALID_FORMAT",
        "message": "Must be a valid email address",
        "value": "not-an-email"
      },
      {
        "field": "amount",
        "code": "OUT_OF_RANGE",
        "message": "Must be between 1 and 1000000",
        "value": 0
      }
    ],
    "request_id": "req_abc123def456",
    "documentation_url": "https://docs.api.com/errors/VALIDATION_FAILED"
  }
}

Ogni errore include:

  • code: Codice parseable dalla macchina
  • message: Descrizione leggibile per il debugging
  • details: Array con errori di campo specifici
  • request_id: ID univoco per correlare con i log del server
  • documentation_url: Link diretto alla documentazione dell’errore

Principio 6: Evolvi senza rompere

Il versionamento è inevitabile. La tua API cambierà. La domanda è come gestirlo senza rompere le integrazioni esistenti.

La nostra strategia di evoluzione:

  1. Modifiche additive senza nuova versione: Aggiungere campi opzionali, nuovi endpoint, nuovi valori in enum. Questi non rompono mai i client esistenti.

  2. Breaking changes = nuova versione major: Eliminare campi, cambiare tipi, rinominare endpoint, modificare comportamenti. Solo in /v2/, /v3/…

  3. Periodo di deprecazione di 12 mesi: Quando una versione viene deprecata, i consumatori hanno 12 mesi per migrare. Gli header Deprecation e Sunset comunicano le date.

  4. Changelog automatico: Ogni deploy genera un changelog che descrive cosa è cambiato, con link alla documentazione di migrazione.

  5. Feature flags, non versioni: Per funzionalità opzionali, usiamo header di opt-in invece di creare nuove versioni.

Principio 7: Progetta per l’osservabilità

Ogni richiesta genera un request_id univoco che viaggia attraverso tutto il sistema (distributed tracing). Questo permette di:

  • Correlare un errore segnalato dal client con i log del server
  • Seguire una richiesta attraverso più microservizi
  • Misurare la latenza di ogni componente nella catena

Inoltre, strumentiamo ogni endpoint con metriche:

  • Latenza (p50, p95, p99)
  • Tasso di errore per codice di stato
  • Richieste al secondo
  • Dimensione della risposta

Principio 8: Proteggiti da te stesso

Le API più pericolose non sono quelle attaccate da terzi, ma quelle che un integratore usa in modo errato (o che il tuo stesso frontend abusa senza rendersene conto).

Protezioni che implementiamo sempre:

  • Timeout rigorosi: ogni operazione ha un timeout. Se il database non risponde in 5s, restituiamo 503 invece di bloccare il thread indefinitamente.
  • Circuit breakers: se un servizio esterno fallisce, smettiamo temporaneamente di chiamarlo invece di accumulare timeout.
  • Limiti dimensione richiesta: massimo 10MB per richiesta. Gli upload grandi vanno tramite signed URL direttamente a S3.
  • Limiti complessità query: in GraphQL, query con profondità > 5 o costo > 1000 vengono rifiutate automaticamente.
  • Paginazione obbligatoria: gli endpoint di lista non hanno opzione di restituire tutto. Sempre paginati.

Principio 9: I webhook come cittadini di prima classe

In API moderne, i webhook sono importanti quanto gli endpoint. Gli integratori non dovrebbero fare polling per sapere se qualcosa è cambiato.

Il nostro design dei webhook include:

  • Retry automatico con exponential backoff (fino a 72 ore di tentativi)
  • Firma HMAC in ogni payload perché il ricevitore verifichi l’autenticità
  • Idempotenza: il ricevitore può ricevere lo stesso evento più volte. Includiamo un event_id univoco.
  • Log di consegna: dashboard dove l’integratore vede la cronologia delle consegne, fallimenti e tentativi
  • Modalità test: endpoint per simulare l’invio di eventi senza azioni reali

Principio 10: La documentazione È l’API

Un’API senza documentazione non esiste. Ma la documentazione che diventa obsoleta è peggio di nessuna documentazione, perché genera falsa fiducia.

Il nostro stack di documentazione:

  • OpenAPI 3.1 come source of truth: tutto si genera da qui
  • Redoc o Swagger UI: documentazione interattiva auto-generata
  • Postman collection: export automatico per testing manuale
  • SDK auto-generati: TypeScript, Python, Go dalla spec OpenAPI
  • Guide di avvio rapido: in meno di 5 minuti, l’integratore fa la sua prima chiamata
  • Changelog vivo: si aggiorna automaticamente ad ogni deploy

La documentazione viene validata in CI: se il codice non coincide con la spec OpenAPI, il deploy fallisce. Questo garantisce che la documentazione sia sempre aggiornata.

Conclusione: le API sono contratti, non endpoint

Progettare API che scalano non è esclusivamente un problema tecnico. È un problema di product design: la tua API è un prodotto che altri sviluppatori consumano. L’esperienza di integrazione determina se quegli sviluppatori (interni o esterni) sono produttivi o si frustrano.

I 10 principi che abbiamo descritto non sono opzionali per API serie. Sono la baseline che ogni team dovrebbe implementare prima di esporre un endpoint al mondo. La differenza tra un’API mediocre e un’API eccellente non sta nella tecnologia sottostante, ma nell’attenzione ai dettagli nel design del contratto, nella gestione degli errori, nella documentazione e nell’operatività in produzione.

Se vuoi che riviediamo l’architettura della tua API attuale o ne progettiamo una da zero con questi principi, offriamo consulenza tecnica gratuita dove analizziamo il tuo caso e proponiamo miglioramenti concreti.

Non perderti nulla

JM

Javier Manzano

CEO & Co-founder in Soamee

Appassionato di tecnologia e sviluppo software. Condividendo conoscenze e esperienze per aiutare altri sviluppatori a crescere.

Ti è piaciuto questo articolo?

Se hai bisogno di aiuto con il tuo progetto di sviluppo, siamo qui per te.

Prenota una call gratuita →