Quando usare POST invece di PUT per creare una risorsa?
Usa POST quando è il server ad assegnare l'identificatore della risorsa e PUT quando lo controlla il client. POST /orders lascia che il server generi l'ID del nuovo ordine e lo restituisca in una risposta 201 Created con un header Location; è il caso comune per le collezioni. PUT /orders/{id} crea o sostituisce integralmente la risorsa a un URI scelto dal client ed è idempotente, quindi sicuro da ritentare, utile quando il client può derivare l'ID in modo deterministico. Se usi POST per creare, proteggiti dai doppi invii con una chiave di idempotenza, dato che POST non è idempotente.
Come versionare un'API senza rompere i client esistenti?
Scegli uno schema di versioning e applicalo con coerenza: il versioning su URI (/v1/orders) è il più visibile e cache-friendly, mentre il versioning via header o media type (Accept: application/vnd.example.v2+json) mantiene stabili gli URL. Dentro una versione major fai solo modifiche retrocompatibili - aggiungi campi opzionali e nuovi endpoint, non rimuovere o rinominare campi né irrigidire la validazione. Riserva le modifiche breaking a una nuova versione major, annuncia le deprecazioni con gli header Deprecation e Sunset e una guida di migrazione, e tieni in vita la vecchia versione per una finestra dichiarata. L'obiettivo è che un client scritto per v1 continui a funzionare finché non ritiri deliberatamente v1.
Quale status code restituire per un errore di validazione?
Restituisci 400 Bad Request per una richiesta malformata o semanticamente non valida che il client deve correggere, e 422 Unprocessable Content se vuoi distinguere una sintassi ben formata che fallisce la validazione di business. Usa 401 Unauthorized quando l'autenticazione manca o non è valida, 403 Forbidden quando il chiamante autenticato non ha i permessi, 404 Not Found per una risorsa mancante e 409 Conflict per un conflitto di stato come un duplicato o un mismatch di versione. Accompagna lo status con un body di errore leggibile dalla macchina - idealmente RFC 9457 Problem Details - così i client possono reagire programmaticamente. Non restituire mai 200 OK con un errore nel body, perché vanifica ogni client e proxy generico.
Cos'è l'idempotenza e perché conta per i retry?
Un'operazione è idempotente se eseguirla molte volte ha lo stesso effetto di eseguirla una volta, così un client può ritentare in sicurezza dopo un timeout di rete senza temere effetti collaterali duplicati. GET, PUT e DELETE sono idempotenti per definizione; POST no. Poiché le reti perdono le risposte, ogni operazione non safe che un client potrebbe ritentare - creare un pagamento, inviare un messaggio - dovrebbe accettare una chiave di idempotenza (es. un UUID in un header Idempotency-Key) che il server memorizza così che una richiesta ripetuta restituisca il risultato originale invece di agire due volte. Progettare per i retry è la differenza tra un'API che sopravvive alla fragilità del mondo reale e una che addebita due volte ai clienti.
Meglio paginazione a offset o a cursore?
Usa la paginazione a cursore (keyset) per dataset grandi o in frequente cambiamento e quella a offset solo per liste piccole e stabili dove conta saltare a una pagina arbitraria. La paginazione a offset (?limit=20&offset=100) è semplice ma degrada su offset grandi e può saltare o duplicare righe quando elementi vengono inseriti o eliminati tra le richieste di pagina. La paginazione a cursore restituisce un cursore opaco che punta subito dopo l'ultimo elemento, così la pagina successiva cerca per chiave di ordinamento stabile e resta corretta e veloce a prescindere dagli inserimenti. Qualunque tu scelga, documentala, limita la dimensione massima di pagina e restituisci il cursore o i link in una posizione coerente.
Come progettare le risposte di errore?
Standardizza su RFC 9457 Problem Details (application/problem+json) così ogni errore ha la stessa forma: type (un URI che identifica la classe di errore), title, status, detail e instance, più eventuali campi di estensione specifici del dominio. Usa lo status code HTTP corretto come segnale primario e il body per le specifiche leggibili da umani e macchine, come quali campi hanno fallito la validazione. Mantieni i messaggi sicuri - non far trapelare mai stack trace, SQL o identificatori interni - e mantieni stabili gli URI di type così i client possono diramare su di essi. Un contratto di errore coerente permette a un client di gestire i fallimenti di molti endpoint con un solo percorso di codice.
Come mettere in sicurezza un endpoint di API?
Servi tutto su TLS, autentica ogni richiesta e autorizza ogni azione separatamente. L'autenticazione stabilisce chi è il chiamante - comunemente un access token OAuth 2.0 o un JWT firmato passato come bearer token - e devi validare la firma del token, la scadenza (exp), l'issuer (iss) e l'audience (aud) a ogni richiesta. L'autorizzazione verifica poi se quel chiamante può compiere quella specifica operazione su quella specifica risorsa, applicata lato server e mai data per buona dal client. Aggiungi rate limiting, valida e limita tutto l'input al confine ed evita di far trapelare dati sensibili nei messaggi di errore.
Cos'è HATEOAS e mi serve davvero?
HATEOAS è il vincolo REST per cui le risposte includono link alle azioni valide successive, così i client navigano l'API seguendo i link invece di costruire URL da conoscenza esterna. In teoria consente al server di cambiare la struttura degli URL senza rompere i client e rende le API auto-descrittive. Nella pratica è il vincolo REST meno adottato perché la maggior parte dei client è scritta su documentazione fissa e la macchineria dei link aggiunge costo. Adottalo quando hai molti client a lunga vita che non puoi coordinare, o un workflow con transizioni dipendenti dallo stato; saltalo per un'API interna consumata da un SDK generato.
Come implementare il rate limiting e comunicarlo ai client?
Scegli un algoritmo (token bucket e sliding window sono comuni), imposta i limiti per client o API key e rifiuta il traffico in eccesso con 429 Too Many Requests. Indica ai client la loro situazione restituendo gli header RateLimit-Limit, RateLimit-Remaining e RateLimit-Reset sulle risposte normali, e includi un header Retry-After sul 429 così un client ben educato sa esattamente quando riprovare. Documenta i limiti e fornisci un percorso per richiedere quote più alte. L'obiettivo è accesso equo e protezione dal sovraccarico, rendendo i limiti abbastanza prevedibili da consentire ai client di auto-limitarsi invece di essere colti di sorpresa.
REST, GraphQL o gRPC: come scegliere?
Per default scegli REST su JSON per API pubbliche, rivolte al browser e ampiamente consumate, perché è cacheable, debuggabile con strumenti standard e universalmente compreso. Scegli GraphQL quando i client devono recuperare selezioni di campi annidate e specificate dal client in un solo round trip e puoi permetterti il costo lato server (pianificazione delle query, limitazione della profondità, rinuncia al caching HTTP). Scegli gRPC per chiamate interne service-to-service a bassa latenza e alto throughput dove un contratto .proto tipizzato e lo streaming HTTP/2 ripagano e la portata browser non è una preoccupazione. Gli stili non si escludono a vicenda: molti sistemi espongono REST all'esterno e gRPC all'interno.
Come progettare un buon URL di API e una gerarchia di risorse?
Modella le risorse come sostantivi plurali e usa i metodi HTTP come verbi: GET /orders, POST /orders, GET /orders/{id}, DELETE /orders/{id}. Esprimi il contenimento con path annidati (/orders/{id}/items) ma evita di annidare più di uno o due livelli, perché le gerarchie profonde diventano fragili; riferisci le altre risorse per ID. Mantieni i nomi coerenti (una sola convenzione di casing, collezioni plurali), evita i verbi nei path e metti filtraggio, ordinamento e paginazione nella query string invece che nel path. URL prevedibili e basati su sostantivi rendono un'API scopribile e permettono ai client di indovinare correttamente.
Cosa va nella query string e cosa nel path?
Il path identifica una risorsa o collezione specifica; la query string affina una richiesta di collezione. Metti l'identità nel path (/orders/{id}) e metti filtraggio, ordinamento, selezione dei campi e paginazione nella query string (/orders?status=open&sort=-createdAt&limit=20). I parametri di query dovrebbero essere opzionali e indipendenti dall'ordine, e una richiesta senza parametri di query dovrebbe restituire una pagina di default sensata. Mantenere pulita questa separazione fa sì che caching, routing e documentazione si comportino in modo prevedibile.
Come gestire gli aggiornamenti parziali: PUT o PATCH?
Usa PUT quando il client invia una rappresentazione completa che sostituisce integralmente la risorsa, e PATCH quando invia solo i campi da cambiare. PUT è idempotente e concettualmente semplice ma costringe i client a inviare (e a rischiare di sovrascrivere) ogni campo. PATCH è più efficiente in banda per risorse grandi; scegli un formato di patch definito come JSON Merge Patch (RFC 7386) per semplici aggiornamenti di campo o JSON Patch (RFC 6902) per operazioni precise su array e strutture annidate. Combina PATCH con la concorrenza ottimistica (If-Match su un ETag) per evitare aggiornamenti persi quando due client modificano in concorrenza.
Come prevenire gli aggiornamenti persi quando due client modificano la stessa risorsa?
Usa il controllo di concorrenza ottimistica con richieste condizionali. Il server restituisce un ETag per la versione corrente di una risorsa; quando un client aggiorna, invia quel valore in un header If-Match. Se la risorsa è cambiata nel frattempo il suo ETag sarà diverso, e il server rifiuta la scrittura con 412 Precondition Failed, invitando il client a rileggere, riconciliare e riprovare. Questo evita il problema dell'aggiornamento perso senza tenere lock a lunga vita, e scala perché il controllo è un confronto di versione economico invece di un lock pessimistico.
Un'API dovrebbe essere stateless, e cosa significa nella pratica?
Sì - la statelessness è un vincolo REST fondamentale e significa che ogni richiesta deve portare tutto ciò che serve al server per elaborarla, senza sessione per-client conservata nella memoria del server tra le chiamate. Nella pratica il client invia le credenziali (un token) a ogni richiesta, e ogni stato applicativo vive sul client o in uno store condiviso come un database o una cache, non legato a una singola istanza di server. La statelessness permette a qualsiasi istanza di gestire qualsiasi richiesta, ed è ciò che rende semplici lo scaling orizzontale, il load balancing e il failover. È anche il motivo per cui dominano i bearer token, invece delle sessioni lato server, nell'autenticazione delle API.
Come rendere i webhook affidabili e sicuri?
Firma ogni payload di webhook (di solito un HMAC sul body con un segreto condiviso in un header di firma) così il ricevente può verificarne l'autenticità, e rifiuta le chiamate non firmate o con firma errata. Includi un ID evento univoco e un timestamp così il consumatore può deduplicare e ignorare i replay, dato che dovresti assumere consegna at-least-once e ritentare le consegne fallite con backoff esponenziale. Aspettati che il ricevente risponda rapidamente con 2xx e svolga il lavoro pesante in modo asincrono, e fornisci un modo per ri-inviare gli eventi mancati. Tratta l'endpoint del ricevente come infrastruttura non fidata che può essere lenta o giù, e progetta retry e dead-lettering di conseguenza.