API REST: come progettarle bene senza complicarsi la vita 🔌

API REST: come progettarle

Quando due applicazioni devono comunicare tra loro, molto spesso entrano in gioco le API. Le utilizziamo continuamente, anche senza rendercene conto: quando un e-commerce recupera le disponibilità di magazzino, quando un’app mostra informazioni provenienti da un servizio esterno o quando una piattaforma SaaS permette ai propri clienti di integrare funzionalità nei loro sistemi.

Il problema è che creare un’API che funziona non significa necessariamente progettare un’API fatta bene.

Un endpoint che restituisce un JSON può essere scritto in pochi minuti. Costruire un’interfaccia coerente, sicura, documentata e capace di evolvere senza rompere le integrazioni esistenti richiede invece qualche attenzione in più.

Vediamo quindi come progettare API REST in modo pratico, evitando complicazioni inutili e alcuni errori che diventano particolarmente fastidiosi quando il progetto cresce.

Che cos’è un’API REST?

Un’API, acronimo di Application Programming Interface, permette a sistemi software differenti di scambiarsi informazioni e richiedere operazioni attraverso un’interfaccia definita.

REST, invece, è uno stile architetturale basato su principi come la separazione tra client e server, l’assenza di stato applicativo della sessione nelle singole richieste e l’utilizzo di un’interfaccia uniforme per interagire con le risorse.

Nella pratica, quando parliamo di API REST sul web, ci riferiamo spesso a servizi che utilizzano HTTP, URL per identificare risorse e JSON per rappresentare i dati.

Un esempio molto semplice:

GET /api/products/42

Il client richiede le informazioni del prodotto identificato dal numero 42. Il server potrebbe rispondere così:

{
  "id": 42,
  "name": "Monitor 27 pollici",
  "price": 189.90,
  "available": true
}

È un esempio volutamente minimale, ma mostra il concetto fondamentale: il client conosce l’interfaccia da utilizzare senza dover conoscere il funzionamento interno del server.

1. Progetta gli endpoint intorno alle risorse

Uno degli errori più comuni consiste nel costruire endpoint che descrivono direttamente le azioni anziché le risorse.

Per esempio:

GET /api/getAllProducts
POST /api/createNewProduct
POST /api/deleteProduct

Non è impossibile utilizzare un’API costruita così, ma nel tempo rischia di diventare poco coerente.

Una struttura orientata alle risorse è generalmente più chiara:

GET    /api/products
GET    /api/products/42
POST   /api/products
PATCH  /api/products/42
DELETE /api/products/42

In questo caso, il percorso identifica la risorsa e il metodo HTTP comunica il tipo di operazione.

Il vantaggio non è soltanto estetico. Quando un nuovo sviluppatore deve integrare l’API, può comprendere più facilmente la logica degli endpoint e prevedere il comportamento delle operazioni.

2. Utilizza correttamente i metodi HTTP

HTTP mette a disposizione diversi metodi, ognuno con una semantica precisa.

  • GET: recupera una rappresentazione della risorsa senza richiedere modifiche al suo stato.
  • POST: invia dati per l’elaborazione, spesso per creare una nuova risorsa.
  • PUT: crea o sostituisce la rappresentazione della risorsa identificata, secondo il contratto dell’API.
  • PATCH: applica modifiche parziali a una risorsa.
  • DELETE: richiede l’eliminazione di una risorsa.

Attenzione però a un dettaglio importante: PUT e PATCH non sono semplicemente due modi intercambiabili per aggiornare qualcosa.

PUT è definito come metodo idempotente: ripetere la stessa richiesta deve avere lo stesso effetto previsto sullo stato della risorsa. PATCH, invece, non è necessariamente idempotente: dipende dal tipo di modifica richiesta.

Per approfondire puoi consultare la documentazione MDN sui metodi HTTP.

3. Restituisci codici di stato HTTP coerenti

Un altro errore frequente è rispondere sempre con HTTP 200, anche quando qualcosa è andato storto.

