Sviluppo web

Progettare un'API: principi che reggono la crescita

I principi che reggono la crescita di un'API: risorse al posto di verbi, codici HTTP con un significato solo, errori leggibili da una macchina e contratto versionato prima del codice.

In sintesi

  • Un'API cresce meglio se le risorse sono sostantivi e i verbi stanno nei metodi HTTP.
  • Un codice di stato deve avere un solo significato: 200 per tutto rende impossibile distinguere un errore da un successo.
  • RFC 9457, luglio 2023, definisce un formato standard per gli errori: application/problem+json.
  • La paginazione a cursore resta stabile quando i dati cambiano; l'offset salta o duplica righe.

Progettare un'API che regge la crescita segue cinque principi: risorse al posto di verbi, codici HTTP con un significato solo, errori leggibili da una macchina, paginazione e idempotenza progettate prima, contratto versionato. Il resto sono dettagli che si possono cambiare senza rompere i client.

Risorse, non verbi

Un'API REST espone sostantivi: /orders, /orders/42, /orders/42/items. L'azione sta nel metodo HTTP, non nel percorso. POST /orders/42/cancel è accettabile quando la cancellazione non è una modifica di campo, ma GET /getOrders è un errore di progetto: GET deve essere sicuro e ripetibile, e un verbo nel percorso invita a usarlo per tutto. Il confronto con le server actions è in route handlers e server actions.

I codici HTTP non sono decorazione

RFC 9110, HTTP Semantics, giugno 2022, definisce il significato dei metodi e degli stati. Usa 201 per una risorsa creata, 204 per un successo senza corpo, 400 per una richiesta malformata, 401 per identità assente, 403 per identità presente senza permesso, 404 per risorsa inesistente, 409 per conflitto di stato, 422 per dati sintatticamente validi ma non accettabili, 429 per troppe richieste. Restituire 200 con {"error": "not found"} obbliga ogni client a leggere il corpo per capire cosa è successo, e rompe cache, retry e monitoraggio.

Errori che una macchina può leggere

RFC 9457, Problem Details for HTTP APIs, luglio 2023, definisce un corpo di errore standard con type, title, status, detail e instance, servito come application/problem+json. Un client può distinguere un errore di validazione da un errore temporaneo senza analizzare il testo del messaggio. Il campo type è un URI: metti lì la documentazione, non una frase per umani.

Paginazione: cursore, non offset

?offset=40&limit=20 sembra semplice finché un record viene inserito o cancellato: la pagina successiva salta una riga o la ripete. Un cursore opaco, per esempio ?after=eyJpZCI6NDJ9, indica un punto stabile nell'ordinamento. Restituisci sempre next_cursor e un has_more esplicito: il client non deve dedurre la fine dalla lunghezza della pagina.

Idempotenza e versioning

Una richiesta POST ripetuta dopo un timeout può creare due ordini. L'header Idempotency-Key, con una chiave generata dal client e conservata per almeno 24 ore, permette al server di restituire la prima risposta invece di eseguire di nuovo. Per il versioning, il numero nella URL (/v1/) è la scelta più leggibile e la più facile da monitorare; la versione nell'header funziona meglio se hai pochi client controllati. In entrambi i casi servono una data di deprecazione e un periodo di doppia esecuzione.

Il contratto prima del codice

OpenAPI 3.1.0, pubblicato il 15 febbraio 2021, descrive percorsi, schemi e risposte in JSON Schema. Scritto prima dell'implementazione, il contratto permette di generare client, documentazione e test di conformità dallo stesso file. Il comando sotto valida la specifica e verifica che l'endpoint risponda: due controlli che intercettano la maggior parte delle rotture.

npx @redocly/cli lint openapi.yaml
curl -s -o /dev/null -w "%{http_code}\n" https://api.example.com/v1/orders

Sicurezza e dati personali

Il regolamento (UE) 2016/679, del 27 aprile 2016, articolo 32, chiede misure adeguate al rischio: autenticazione forte, cifratura in transito, registri di accesso e limiti di frequenza. L'articolo 25 chiede che la minimizzazione sia nel progetto: un endpoint che restituisce l'intero record utente per mostrarne il nome è un problema di progettazione, non di configurazione. Sul lato applicativo, il controllo degli accessi va nel middleware e non nel client; il modello è descritto in middleware di autenticazione.

Dove questi principi non bastano

Un'API interna chiamata da un solo servizio può usare RPC e non guadagnare nulla da REST. Un flusso in tempo reale richiede WebSocket o Server-Sent Events, dove i codici HTTP non arrivano. E nessun principio risolve la latenza: se una risposta impiega tre secondi, la documentazione perfetta non salva l'esperienza.

Da dove partire

    Domande frequenti

    Meglio REST o GraphQL?

    Dipende dai client. REST è più semplice da mettere in cache e da monitorare; GraphQL conviene quando molti client chiedono forme di dati diverse e il costo di una query controllata è accettabile. Per un'API pubblica piccola, REST resta la scelta più prevedibile.

    Quando usare POST e quando PUT?

    PUT sostituisce una risorsa in un percorso noto ed è idempotente: ripeterlo produce lo stesso stato. POST crea una risorsa o avvia un'azione e non è idempotente, salvo l'uso di una chiave di idempotenza.

    Che differenza c'è tra 401 e 403?

    401 significa che manca o non è valida l'autenticazione: il client deve ottenere credenziali. 403 significa che l'identità è nota ma non ha il permesso: ripetere la richiesta non cambia nulla.

    Serve davvero versionare l'API?

    Sì, appena hai un client che non aggiorni insieme al server. Il numero nella URL è la forma più leggibile; la versione nell'header riduce il rumore ma richiede più disciplina nel monitoraggio.

    Come gestisco un errore di validazione?

    Rispondi 422 con un corpo application/problem+json e un elenco di campi non validi. Non restituire 200 con un messaggio: il client non può distinguere un successo da un rifiuto.