{"id":940,"date":"2021-07-18T09:37:00","date_gmt":"2021-07-18T07:37:00","guid":{"rendered":"https:\/\/brentasoft.com\/blog\/rest-api-design-best-practice-2021\/"},"modified":"2021-07-18T09:37:00","modified_gmt":"2021-07-18T07:37:00","slug":"rest-api-design-best-practice-2021","status":"publish","type":"post","link":"https:\/\/brentasoft.com\/blog\/rest-api-design-best-practice-2021\/","title":{"rendered":"REST API design: 10 best practice per il 2021"},"content":{"rendered":"<p>Progettare una <strong>REST API<\/strong> non significa semplicemente esporre endpoint HTTP che restituiscono JSON. Significa creare un contratto fra sistemi che dovr\u00e0 sopravvivere ad anni di evoluzione, integrazioni con applicazioni terze, refactoring del backend e cambi di team. Una API mal progettata diventa rapidamente un debito tecnico costoso: ogni modifica rompe i client, la documentazione \u00e8 fuori sincrono con il codice, il supporto agli sviluppatori esterni esplode in ticket e i tempi di onboarding di nuovi consumatori si allungano da ore a settimane.<\/p>\n<p>In questa guida raccogliamo le 10 <strong>rest api design best practice<\/strong> piu&#8217; consolidate nel 2021, dalle scelte di naming alla gestione del versioning, dai codici di stato HTTP alla documentazione con OpenAPI 3.0. Si tratta di principi che applichiamo quotidianamente quando sviluppiamo gestionali, e-commerce e soluzioni di <a href=\"https:\/\/brentasoft.com\/soluzioni\/integrazione-api.php\">integrazione API<\/a> per i nostri clienti, dove l&#8217;interoperabilita&#8217; fra sistemi e&#8217; un requisito non negoziabile.<\/p>\n<h2>1. REST API design: i 10 principi fondamentali<\/h2>\n<p>Prima di entrare nel dettaglio di naming e verbi HTTP, conviene fissare i principi guida che ispirano una buona REST API. Roy Fielding, nella <a href=\"https:\/\/www.ics.uci.edu\/~fielding\/pubs\/dissertation\/top.htm\" target=\"_blank\" rel=\"noopener\">dissertazione del 2000<\/a> in cui ha coniato il termine REST, ha descritto sei vincoli architetturali: client-server, stateless, cacheable, uniform interface, layered system, code on demand (opzionale). Da questi vincoli derivano i principi pratici che ogni API designer dovrebbe interiorizzare.<\/p>\n<ol>\n<li><strong>Risorse, non azioni<\/strong>: una API REST espone risorse (sostantivi), non operazioni (verbi). Le operazioni sono espresse dai metodi HTTP.<\/li>\n<li><strong>Stateless<\/strong>: ogni richiesta contiene tutto il contesto necessario per essere processata. Niente sessioni server-side fra chiamate.<\/li>\n<li><strong>Uniform interface<\/strong>: stessi pattern per tutte le risorse, comportamento prevedibile.<\/li>\n<li><strong>Cacheable<\/strong>: usare gli header HTTP (Cache-Control, ETag, Last-Modified) per consentire caching efficace.<\/li>\n<li><strong>Codici di stato corretti<\/strong>: 2xx per successo, 4xx per errori del client, 5xx per errori del server. Mai un 200 con campo <code>error<\/code> nel body.<\/li>\n<li><strong>Versioning esplicito<\/strong>: la API deve poter evolvere senza rompere i client esistenti.<\/li>\n<li><strong>Documentazione viva<\/strong>: OpenAPI 3.0 \/ Swagger come fonte di verita&#8217; contrattuale.<\/li>\n<li><strong>Sicurezza by default<\/strong>: HTTPS obbligatorio, autenticazione su tutti gli endpoint che non siano deliberatamente pubblici.<\/li>\n<li><strong>Errori machine-readable<\/strong>: risposte di errore con un formato strutturato e codice applicativo riconoscibile.<\/li>\n<li><strong>Coerenza interna<\/strong>: convenzioni di naming, paginazione, filtri uniformi su tutta la superficie API.<\/li>\n<\/ol>\n<p>Questi dieci principi sono il filtro con cui valutare ogni decisione di design. Quando un endpoint sembra strano, quasi sempre viola uno di questi punti.<\/p>\n<h2>2. Naming convention: risorse, sostantivi, plurali<\/h2>\n<p>La <strong>naming convention rest api<\/strong> e&#8217; la prima cosa che salta all&#8217;occhio di chi consuma il tuo servizio. Una buona convenzione rende la API leggibile come una frase in inglese; una cattiva convenzione costringe a consultare la documentazione anche per le operazioni piu&#8217; banali.<\/p>\n<p>Le regole di base, ormai consolidate dal mercato:<\/p>\n<ul>\n<li><strong>Sostantivi al plurale<\/strong> per le collezioni: <code>\/orders<\/code>, <code>\/customers<\/code>, <code>\/products<\/code>. Mai <code>\/order<\/code> o <code>\/getOrders<\/code>.<\/li>\n<li><strong>Identificatori per il singolo elemento<\/strong>: <code>\/orders\/{id}<\/code>. Lo stesso path con un id punta a una specifica risorsa.<\/li>\n<li><strong>Sub-risorse per le relazioni<\/strong>: <code>\/customers\/{id}\/orders<\/code> per gli ordini di un cliente specifico.<\/li>\n<li><strong>Kebab-case nei path<\/strong>: <code>\/purchase-orders<\/code>, non <code>\/purchaseOrders<\/code> o <code>\/purchase_orders<\/code>. Gli URL sono case-sensitive nel path e il kebab-case e&#8217; lo standard di fatto.<\/li>\n<li><strong>camelCase nei JSON body<\/strong>: <code>{\"firstName\": \"Mario\"}<\/code>. Coerente con JavaScript, che e&#8217; il consumatore principale.<\/li>\n<li><strong>Mai verbi nei path<\/strong>: niente <code>\/getOrders<\/code>, <code>\/createCustomer<\/code>. Il verbo e&#8217; il metodo HTTP.<\/li>\n<\/ul>\n<p>Per le operazioni che non si mappano naturalmente su CRUD &#8211; per esempio uno &#8220;stornare&#8221; un pagamento, &#8220;approvare&#8221; un documento, &#8220;rigenerare&#8221; un token &#8211; la convenzione piu&#8217; diffusa e&#8217; usare un sub-path con un verbo: <code>POST \/payments\/{id}\/refunds<\/code>, <code>POST \/documents\/{id}\/approve<\/code>. Si rimane comunque centrati sulla risorsa.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/brentasoft.com\/blog\/wp-content\/uploads\/2021\/07\/team-design-api-rest.jpg\" alt=\"Team di sviluppatori durante una sessione di API design\" \/><\/p>\n<h2>3. HTTP verbs: GET, POST, PUT, PATCH, DELETE<\/h2>\n<p>I metodi HTTP non sono intercambiabili: ognuno ha una semantica precisa, definita dalla RFC 7231 e dalle sue successive. Rispettarla non e&#8217; pedanteria: client, proxy, CDN e tool di test si comportano diversamente in base al verbo usato.<\/p>\n<table border=\"1\" cellpadding=\"8\" cellspacing=\"0\" style=\"border-collapse:collapse;width:100%;margin:20px 0;\">\n<thead>\n<tr style=\"background:#f5f7fa;\">\n<th>Verbo<\/th>\n<th>Uso tipico<\/th>\n<th>Safe<\/th>\n<th>Idempotente<\/th>\n<th>Cacheable<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>GET<\/strong><\/td>\n<td>Leggere una risorsa o una collezione<\/td>\n<td>Si<\/td>\n<td>Si<\/td>\n<td>Si<\/td>\n<\/tr>\n<tr>\n<td><strong>POST<\/strong><\/td>\n<td>Creare una nuova risorsa o azioni non idempotenti<\/td>\n<td>No<\/td>\n<td>No<\/td>\n<td>Solo con header espliciti<\/td>\n<\/tr>\n<tr>\n<td><strong>PUT<\/strong><\/td>\n<td>Sostituire integralmente una risorsa esistente<\/td>\n<td>No<\/td>\n<td>Si<\/td>\n<td>No<\/td>\n<\/tr>\n<tr>\n<td><strong>PATCH<\/strong><\/td>\n<td>Aggiornamento parziale di una risorsa<\/td>\n<td>No<\/td>\n<td>No (di norma)<\/td>\n<td>No<\/td>\n<\/tr>\n<tr>\n<td><strong>DELETE<\/strong><\/td>\n<td>Rimuovere una risorsa<\/td>\n<td>No<\/td>\n<td>Si<\/td>\n<td>No<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>La differenza fra <strong>PUT<\/strong> e <strong>PATCH<\/strong> e&#8217; una delle fonti di confusione piu&#8217; comuni. PUT richiede di inviare l&#8217;intera rappresentazione della risorsa: ogni campo non incluso viene trattato come &#8220;azzerato&#8221; o riportato al valore di default. PATCH applica solo le modifiche specificate nel payload, lasciando intatto il resto. Per i payload PATCH, lo standard piu&#8217; usato in ambito enterprise e&#8217; JSON Merge Patch (RFC 7396), piu&#8217; semplice di JSON Patch (RFC 6902) e sufficiente per la maggior parte dei casi.<\/p>\n<h2>4. Status codes: come usarli correttamente<\/h2>\n<p>Restituire il corretto codice di stato HTTP non e&#8217; un dettaglio: e&#8217; la prima informazione che il client legge per decidere se riprovare, mostrare un errore, fare login o aggiornare la cache. Un&#8217;API che risponde sempre 200 &#8211; anche in caso di errore &#8211; costringe ogni client a parsare il body per scoprire cosa e&#8217; successo, vanificando l&#8217;infrastruttura HTTP esistente.<\/p>\n<p>Il set minimo di status code che ogni REST API dovrebbe usare correttamente:<\/p>\n<ul>\n<li><strong>200 OK<\/strong>: richiesta completata con successo, body presente.<\/li>\n<li><strong>201 Created<\/strong>: nuova risorsa creata. Header <code>Location<\/code> con l&#8217;URL della risorsa creata.<\/li>\n<li><strong>204 No Content<\/strong>: operazione completata, nessun body. Tipico per DELETE o PUT senza response body.<\/li>\n<li><strong>400 Bad Request<\/strong>: richiesta malformata o validazione fallita lato client.<\/li>\n<li><strong>401 Unauthorized<\/strong>: autenticazione mancante o non valida. (Nome storicamente fuorviante: significa &#8220;non autenticato&#8221;.)<\/li>\n<li><strong>403 Forbidden<\/strong>: utente autenticato ma non autorizzato a quella risorsa.<\/li>\n<li><strong>404 Not Found<\/strong>: risorsa inesistente o nascosta per motivi di sicurezza.<\/li>\n<li><strong>409 Conflict<\/strong>: stato attuale incompatibile con la richiesta (es. tentativo di creare una entita&#8217; duplicata).<\/li>\n<li><strong>422 Unprocessable Entity<\/strong>: sintassi corretta ma validazione semantica fallita.<\/li>\n<li><strong>429 Too Many Requests<\/strong>: rate limit superato. Restituire <code>Retry-After<\/code>.<\/li>\n<li><strong>500 Internal Server Error<\/strong>: errore generico del server, non causato dal client.<\/li>\n<li><strong>503 Service Unavailable<\/strong>: servizio temporaneamente non disponibile (manutenzione, overload).<\/li>\n<\/ul>\n<p>Una convenzione utile: <strong>4xx<\/strong> dice al client &#8220;cambia qualcosa nella tua richiesta&#8221;, <strong>5xx<\/strong> dice &#8220;non e&#8217; colpa tua, riprova piu&#8217; tardi&#8221;. Se il dubbio e&#8217; fra 401 e 403, ricordare che 401 implica che ripresentando credenziali valide la richiesta potrebbe avere successo, mentre 403 significa che ne&#8217; la stessa ne&#8217; un&#8217;altra credenziale potranno autorizzare quell&#8217;operazione.<\/p>\n<h2>5. URL path: gerarchia delle risorse<\/h2>\n<p>La struttura del path comunica la relazione fra le risorse. Una buona gerarchia rende intuitive le navigazioni, una cattiva gerarchia genera path innaturali e duplicazioni.<\/p>\n<p>Esempio di gerarchia coerente per un gestionale:<\/p>\n<pre><code>GET    \/api\/v1\/customers\nPOST   \/api\/v1\/customers\nGET    \/api\/v1\/customers\/{id}\nPUT    \/api\/v1\/customers\/{id}\nDELETE \/api\/v1\/customers\/{id}\n\nGET    \/api\/v1\/customers\/{id}\/orders\nPOST   \/api\/v1\/customers\/{id}\/orders\n\nGET    \/api\/v1\/orders\/{id}\nGET    \/api\/v1\/orders\/{id}\/items\nPOST   \/api\/v1\/orders\/{id}\/refunds\n<\/code><\/pre>\n<p>Regole pratiche:<\/p>\n<ul>\n<li><strong>Massimo due livelli di nesting<\/strong>: <code>\/customers\/{id}\/orders<\/code> e&#8217; chiaro, <code>\/customers\/{id}\/orders\/{oid}\/items\/{iid}\/notes\/{nid}<\/code> e&#8217; un incubo. Oltre il secondo livello conviene appiattire usando id top-level: <code>\/order-items\/{id}<\/code>.<\/li>\n<li><strong>Lower-case<\/strong>: per evitare ambiguita&#8217; fra case-sensitive (path) e case-insensitive (host).<\/li>\n<li><strong>Niente estensioni<\/strong>: <code>\/customers<\/code>, non <code>\/customers.json<\/code>. Il formato si negozia con header <code>Accept<\/code>.<\/li>\n<li><strong>Plurale anche per risorse &#8220;singleton&#8221;<\/strong>: se ogni utente ha un unico profilo, <code>\/users\/{id}\/profile<\/code> e&#8217; un compromesso accettabile, in quanto <code>profile<\/code> e&#8217; un singleton legato a quell&#8217;utente.<\/li>\n<\/ul>\n<h2>6. Versioning: path, header, query string (pro\/contro)<\/h2>\n<p>Il <strong>versioning api<\/strong> e&#8217; uno dei pochi temi su cui la community si e&#8217; divisa storicamente. Le tre strategie principali sono:<\/p>\n<h3>Versioning nel path<\/h3>\n<p><code>\/api\/v1\/customers<\/code>, <code>\/api\/v2\/customers<\/code>. E&#8217; la strategia piu&#8217; diffusa nelle API pubbliche (Stripe, Twitter, GitHub). I vantaggi: massima visibilita&#8217; della versione, facile da gestire in routing e log, consente di eseguire piu&#8217; versioni in parallelo come deployment separati. Lo svantaggio &#8220;puristico&#8221; e&#8217; che secondo i fondamentalisti REST l&#8217;URL identifica una risorsa, non un contratto.<\/p>\n<h3>Versioning via header<\/h3>\n<p><code>Accept: application\/vnd.miaapi.v2+json<\/code> oppure header custom <code>X-API-Version: 2<\/code>. Approccio teoricamente piu&#8217; &#8220;REST-puro&#8221; perche&#8217; considera la versione come una variazione di rappresentazione. In pratica e&#8217; meno visibile, complica i test con tool come browser e curl, ed e&#8217; meno gradito agli sviluppatori che integrano l&#8217;API.<\/p>\n<h3>Versioning in query string<\/h3>\n<p><code>\/customers?api-version=2<\/code>. Compromesso fra path e header. Usato da Azure REST API. Si scrive male e si memorizza peggio.<\/p>\n<p>Per la stragrande maggioranza dei progetti raccomandiamo il <strong>versioning nel path<\/strong>, almeno per la versione major. Le minor non hanno bisogno di essere visibili: una API ben progettata aggiunge campi e endpoint senza rompere i client (additive changes). Solo i breaking change giustificano una nuova v2.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/brentasoft.com\/blog\/wp-content\/uploads\/2021\/07\/documentazione-api-codice.jpg\" alt=\"Schermata di codice e documentazione API\" \/><\/p>\n<h2>7. Pagination, filtering, sorting: standard de-facto<\/h2>\n<p>Restituire una collezione di 50.000 record in un&#8217;unica response e&#8217; una ricetta per timeout, problemi di memoria e UX disastrosa. Ogni endpoint che restituisce una lista deve supportare paginazione, filtri e ordinamento.<\/p>\n<h3>Pagination<\/h3>\n<p>Le due famiglie principali:<\/p>\n<ul>\n<li><strong>Offset\/limit<\/strong>: <code>?page=3&page_size=50<\/code> oppure <code>?offset=100&limit=50<\/code>. Semplice, ma soffre di &#8220;drift&#8221; quando i dati cambiano durante la navigazione (record nuovi spinti dentro la pagina). Adatto per dataset piccoli o rapporti.<\/li>\n<li><strong>Cursor-based<\/strong>: <code>?cursor=eyJpZCI6MTIzfQ&limit=50<\/code>. La response include un cursor opaco per la pagina successiva. Robusto contro modifiche concorrenti, performante su dataset grandi (no OFFSET costoso). Standard per API moderne (Stripe, Slack, Twitter).<\/li>\n<\/ul>\n<p>La response dovrebbe sempre includere meta-informazioni di paginazione, per esempio:<\/p>\n<pre><code>{\n  \"data\": [ ... ],\n  \"pagination\": {\n    \"next_cursor\": \"eyJpZCI6MTczfQ\",\n    \"has_more\": true,\n    \"total\": 4287\n  }\n}<\/code><\/pre>\n<h3>Filtering<\/h3>\n<p>Filtri come query string: <code>?status=active&created_after=2021-01-01<\/code>. Per filtri complessi, evitare di reinventare un linguaggio di query: o si usano operatori espliciti (<code>?price[gte]=100&price[lte]=500<\/code>) o ci si appoggia a uno standard come RSQL\/FIQL. Mai mettere logiche di filtro nel body di una GET.<\/p>\n<h3>Sorting<\/h3>\n<p>Convenzione comune: <code>?sort=-created_at,name<\/code>, con prefisso <code>-<\/code> per il descending. Documentare quali campi sono effettivamente ordinabili (deve esserci un indice DB).<\/p>\n<h2>8. Error handling: formato risposte di errore<\/h2>\n<p>Le risposte di errore sono il manuale di sopravvivenza dell&#8217;integratore. Devono essere strutturate, machine-readable e contenere abbastanza informazioni per il debug senza esporre dettagli interni.<\/p>\n<p>Lo standard piu&#8217; utilizzato in ambito enterprise e&#8217; <strong>RFC 7807 &#8211; Problem Details for HTTP APIs<\/strong>:<\/p>\n<pre><code>HTTP\/1.1 422 Unprocessable Entity\nContent-Type: application\/problem+json\n\n{\n  \"type\": \"https:\/\/api.example.com\/errors\/validation\",\n  \"title\": \"Validation failed\",\n  \"status\": 422,\n  \"detail\": \"Il campo email non rispetta il formato richiesto\",\n  \"instance\": \"\/api\/v1\/customers\",\n  \"errors\": [\n    { \"field\": \"email\", \"code\": \"invalid_format\" }\n  ],\n  \"request_id\": \"req_7d6a2b1f\"\n}<\/code><\/pre>\n<p>I campi chiave: <code>type<\/code> (URI di documentazione del tipo di errore), <code>title<\/code> (riassunto leggibile), <code>status<\/code> (status HTTP duplicato), <code>detail<\/code> (messaggio specifico), <code>instance<\/code> (URL della richiesta), piu&#8217; eventuali campi custom come <code>request_id<\/code> per il supporto e <code>errors<\/code> con i dettagli per campo.<\/p>\n<p>Regole d&#8217;oro per gli errori:<\/p>\n<ul>\n<li>Mai esporre stack trace, query SQL o path filesystem nel detail in produzione.<\/li>\n<li>Sempre includere un <code>request_id<\/code> univoco e logharlo lato server.<\/li>\n<li>Codici applicativi stabili (<code>code: \"invalid_format\"<\/code>) sui quali i client possono fare branching, indipendenti dal messaggio user-facing.<\/li>\n<li>Errori di validazione: 422 con dettaglio per campo. Errori di business: 409 Conflict con codice specifico.<\/li>\n<\/ul>\n<h2>9. OpenAPI 3.0 \/ Swagger: documentazione come contratto<\/h2>\n<p>Nel 2021 <strong>openapi swagger<\/strong> e&#8217; lo standard de-facto per documentare REST API. OpenAPI 3.0 (l&#8217;evoluzione di Swagger 2.0, governata dalla <a href=\"https:\/\/www.openapis.org\/\" target=\"_blank\" rel=\"noopener\">OpenAPI Initiative<\/a>) e&#8217; un linguaggio YAML\/JSON che descrive endpoint, parametri, body, risposte, schemi e sicurezza.<\/p>\n<p>I vantaggi di adottare OpenAPI come fonte di verita&#8217;:<\/p>\n<ul>\n<li><strong>Documentazione interattiva<\/strong> con Swagger UI, Redoc o Stoplight Elements. Il consumatore prova le chiamate dal browser.<\/li>\n<li><strong>Generazione client<\/strong> in decine di linguaggi (Java, TypeScript, Python, Go, PHP) con OpenAPI Generator.<\/li>\n<li><strong>Mock server<\/strong> automatici per parallelizzare lo sviluppo backend e frontend (Prism, Stoplight, Postman Mock Servers).<\/li>\n<li><strong>Contract testing<\/strong>: validazione automatica che il backend rispetti il contratto e che i client restino compatibili.<\/li>\n<li><strong>Lint<\/strong> con tool come Spectral per imporre regole di stile aziendale.<\/li>\n<\/ul>\n<p>Due approcci possibili: <strong>code-first<\/strong> (le annotazioni nel codice generano lo spec) o <strong>spec-first<\/strong> (lo spec OpenAPI e&#8217; scritto a mano e il codice viene generato\/validato a partire da esso). Per progetti enterprise, dove il contratto API spesso precede l&#8217;implementazione, lo spec-first paga di piu&#8217; nel medio termine: la API viene &#8220;disegnata&#8221; prima di essere costruita.<\/p>\n<h2>10. Idempotenza e safety<\/h2>\n<p>Un&#8217;operazione e&#8217; <strong>safe<\/strong> se non modifica lo stato del server (GET, HEAD). E&#8217; <strong>idempotente<\/strong> se ripeterla N volte produce lo stesso effetto di farla una volta sola (GET, PUT, DELETE). POST e&#8217; tipicamente non idempotente.<\/p>\n<p>L&#8217;idempotenza e&#8217; cruciale per la robustezza in ambienti reali: reti instabili, retry automatici dei client, code di messaggi at-least-once. Se un client invia un POST per creare un ordine e non riceve risposta, deve poter ripetere la chiamata senza creare due ordini duplicati.<\/p>\n<p>Lo standard piu&#8217; diffuso per rendere POST idempotenti e&#8217; l&#8217;<strong>Idempotency-Key<\/strong>: il client genera un UUID e lo invia in header. Il server memorizza la risposta associata a quella chiave per un periodo (24h tipicamente). Se la stessa chiave arriva una seconda volta, il server restituisce la stessa risposta gia&#8217; generata, senza ri-eseguire l&#8217;operazione.<\/p>\n<pre><code>POST \/api\/v1\/payments\nIdempotency-Key: 7d6a2b1f-3c4d-4e5f-9a8b-1c2d3e4f5a6b\nContent-Type: application\/json\n\n{ \"amount\": 100.00, \"currency\": \"EUR\" }<\/code><\/pre>\n<p>Implementare l&#8217;Idempotency-Key correttamente richiede uno storage dedicato (Redis e&#8217; tipico) e logica di concorrenza per prevenire race condition fra due retry simultanei.<\/p>\n<h2>11. Caching e ETag<\/h2>\n<p>HTTP ha un&#8217;infrastruttura di caching potente che molte API ignorano. Sfruttarla riduce drasticamente latenza, carico server e consumo di banda.<\/p>\n<p>I tre meccanismi da conoscere:<\/p>\n<ul>\n<li><strong>Cache-Control<\/strong>: direttive sul caching. <code>Cache-Control: private, max-age=300<\/code> consente al browser\/client di cacheare la risposta per 5 minuti. <code>no-store<\/code> per dati sensibili.<\/li>\n<li><strong>ETag<\/strong>: hash della rappresentazione corrente della risorsa. Il server lo restituisce in response, il client lo rimanda in <code>If-None-Match<\/code> nelle richieste successive. Se non e&#8217; cambiato, il server risponde 304 Not Modified senza body.<\/li>\n<li><strong>Last-Modified<\/strong>: timestamp dell&#8217;ultima modifica. Variante a granularita&#8217; temporale di ETag, usata con <code>If-Modified-Since<\/code>.<\/li>\n<\/ul>\n<p>Esempio di flusso ETag:<\/p>\n<pre><code># Prima richiesta\nGET \/api\/v1\/customers\/42\nHTTP\/1.1 200 OK\nETag: \"a1b2c3\"\n{ \"id\": 42, \"name\": \"Mario Rossi\" }\n\n# Seconda richiesta\nGET \/api\/v1\/customers\/42\nIf-None-Match: \"a1b2c3\"\nHTTP\/1.1 304 Not Modified\n(nessun body)<\/code><\/pre>\n<p>L&#8217;ETag serve anche per il <strong>controllo di concorrenza ottimistico<\/strong>: in PUT\/PATCH si invia <code>If-Match: \"a1b2c3\"<\/code>; se nel frattempo la risorsa e&#8217; cambiata, il server risponde 412 Precondition Failed e il client deve riconciliare. Indispensabile per evitare il classico &#8220;lost update&#8221; in cui due editor sovrascrivono a turno il lavoro dell&#8217;altro.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/brentasoft.com\/blog\/wp-content\/uploads\/2021\/07\/api-versioning-design.jpg\" alt=\"Office laptop con struttura JSON di una API\" \/><\/p>\n<h2>12. Domande frequenti<\/h2>\n<h3>Quando una API smette di essere RESTful?<\/h3>\n<p>Quando viola sistematicamente i vincoli architetturali: stateful per sessione lato server, verbi nei path, status code sempre 200, risposte non cacheabili, contratto monolitico senza versioning. In pratica nessuno implementa REST al 100% (HATEOAS resta una rarita&#8217;); l&#8217;importante e&#8217; rispettare i principi che generano valore concreto.<\/p>\n<h3>Devo usare HATEOAS?<\/h3>\n<p>HATEOAS (Hypermedia As The Engine Of Application State) prevede che ogni response contenga link alle prossime azioni disponibili. In teoria abilita client &#8220;discovery-driven&#8221;; in pratica, nel 2021, e&#8217; adottato pochissimo perche&#8217; i client tipici (SPA, mobile app) sono comunque accoppiati al contratto. Spec come HAL e JSON:API danno una via di mezzo: includi link self e relazioni dove utili, senza rinunciare a una documentazione esplicita.<\/p>\n<h3>Quante versioni della API devo mantenere in parallelo?<\/h3>\n<p>Nessuno ama la risposta corretta: dipende. La regola pratica e&#8217; &#8220;almeno la attuale + la precedente, per N mesi dichiarati&#8221;. Stripe mantiene versioni storiche da anni grazie a un sistema di &#8220;version pinning&#8221; lato cliente. Per una API interna o di nicchia, sostenere due versioni in produzione e&#8217; gia&#8217; un costo non trascurabile.<\/p>\n<h3>REST o GraphQL nel 2021?<\/h3>\n<p>REST resta la scelta di default per API pubbliche, integrazioni B2B e tutti i casi in cui la cacheabilita&#8217; HTTP, l&#8217;ecosistema di tool e la curva di apprendimento contano. GraphQL brilla quando il client deve aggregare dati da fonti multiple con shape variabile (mobile app con bandwidth costoso, dashboard ricche). Le due tecnologie convivono benissimo: il backend espone REST verso il mondo esterno e GraphQL verso il proprio frontend.<\/p>\n<h3>Come gestisco file upload in REST API?<\/h3>\n<p>Due pattern: <code>multipart\/form-data<\/code> diretto verso l&#8217;endpoint (semplice, va bene fino a qualche decina di MB) o pre-signed URL su object storage (S3, Azure Blob): l&#8217;API restituisce un URL firmato, il client carica direttamente sullo storage senza passare per il backend. Il secondo pattern scala a file di GB e libera l&#8217;application server dal traffico binario.<\/p>\n<h3>Come faccio rate limiting?<\/h3>\n<p>Header standard sulla response: <code>X-RateLimit-Limit<\/code>, <code>X-RateLimit-Remaining<\/code>, <code>X-RateLimit-Reset<\/code>. Quando si supera il limite, 429 Too Many Requests con header <code>Retry-After<\/code>. Implementazione con token bucket o sliding window su Redis. Le quote possono essere per API key, per utente, per IP o combinazione.<\/p>\n<h3>Devo scrivere lo spec OpenAPI a mano o generarlo dal codice?<\/h3>\n<p>Per progetti greenfield enterprise, spec-first paga: il design API diventa un&#8217;attivita&#8217; esplicita, lo spec si rivede in code review, il codice si genera (server stub + DTO + validatori). Per API che evolvono in retroguardia su codebase esistenti, code-first con annotazioni e&#8217; piu&#8217; pragmatico, ma richiede disciplina per mantenere lo spec coerente.<\/p>\n<h2>Conclusione<\/h2>\n<p>Le 10 <strong>rest api design best practice<\/strong> illustrate non sono una checklist da spuntare meccanicamente, ma un framework per prendere decisioni di design coerenti. Una API ben progettata si riconosce dal fatto che le scelte appaiono &#8220;ovvie&#8221; anche a chi la incontra per la prima volta: i path raccontano la struttura del dominio, i verbi HTTP sono usati per quello che sono, gli errori dicono esattamente cosa fare, la documentazione e&#8217; allineata al codice.<\/p>\n<p>Se progetti API per <a href=\"https:\/\/brentasoft.com\/soluzioni\/web-app-pwa.php\">web app e PWA<\/a>, <a href=\"https:\/\/brentasoft.com\/soluzioni\/gestionali-personalizzati.php\">gestionali personalizzati<\/a> o flussi di <a href=\"https:\/\/brentasoft.com\/soluzioni\/automazione.php\">automazione<\/a> aziendale, la qualita&#8217; del design API e&#8217; direttamente proporzionale alla velocita&#8217; con cui altri team potranno integrarsi al tuo prodotto. Per approfondire i fondamentali, abbiamo pubblicato <a href=\"https:\/\/brentasoft.com\/blog\/cos-e-una-api-rest-guida-non-sviluppatori\/\">una guida REST per non sviluppatori<\/a>; per gli aspetti di sicurezza, vedi <a href=\"https:\/\/brentasoft.com\/blog\/api-security-best-practice-2021\/\">API security best practice 2021<\/a>.<\/p>\n<div style=\"background:#f5f7fa;border-left:4px solid #0066cc;padding:20px;margin:30px 0;border-radius:4px;\">\n<h3 style=\"margin-top:0;\">Stai progettando API per la tua azienda?<\/h3>\n<p>Brentasoft sviluppa REST API custom con OpenAPI documentation, versioning gestito e best practice di settore per PMI italiane: gestionali, e-commerce, mobile app.<\/p>\n<p style=\"margin-bottom:0;\"><a href=\"https:\/\/brentasoft.com\/erp-brenta.php\" style=\"display:inline-block;background:#0066cc;color:#fff;padding:12px 24px;border-radius:4px;text-decoration:none;font-weight:600;\">Scopri ERP Brenta &rarr;<\/a><\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>REST API design best practice 2021: naming convention, versioning, OpenAPI Swagger, status codes e idempotenza per backend developer e API designer.<\/p>\n","protected":false},"author":2,"featured_media":941,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_seopress_robots_primary_cat":"","_seopress_titles_title":"REST API design: 10 best practice per il 2021","_seopress_titles_desc":"REST API design best practice 2021: naming convention, versioning, OpenAPI Swagger, status codes e idempotenza per backend developer e API designer.","_seopress_robots_index":"","footnotes":""},"categories":[9],"tags":[601,605,604,602,600,94,603],"class_list":["post-940","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-guide-e-tutorial","tag-api-design","tag-http-verbs","tag-naming-convention","tag-openapi","tag-rest-api","tag-sviluppo-software","tag-swagger"],"_links":{"self":[{"href":"https:\/\/brentasoft.com\/blog\/wp-json\/wp\/v2\/posts\/940","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/brentasoft.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/brentasoft.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/brentasoft.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/brentasoft.com\/blog\/wp-json\/wp\/v2\/comments?post=940"}],"version-history":[{"count":0,"href":"https:\/\/brentasoft.com\/blog\/wp-json\/wp\/v2\/posts\/940\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/brentasoft.com\/blog\/wp-json\/wp\/v2\/media\/941"}],"wp:attachment":[{"href":"https:\/\/brentasoft.com\/blog\/wp-json\/wp\/v2\/media?parent=940"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/brentasoft.com\/blog\/wp-json\/wp\/v2\/categories?post=940"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/brentasoft.com\/blog\/wp-json\/wp\/v2\/tags?post=940"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}