Immagina di ricevere questa risposta:

HTTP/1.1 200 OK

{
  "success": false,
  "error": "Product not found"
}

Il corpo della risposta comunica un errore, ma il codice HTTP indica che la richiesta è stata gestita con successo. Questo comportamento rende più difficile lavorare con client, strumenti di monitoraggio e integrazioni automatiche.

È preferibile utilizzare codici coerenti con il risultato:

  • 200 OK: richiesta completata correttamente.
  • 201 Created: nuova risorsa creata.
  • 204 No Content: operazione completata senza corpo di risposta.
  • 400 Bad Request: richiesta non valida.
  • 401 Unauthorized: autenticazione mancante o non valida.
  • 403 Forbidden: operazione non consentita.
  • 404 Not Found: risorsa non trovata.
  • 409 Conflict: richiesta in conflitto con lo stato della risorsa.
  • 422 Unprocessable Content: contenuto formalmente comprensibile ma non elaborabile secondo le regole applicabili.
  • 429 Too Many Requests: limite di richieste superato.
  • 500 Internal Server Error: errore interno inatteso.

Non è necessario utilizzare ogni codice possibile. È molto più importante scegliere quelli appropriati e mantenere un comportamento prevedibile.

4. Gli errori devono essere utili, non misteriosi

Una risposta come “Something went wrong” è frustrante tanto per l’utente quanto per lo sviluppatore che deve capire il problema.

Un errore ben strutturato dovrebbe comunicare almeno:

  • che tipo di problema si è verificato;
  • quale richiesta o campo ha causato l’errore, quando pertinente;
  • se il client può correggere la richiesta;
  • un eventuale identificativo per rintracciare l’evento nei log.

Per esempio:

{
  "type": "https://api.example.com/problems/validation",
  "title": "Validation failed",
  "status": 422,
  "detail": "Il campo email non contiene un indirizzo valido.",
  "request_id": "req_8f3a21"
}

Un riferimento particolarmente utile è RFC 9457, che definisce un formato standard per rappresentare i problemi nelle API HTTP.

Naturalmente, i messaggi di errore non devono esporre stack trace, password, query SQL complete o altri dettagli interni che potrebbero facilitare un attacco.

5. La paginazione non è un optional

Un endpoint che restituisce dieci prodotti funziona benissimo. Ma che cosa succede quando i prodotti diventano centomila?

Restituire tutto in una sola risposta significa aumentare il consumo di memoria, il traffico di rete e il tempo necessario per elaborare i risultati.

Una soluzione semplice è la paginazione:

GET /api/products?page=2&limit=20

Il server restituisce soltanto una porzione dei risultati:

{
  "data": [
    {
      "id": 21,
      "name": "Prodotto A"
    }
  ],
  "pagination": {
    "page": 2,
    "limit": 20,
    "total": 125
  }
}

Questo approccio è facile da comprendere e può essere sufficiente per molti progetti.

Quando però i dati cambiano frequentemente o gli insiemi sono molto grandi, la paginazione basata su cursori può essere più adatta rispetto a quella basata su offset.

Non esiste una soluzione perfetta per ogni caso: la scelta dipende dalla quantità di dati, dall’ordinamento e dal tipo di consultazione previsto.

6. Filtri e ordinamenti devono essere prevedibili

Un’API che gestisce elenchi dovrebbe permettere al client di recuperare soltanto i dati necessari.

Per esempio:

GET /api/products?category=monitor&sort=price&order=asc

È importante però non trasformare i parametri ricevuti direttamente in parti di una query SQL.

I valori devono essere validati e, quando vengono utilizzati in query al database, gestiti attraverso prepared statement e regole appropriate. I nomi delle colonne utilizzabili per l’ordinamento, per esempio, dovrebbero provenire da una lista consentita.

La flessibilità di un’API non deve diventare una scorciatoia per introdurre vulnerabilità.

7. Sicurezza: non basta proteggere l’endpoint con un token

Una delle convinzioni più pericolose è che un’API sia sicura soltanto perché richiede un token di autenticazione.

Immagina un endpoint:

GET /api/invoices/152

Un utente autenticato richiede la fattura 152. Il server deve verificare non soltanto che il token sia valido, ma anche che quell’utente abbia il diritto di accedere proprio a quella fattura.

Se basta modificare il numero nell’URL per leggere documenti appartenenti ad altri utenti, il sistema presenta un problema di autorizzazione sugli oggetti.

È uno dei rischi affrontati nella OWASP API Security Top 10.

Oltre all’autorizzazione, verifica anche:

  • validazione degli input;
  • protezione delle credenziali;
  • limiti di richiesta e consumo delle risorse;
  • configurazione corretta di CORS;
  • gestione sicura dei file caricati;
  • registrazione degli eventi rilevanti;
  • protezione degli endpoint amministrativi.

🔐 La sicurezza deve essere parte del contratto dell’API, non una funzione aggiunta quando il progetto è già online.

8. Versioning: come evolvere senza rompere tutto

Prima o poi arriva il momento di modificare un’API. Un campo viene rinominato, una risposta cambia struttura o una funzionalità viene sostituita.

Il problema è che i client esistenti potrebbero dipendere dal comportamento precedente.

Una strategia semplice consiste nell’utilizzare versioni esplicite negli URL:

/api/v1/products
/api/v2/products

Non è l’unico approccio possibile: esistono anche strategie basate su header e media type. L’importante è definire una politica chiara per le modifiche incompatibili.

Non ogni cambiamento richiede una nuova versione. Aggiungere un campo opzionale, per esempio, può essere compatibile con molti client, ma non è una garanzia assoluta: alcuni consumatori potrebbero utilizzare validazioni particolarmente rigide.

Prima di introdurre modifiche importanti, documenta il cambiamento, verifica le integrazioni esistenti e prevedi un periodo di transizione ragionevole.

9. Documentazione: OpenAPI può farti risparmiare molto tempo

Un’API senza documentazione costringe chi la utilizza a indovinare parametri, risposte ed errori.

La specifica OpenAPI permette di descrivere in modo strutturato endpoint, parametri, schemi dei dati, autenticazione e risposte.

Da questa descrizione è possibile generare documentazione interattiva, supportare strumenti di test e, in alcuni casi, produrre client per diversi linguaggi.

Una documentazione davvero utile dovrebbe includere anche esempi realistici, codici di errore e indicazioni su limiti di utilizzo e compatibilità.

10. Una checklist prima di pubblicare un’API

  • ☐ Gli endpoint utilizzano nomi coerenti?
  • ☐ I metodi HTTP rispettano la semantica prevista?
  • ☐ I codici di stato descrivono correttamente il risultato?
  • ☐ Gli errori sono strutturati e comprensibili?
  • ☐ Gli input vengono validati?
  • ☐ Ogni operazione verifica i permessi sulla risorsa interessata?
  • ☐ Gli elenchi supportano limiti e paginazione?
  • ☐ Le richieste costose hanno protezioni adeguate?
  • ☐ Le modifiche incompatibili vengono gestite con una strategia di evoluzione?
  • ☐ Esistono documentazione e test per i principali casi d’uso?

Conclusione: una buona API deve essere prevedibile

Progettare API REST non significa inseguire la struttura più sofisticata o utilizzare ogni possibile funzionalità di HTTP.

Significa costruire un’interfaccia che gli altri sviluppatori possano comprendere facilmente, integrare senza sorprese e mantenere nel tempo.

Endpoint coerenti, risposte chiare, controlli di sicurezza e documentazione adeguata fanno una differenza enorme, soprattutto quando l’API diventa il punto di collegamento tra più applicazioni.

Il codice interno può cambiare anche radicalmente. Una buona API, invece, dovrebbe permettere ai suoi utilizzatori di continuare a lavorare senza dover rincorrere ogni modifica. 🚀

Vuoi aggiungere qualcosa?

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *