<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ API Rest - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Impara a programmare gratuitamente! Tutorial di programmazione su Python, JavaScript, Linux e molto altro. ]]>
        </description>
        <link>https://www.freecodecamp.org/italian/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ API Rest - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/italian/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 26 May 2026 04:21:04 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/italian/news/tag/api-rest/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ Il Manuale delle Migliori Pratiche di Progettazione di API REST - Come Creare un'API REST con JavaScript, Node.js ed Express.js ]]>
                </title>
                <description>
                    <![CDATA[ Ho creato e utilizzato molte API negli ultimi anni. In questo periodo, mi sono imbattuto in buone e cattive pratiche e ho sperimentato brutte situazioni durante l'utilizzo e la creazione di API. Ma ci sono stati anche grandi momenti. Ci sono articoli utili online che presentano molte migliori pratiche, ma ]]>
                </description>
                <link>https://www.freecodecamp.org/italian/news/il-manuale-delle-migliori-pratiche-di-progettazione-di-api-rest/</link>
                <guid isPermaLink="false">642e916ebdacb60671303019</guid>
                
                    <category>
                        <![CDATA[ API Rest ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Roberto Pauletto ]]>
                </dc:creator>
                <pubDate>Mon, 03 Jul 2023 05:30:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/italian/news/content/images/2023/04/rest-api-design-course-header.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Articolo originale:</strong> <a href="https://www.freecodecamp.org/news/rest-api-design-best-practices-build-a-rest-api/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">REST API Design Best Practices Handbook – How to Build a REST API with JavaScript, Node.js, and Express.js</a>
      </p><p>Ho creato e utilizzato molte API negli ultimi anni. In questo periodo, mi sono imbattuto in buone e cattive pratiche e ho sperimentato brutte situazioni durante l'utilizzo e la creazione di API. Ma ci sono stati anche grandi momenti.</p><p>Ci sono articoli utili online che presentano molte migliori pratiche, ma secondo me molti di essi mancano di praticità. Conoscere la teoria con pochi esempi va bene, ma mi sono sempre chiesto come sarebbe l'implementazione in un esempio più attinente al mondo reale.</p><p>Fornire esempi semplici aiuta nella comprensione del concetto stesso senza molta complessità, ma in pratica le cose non sono sempre così semplici. Sono piuttosto sicuro che sai di cosa sto parlando 😁</p><p>Ecco perché ho deciso di scrivere questo tutorial. Ho unito insieme tutte le cose (buone e meno buone) che ho appreso in un articolo fruibile fornendo un esempio pratico che può essere seguito. Alla fine, creeremo un'API completa mentre implementeremo una buona pratica dopo l'altra.</p><p>Alcune cose da ricordare prima di partire:</p><p>Le buone pratiche, come potresti avere intuito, non sono leggi o regole specifiche da seguire. Sono convenzioni o suggerimenti che si sono evoluti nel tempo e si sono rivelati efficaci. Al giorno d'oggi, alcune sono diventate lo standard. Tuttavia questo non vuol dire che tu le debba adottare in toto.</p><p>Dovrebbero fornirti una direzione per rendere la tua API migliore per quanto riguarda l'esperienza utente (sia chi la usa che chi la crea), la sicurezza e le prestazioni.</p><p>Tieni a mente che i progetti sono diversi e richiedono diversi approcci. Potrebbero esserci situazioni nelle quali non puoi o non dovresti seguire certe convenzioni. Quindi ogni sviluppatore deve prendere queste decisioni in autonomia o con i suoi colleghi.</p><p>Ora che abbiamo tolto di mezzo queste cose, senza ulteriori indugi, mettiamoci al lavoro!</p><h2 id="sommario"><strong>Sommario</strong></h2><ul><li><a href="#il-nostro-progetto-di-esempio">Il nostro progetto di esempio</a></li><li><a href="#prerequisiti">Prerequisiti</a></li><li><a href="#architettura">Architettura</a></li><li><a href="#impostazione-di-base">Impostazione di base</a></li><li><a href="#migliori-pratiche-per-api-rest">Migliori pratiche per API REST</a></li><li><a href="#versionamento">Versionamento</a></li><li><a href="#nomi-di-risorse-al-plurale">Nomi risorse al plurale</a></li><li><a href="#accettare-e-rispondere-con-dati-in-formato-json">Accettare e rispondere con dati in formato JSON</a></li><li><a href="#rispondere-con-codici-di-errore-standard-http">Rispondere con codici di errore standard HTTP</a>	</li><li><a href="#evitare-verbi-nei-nomi-degli-endpoint">Evitare verbi nei nomi degli endpoint</a></li><li><a href="#raggruppare-risorse-associate">Raggruppare risorse associate</a></li><li><a href="#integrare-filtro-ordinamento-e-paginazione">Integrare filtro, ordinamento e paginazione</a></li><li><a href="#usare-cache-dei-dati-per-migliorare-le-prestazioni">Usare cache dei dati per migliorare le prestazioni</a></li><li><a href="#buone-pratiche-di-sicurezza">Buone pratiche di sicurezza</a></li><li><a href="#documentare-correttamente-la-tua-api">Documentare correttamente la tua API</a></li><li><a href="#conclusione">Conclusione</a></li></ul><h3 id="il-nostro-progetto-di-esempio">Il Nostro Progetto di Esempio</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2022/04/alvaro-reyes-qWwpHwip31M-unsplash--1-.jpg" class="kg-image" alt="alvaro-reyes-qWwpHwip31M-unsplash--1-" width="600" height="400" loading="lazy"><figcaption>Photo by <a href="https://unsplash.com/@alvarordesign?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" style="box-sizing: inherit; margin: 0px; padding: 0px; border: 0px; font-style: inherit; font-variant: inherit; font-weight: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-size: 17.6px; vertical-align: baseline; background-color: transparent; color: var(--gray90); text-decoration: underline; cursor: pointer; word-break: break-word;">Alvaro Reyes</a> on <a href="https://unsplash.com/s/photos/project?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" style="box-sizing: inherit; margin: 0px; padding: 0px; border: 0px; font-style: inherit; font-variant: inherit; font-weight: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-size: 17.6px; vertical-align: baseline; background-color: transparent; color: var(--gray90); text-decoration: underline; cursor: pointer; word-break: break-word;">Unsplash</a></figcaption></figure><p>Prima di iniziare l'implementazione delle migliori pratiche nel nostro progetto di esempio, ti faccio una breve introduzione di ciò che andremo a creare.</p><p>Creeremo un'API REST per una Applicazione di Allenamento CrossFit. Se non sei familiare con il CrossFit, si tratta di un metodo di fitness e uno sport competitivo che combina allenamenti ad alta intensità con elementi da parecchi sport (sollevamento pesi, ginnastica e altri).</p><p>Nella nostra applicazione vorremmo creare, leggere, aggiornare e cancellare i <strong><strong>WOD</strong></strong> (<strong><strong>W</strong></strong>orkouts <strong><strong>o</strong></strong>f the <strong><strong>D</strong></strong>ay - Allenamenti della giornata). Questo aiuterà i nostri utenti (vale a dire i proprietari di palestre) a generare piani di allenamento e tracciare i propri allenamenti all'interno di una singola applicazione. Inoltre potranno anche aggiungere qualche importante suggerimento di allenamento per ciascuna sessione.</p><p>Il nostro compito sarà la progettazione e l'implementazione di un'API per quell'applicazione.</p><h3 id="prerequisiti">Prerequisiti</h3><p>Per seguire il tutorial dovrai avere qualche esperienza di JavaScript, Node.js, Express.js e di architettura di backend. Termini come REST e API non dovrebbero essere nuovi per te e dovresti avere una comprensione del <a href="https://it.wikipedia.org/wiki/Sistema_client/server">Modello Client-Server</a>.</p><p>Naturalmente non devi essere un esperto in questi campi, ma averne familiarità e possibilmente qualche esperienza dovrebbe essere sufficiente.</p><p>Se non hai tutti i prerequisiti, ovviamente non è un motivo per saltare questo tutorial. C'è ancora molto da imparare qui anche per te. Ma avere queste conoscenze ti renderà più facile seguire.</p><p>Anche se questa API è scritta in JavaScript e usa Express, le migliori pratiche non si limitano a questi strumenti. Possono essere applicate anche ad altri linguaggi di programmazione o framework.</p><h3 id="architettura"><strong><strong>Archite</strong>t<strong>tur</strong>a</strong></h3><p>Come discusso qui sopra, useremo Express.js per la nostra API. Non voglio creare un'architettura complessa, quindi mi piacerebbe attenermi all'<strong>architettura a 3 livelli</strong>:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/04/Bildschirmfoto-2022-04-25-um-14.33.24-1.png" class="kg-image" alt="Bildschirmfoto-2022-04-25-um-14.33.24-1" width="600" height="400" loading="lazy"></figure><p>All'interno del <strong><strong>Controller</strong></strong> gestiremo tutto ciò che abbia relazione con HTTP. Il che vuol dire che avremo a che fare con richieste e risposte per i nostri endpoint. Sopra questo livello c'è anche un piccolo <strong><strong>Router</strong></strong> da Express che instrada le richieste al controller corrispondente.</p><p>Tutta la logica di business sarà nel<strong><strong> Service</strong> Layer<strong> </strong></strong>(Livello Servizio) che esporta certi servizi (metodi) usati dal controller.</p><p>Il terzo livello, <strong><strong>Data Access Layer</strong></strong> &nbsp;(Livello di Accesso Dati) sarà dove lavoreremo con il nostro database. Esporteremo alcuni metodi per certe operazioni di database come creare un WOD (allenamento del giorno) che può essere usato dal nostro Service Layer.</p><p>Nel nostro esempio non useremo un <em>vero</em> database come MongoDB o PostgreSQL in quanto preferirei concentrarmi maggiormente sulle migliori pratiche stesse. Pertanto useremo un file JSON locale che imita il nostro database. Tuttavia questa logica può essere trasferita ad altri database, naturalmente.</p><h3 id="impostazione-di-base"><strong>Impostazione di Base</strong></h3><p>Ora dovremmo essere in grado di creare l'impostazione di base per la nostra API. Non complicheremo eccessivamente le cose e costruiremo una struttura di progetto semplice ma organizzata.</p><p>Per prima cosa creiamo la struttura di directory generale con tutti i file necessari e le dipendenze. Quindi eseguiremo un piccolo test per verificare che tutto venga eseguito correttamente:</p><pre><code class="language-bash"># Crea la directory del progetto e portati su di essa
mkdir crossfit-wod-api &amp;&amp; cd crossfit-wod-api</code></pre><pre><code class="language-bash"># Crea una cartella src e portati su di essa
mkdir src &amp;&amp; cd src</code></pre><pre><code class="language-bash"># Crea le sotto cartelle
mkdir controllers &amp;&amp; mkdir services &amp;&amp; mkdir database &amp;&amp; mkdir routes</code></pre><pre><code class="language-bash"># Crea un file indice (il punto di entrata della nostra API)
touch index.js</code></pre><pre><code class="language-bash"># Attualmente ci troviamo nella cartella src, quindi dobbiamo prima salire di un livello 
cd .. 

# Crea il file package.json 
npm init -y</code></pre><p>Installa le dipendenze per l'impostazione di base:</p><pre><code class="language-bash"># Dipendenze di sviluppo
npm i -D nodemon 

# Dipendenze 
npm i express</code></pre><p>Apri il progetto con il tuo editor di testo preferito e configura Express:</p><pre><code class="language-javascript">// In src/index.js 
const express = require("express"); 

const app = express(); 
const PORT = process.env.PORT || 3000; 

// A scopo di test 
app.get("/", (req, res) =&gt; { 
    res.send("&lt;h2&gt;It's Working!&lt;/h2&gt;"); 
}); 

app.listen(PORT, () =&gt; { 
    console.log(`API is listening on port ${PORT}`); 
});</code></pre><p>Aggiungi un nuovo script chiamato <strong><strong>"dev"</strong></strong> all'interno di package.json:</p><pre><code class="language-json">{
  "name": "crossfit-wod-api",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "nodemon src/index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "nodemon": "^2.0.15"
  },
  "dependencies": {
    "express": "^4.17.3"
  }
}
</code></pre><p>Lo script si assicura che il server di sviluppo venga riavviato automaticamente quando eseguiamo delle modifiche (grazie a nodemon).</p><p>Lancia il server di sviluppo:</p><pre><code class="language-bash">npm run dev</code></pre><p>Se osservi il tuo terminale, dovresti vedere un messaggio che dice <strong><strong>"API is listening on port 3000"</strong></strong>.</p><p>All'interno del tuo browser vai a <strong><strong>localhost:3000</strong></strong>. Se tutto è impostato correttamente, dovresti vedere quanto segue:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2022/04/Bildschirmfoto-2022-04-30-um-11.09.44.png" class="kg-image" alt="Bildschirmfoto-2022-04-30-um-11.09.44" width="600" height="400" loading="lazy"></figure><p>Grande! Ora tutto è impostato per implementare le migliori pratiche.</p><h2 id="migliori-pratiche-per-api-rest"><strong>Migliori Pratiche per API <strong>REST</strong></strong></h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2022/04/constantin-wenning-idDvA4jPBO8-unsplash--1-.jpg" class="kg-image" alt="constantin-wenning-idDvA4jPBO8-unsplash--1-" width="600" height="400" loading="lazy"><figcaption>Photo by <a href="https://unsplash.com/@conniwenningsimages?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" style="box-sizing: inherit; margin: 0px; padding: 0px; border: 0px; font-style: inherit; font-variant: inherit; font-weight: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-size: 17.6px; vertical-align: baseline; background-color: transparent; color: var(--gray90); text-decoration: underline; cursor: pointer; word-break: break-word;">Constantin Wenning</a> on <a href="https://unsplash.com/s/photos/handshake?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" style="box-sizing: inherit; margin: 0px; padding: 0px; border: 0px; font-style: inherit; font-variant: inherit; font-weight: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-size: 17.6px; vertical-align: baseline; background-color: transparent; color: var(--gray90); text-decoration: underline; cursor: pointer; word-break: break-word;">Unsplash</a></figcaption></figure><p>Bene! Ora che abbiamo un'impostazione veramente di base per Express, possiamo estendere la nostra API con le seguenti migliori pratiche.</p><p>Iniziamo in modo semplice con i nostri endpoint <a href="https://it.wikipedia.org/wiki/CRUD">CRUD</a> fondamentali. Successivamente estenderemo l'API con ciascuna migliore pratica.</p><h3 id="versionamento"><strong><strong>Version</strong>amento</strong></h3><p>Aspetta un secondo. Prima di scrivere codice specifico per qualsiasi API dobbiamo essere consapevoli del versionamento. Come in ogni altra applicazione, ci saranno miglioramenti, nuove funzionalità e cose di questo genere. Quindi è importante applicare un buon versionamento anche alla nostra API.</p><p>Il grande vantaggio è che possiamo lavorare con nuove funzionalità o miglioramenti su una nuova versione mentre i client stanno ancora usando la versione corrente e non saranno interessati da modifiche non retro compatibili.</p><p>Non forzeremo neanche i client ad adottare la nuova versione immediatamente. Possono usare la versione corrente e migrare in autonomia quando la nuova versione è stabile.</p><p>La versione corrente e la nuova sono praticamente in esecuzione in parallelo e non interferiscono tra loro.</p><p>Come possiamo differenziare le versioni? Una buona pratica è aggiungere un segmento di percorso come <strong><strong>v1</strong></strong> o <strong><strong>v2 </strong></strong>all'interno dell'URL.</p><pre><code class="language-javascript">// Versione 1 
"/api/v1/workouts" 

// Versione 2 
"/api/v2/workouts" 

// ...</code></pre><p>Ecco ciò che esporremo al mondo esterno, quello che sarà utilizzato da altri sviluppatori. Dobbiamo anche strutturare il nostro progetto per poter differenziare ogni versione.</p><p>Ci sono molti approcci diversi per la gestione del versionamento all'interno di una API Express. Nel nostro caso, vorrei creare una sotto cartella per ogni versione all'interno della directory <strong>src</strong> chiamata <strong><strong>v1</strong></strong>, <strong>v2 </strong>e così via.</p><pre><code class="language-bash">mkdir src/v1</code></pre><p>Ora spostiamo la cartella <code>routes</code> nella nuova directory <code>v1</code>.</p><pre><code class="language-bash"># Ottieni il percorso della tua directory corrente (copialo)
pwd 

# Sposta "routes" in "v1" (inserisci il percorso ricavato qui sopra in {pwd}) 
mv {pwd}/src/routes {pwd}/src/v1</code></pre><p>La nuova directory <strong><strong><code>/src/v1/routes</code> </strong></strong>conterrà tutte le nostre rotte per la versione 1. Aggiungeremo "vero" contenuto successivamente. Per ora aggiungiamo un semplice file <strong><strong>index.js </strong></strong>per vedere se funziona.</p><pre><code class="language-bash"># In /src/v1/routes 
touch index.js</code></pre><p>All'interno del file inseriamo il codice per avviare un semplice router.</p><pre><code class="language-javascript">// In src/v1/routes/index.js
const express = require("express");
const router = express.Router();

router.route("/").get((req, res) =&gt; {
  res.send(`&lt;h2&gt;Hello from ${req.baseUrl}&lt;/h2&gt;`);
});

module.exports = router;</code></pre><p>Ora dobbiamo attaccare il nostro router per v1 all'interno del punto di entrata radice nel file <code>src/index.js</code>.</p><pre><code class="language-javascript">// In src/index.js
const express = require("express");
// *** AGGIUNGI ***
const v1Router = require("./v1/routes");

const app = express();
const PORT = process.env.PORT || 3000;

// *** RIMUOVI ***
app.get("/", (req, res) =&gt; {
  res.send("&lt;h2&gt;It's Working!&lt;/h2&gt;");
});

// *** AGGIUNGI ***
app.use("/api/v1", v1Router);

app.listen(PORT, () =&gt; {
  console.log(`API is listening on port ${PORT}`);
});
</code></pre><p>Ora nel tuo browser vai a <strong><strong>localhost:3000/api/v1</strong></strong> e dovresti vedere quanto segue:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2022/04/Bildschirmfoto-2022-04-30-um-11.22.28.png" class="kg-image" alt="Bildschirmfoto-2022-04-30-um-11.22.28" width="600" height="400" loading="lazy"></figure><p>Congratulazioni! Hai appena strutturato il progetto per gestire diverse versioni. Ora passiamo le richieste in arrivo su <code>/api/v1</code> alla versione 1 del nostro router, il quale successivamente instraderà ogni richiesta al metodo del controller corrispondente.</p><p>Prima di continuare, vorrei sottolineare una cosa.</p><p>Abbiamo appena spostato la nostra cartella <code>routes</code> all'interno della directory <code>v1</code>. Le altre cartelle come <code>controllers</code> o <code>services</code> rimangono ancora nella directory <code>src</code>. Questo va bene per adesso in quanto stiamo creando un'API piuttosto piccola. Possiamo usare gli stessi controller e servizi in ogni versione globalmente.</p><p>Quando l'API sta crescendo e richiede diversi metodi di controller specifici per la versione v2, per esempio, sarebbe una idea migliore spostare la cartella <code>controllers</code> all'interno della directory v2 in modo di avere tutta la logica specifica per quella particolare versione incapsulata.</p><p>Un'altra ragione per fare questo potrebbe essere che potremmo cambiare un servizio che è usato da tutte le altre versioni. Non vogliamo che ci siano problemi di compatibilità con altre versioni. Pertanto sarebbe una decisione saggia spostare anche la cartella <code>services</code> all'interno della cartella della specifica versione.</p><p>Come detto in precedenza, tuttavia, nel nostro esempio ritengo che vada bene differenziare solo le rotte e lasciare che il router gestisca il resto. Cionondimeno è importante tenere a mente di avere una struttura pulita quando l'API cresce di dimensioni e necessita di modifiche.</p><h3 id="nomi-di-risorse-al-plurale"><strong>Nomi di Risorse al Plurale</strong></h3><p>Dopo aver impostato tutto possiamo concentrarci sulla vera implementazione della nostra API. Come ho detto, vorrei iniziare con gli endpoint CRUD fondamentali.</p><p>In altre parole, iniziamo implementando gli endpoint per creare, leggere, aggiornare ed eliminare gli allenamenti.</p><p>Per prima cosa creiamo un controller, un servizio e un router specifici per i nostri allenamenti (workout).</p><pre><code class="language-bash">touch src/controllers/workoutController.js 

touch src/services/workoutService.js 

touch src/v1/routes/workoutRoutes.js</code></pre><p>Mi piace sempre partire prima con le rotte. Pensiamo come denominare i nostri endpoint. Ciò va di pari passo con questa particolare miglior pratica.</p><p>Possiamo denominare l'endpoint di creazione <strong><strong><code>/api/v1/workout</code> </strong></strong>in quanto vorremmo aggiungere un altro allenamento, giusto? Praticamente c'è nulla di sbagliato con questo approccio, ma può portare a fraintendimenti.</p><p>Ricorda sempre: la tua API viene usata da altri esseri umani e dovrebbe essere precisa. Questo si applica anche per la denominazione delle tue risorse.</p><p>Ho sempre immaginato una risorsa come una scatola. Nel nostro esempio la scatola è una collezione che contiene diversi <strong>allenamenti </strong>(workouts).</p><p>Usare il plurale per denominare le tue risorse ha il grande vantaggio che per le altre persone risulta chiaro che si tratta di una collezione di diversi allenamenti.</p><p>Pertanto definiamo i nostri endpoint all'interno del router nel file <code>workoutRoutes.js</code>.</p><pre><code class="language-javascript">// In src/v1/routes/workoutRoutes.js
const express = require("express");
const router = express.Router();

router.get("/", (req, res) =&gt; {
  res.send("Get all workouts");
});

router.get("/:workoutId", (req, res) =&gt; {
  res.send("Get an existing workout");
});

router.post("/", (req, res) =&gt; {
  res.send("Create a new workout");
});

router.patch("/:workoutId", (req, res) =&gt; {
  res.send("Update an existing workout");
});

router.delete("/:workoutId", (req, res) =&gt; {
  res.send("Delete an existing workout");
});

module.exports = router;
</code></pre><p>Puoi eliminare il file di test <strong><strong><code>index.js</code></strong></strong> all'interno di <strong><strong><code>src/v1/routes</code></strong></strong>.</p><p>Ora passiamo ai nostri punti di entrata e agganciamo il nostro router per la versione v1 (v1WorkoutRouter).</p><pre><code class="language-javascript">// In src/index.js
const express = require("express");
// *** RIMUOVI ***
const v1Router = require("./v1/routes");
// *** AGGIUNGI ***
const v1WorkoutRouter = require("./v1/routes/workoutRoutes");

const app = express();
const PORT = process.env.PORT || 3000;

// *** RIMUOVI ***
app.use("/api/v1", v1Router);

// *** AGGIUNGI ***
app.use("/api/v1/workouts", v1WorkoutRouter);

app.listen(PORT, () =&gt; {
  console.log(`API is listening on port ${PORT}`);
});
</code></pre><p>È andato tutto liscio, giusto? Ora catturiamo tutte le richieste verso <strong><strong><code>/api/v1/workouts</code></strong></strong> con il nostro <code>v1WorkoutRouter</code>.</p><p>All'interno del nostro router chiameremo un metodo diverso gestito dal nostro controller per ciascun endpoint.</p><p>Creiamo un metodo per ciascun endpoint. Inviamo semplicemente un messaggio di risposta, dovrebbe bastare per adesso.</p><pre><code class="language-javascript">// In src/controllers/workoutController.js
const getAllWorkouts = (req, res) =&gt; {
  res.send("Get all workouts");
};

const getOneWorkout = (req, res) =&gt; {
  res.send("Get an existing workout");
};

const createNewWorkout = (req, res) =&gt; {
  res.send("Create a new workout");
};

const updateOneWorkout = (req, res) =&gt; {
  res.send("Update an existing workout");
};

const deleteOneWorkout = (req, res) =&gt; {
  res.send("Delete an existing workout");
};

module.exports = {
  getAllWorkouts,
  getOneWorkout,
  createNewWorkout,
  updateOneWorkout,
  deleteOneWorkout,
};
</code></pre><p>Ora è tempo di rifattorizzare un po' il nostro router e usare i metodi del controller.</p><pre><code class="language-javascript">// In src/v1/routes/workoutRoutes.js
const express = require("express");
const workoutController = require("../../controllers/workoutController");

const router = express.Router();

router.get("/", workoutController.getAllWorkouts);

router.get("/:workoutId", workoutController.getOneWorkout);

router.post("/", workoutController.createNewWorkout);

router.patch("/:workoutId", workoutController.updateOneWorkout);

router.delete("/:workoutId", workoutController.deleteOneWorkout);

module.exports = router;
</code></pre><p>Ora possiamo testare le nostre richieste <strong><strong>GET</strong> </strong>per l'endopoint <strong><strong><code>/api/v1/workouts/:workoutId</code></strong></strong> digitando <strong><strong>localhost:3000/api/v1/workouts/2342</strong></strong> nel browser. Dovresti vedere qualcosa tipo questo:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2022/04/Bildschirmfoto-2022-04-30-um-11.29.19.png" class="kg-image" alt="Bildschirmfoto-2022-04-30-um-11.29.19" width="600" height="400" loading="lazy"></figure><p>Ci siamo riusciti! Il primo livello della nostra architettura è fatto. Creiamo ora il livello del servizio implementando la nostra prossima migliore pratica.</p><h3 id="accettare-e-rispondere-con-dati-in-formato-json"><strong>Accettare e Rispondere con Dati in Formato JSON</strong></h3><p>Quando interagisci con un'API, invii sempre dati specifici con la tua richiesta oppure ricevi dati con la risposta. Ci sono molti formati di dato diversi ma JSON (JavaScript Object Notation) è un formato standardizzato.</p><p>Anche se JSON contiene il termine <strong><strong>JavaScript</strong></strong>, non legato specificatamente a questo linguaggio. Puoi scrivere le tue API anche in Java o Python, che sono anch'essi in grado di gestire bene JSON.</p><p>A causa della sua standardizzazione, l'API dovrebbe accettare e rispondere con dati in formato JSON.</p><p>Diamo uno sguardo alla nostra implementazione corrente e vediamo come possiamo integrare questa miglior pratica.</p><p>Per prima cosa creiamo il nostro livello di servizio.</p><pre><code class="language-javascript">// In src/services/workoutService.js
const getAllWorkouts = () =&gt; {
  return;
};

const getOneWorkout = () =&gt; {
  return;
};

const createNewWorkout = () =&gt; {
  return;
};

const updateOneWorkout = () =&gt; {
  return;
};

const deleteOneWorkout = () =&gt; {
  return;
};

module.exports = {
  getAllWorkouts,
  getOneWorkout,
  createNewWorkout,
  updateOneWorkout,
  deleteOneWorkout,
};
</code></pre><p>È anche una buona pratica denominare i metodi del servizio allo stesso modo dei metodi del controller, in modo da avere una connessione tra questi. Iniziamo con il restituire nulla.</p><p>All'interno del controller nel file <code>workoutController.js</code> possiamo usare questi metodi.</p><pre><code class="language-javascript">// In src/controllers/workoutController.js
// *** AGGIUNGI***
const workoutService = require("../services/workoutService");

const getAllWorkouts = (req, res) =&gt; {
  // *** AGGIUNGI ***
  const allWorkouts = workoutService.getAllWorkouts();
  res.send("Get all workouts");
};

const getOneWorkout = (req, res) =&gt; {
  // *** AGGIUNGI ***
  const workout = workoutService.getOneWorkout();
  res.send("Get an existing workout");
};

const createNewWorkout = (req, res) =&gt; {
  // *** AGGIUNGI ***
  const createdWorkout = workoutService.createNewWorkout();
  res.send("Create a new workout");
};

const updateOneWorkout = (req, res) =&gt; {
  // *** AGGIUNGI ***
  const updatedWorkout = workoutService.updateOneWorkout();
  res.send("Update an existing workout");
};

const deleteOneWorkout = (req, res) =&gt; {
  // *** AGGIUNGI ***
  workoutService.deleteOneWorkout();
  res.send("Delete an existing workout");
};

module.exports = {
  getAllWorkouts,
  getOneWorkout,
  createNewWorkout,
  updateOneWorkout,
  deleteOneWorkout,
};
</code></pre><p>Al momento non dovrebbe cambiare nulla all'interno delle nostre risposte. Tuttavia sotto il cofano il livello controller ora parla con il livello servizio.</p><p>All'interno dei metodi del servizio gestiremo la nostra logica di business come la trasformazione delle strutture dati e la comunicazione con il livello database.</p><p>Per fare questo, ci serve un database e una collezione di metodi che gestisca effettivamente l'interazione con il database. Il nostro database sarà un semplice file JSON che abbiamo già valorizzato in precedenza con qualche allenamento.</p><pre><code class="language-bash"># Crea un nuovo file chiamato db.json all'interno di src/database 
touch src/database/db.json 

# Crea un file workout.js che contiene tutti i metodi specifici per l'allenamento in /src/database 
touch src/database/Workout.js</code></pre><p>Copia quanto segue in db.json:</p><pre><code class="language-json">{
  "workouts": [
    {
      "id": "61dbae02-c147-4e28-863c-db7bd402b2d6",
      "name": "Tommy V",
      "mode": "For Time",
      "equipment": [
        "barbell",
        "rope"
      ],
      "exercises": [
        "21 thrusters",
        "12 rope climbs, 15 ft",
        "15 thrusters",
        "9 rope climbs, 15 ft",
        "9 thrusters",
        "6 rope climbs, 15 ft"
      ],
      "createdAt": "4/20/2022, 2:21:56 PM",
      "updatedAt": "4/20/2022, 2:21:56 PM",
      "trainerTips": [
        "Split the 21 thrusters as needed",
        "Try to do the 9 and 6 thrusters unbroken",
        "RX Weights: 115lb/75lb"
      ]
    },
    {
      "id": "4a3d9aaa-608c-49a7-a004-66305ad4ab50",
      "name": "Dead Push-Ups",
      "mode": "AMRAP 10",
      "equipment": [
        "barbell"
      ],
      "exercises": [
        "15 deadlifts",
        "15 hand-release push-ups"
      ],
      "createdAt": "1/25/2022, 1:15:44 PM",
      "updatedAt": "3/10/2022, 8:21:56 AM",
      "trainerTips": [
        "Deadlifts are meant to be light and fast",
        "Try to aim for unbroken sets",
        "RX Weights: 135lb/95lb"
      ]
    },
    {
      "id": "d8be2362-7b68-4ea4-a1f6-03f8bc4eede7",
      "name": "Heavy DT",
      "mode": "5 Rounds For Time",
      "equipment": [
        "barbell",
        "rope"
      ],
      "exercises": [
        "12 deadlifts",
        "9 hang power cleans",
        "6 push jerks"
      ],
      "createdAt": "11/20/2021, 5:39:07 PM",
      "updatedAt": "11/20/2021, 5:39:07 PM",
      "trainerTips": [
        "Aim for unbroken push jerks",
        "The first three rounds might feel terrible, but stick to it",
        "RX Weights: 205lb/145lb"
      ]
    }
  ]
}</code></pre><p>Come puoi vedere ci sono tre allenamenti inseriti. Un allenamento contiene i campi id, name, mode, equipment, exercises, createdAt, updatedAt, e trainerTips, (rispettivamente &nbsp;id, nome, modalità, equipaggiamenti, esercizi, creatoIl, aggiornatoIl e suggerimentiAllenatore - n.d.t.).</p><p>Partiamo con il più semplice e ritorniamo tutti gli allenamenti che sono stati salvati, poi iniziamo a implementare il metodo corrispondente all'interno del livello di accesso dati (<code>src/database/Workout.js</code>).</p><p>Ancora una volta, ho scelto di denominare il metodo qui con lo stesso nome di quello del servizio e del controller, ma è completamente opzionale.</p><pre><code class="language-javascript">// In src/database/Workout.js
const DB = require("./db.json");

const getAllWorkouts = () =&gt; {
  return DB.workouts;
};

module.exports = { getAllWorkouts };
</code></pre><p>Torniamo nuovamente al servizio nel file <code>workoutService.js</code> e implementiamo la logica per <code><strong>getAllWorkouts</strong></code>.</p><pre><code class="language-javascript">// In src/services/workoutService.js
// *** AGGIUNGI ***
const Workout = require("../database/Workout");
const getAllWorkouts = () =&gt; {
  // *** AGGIUNGI ***
  const allWorkouts = Workout.getAllWorkouts();
  // *** AGGIUNGI ***
  return allWorkouts;
};

const getOneWorkout = () =&gt; {
  return;
};

const createNewWorkout = () =&gt; {
  return;
};

const updateOneWorkout = () =&gt; {
  return;
};

const deleteOneWorkout = () =&gt; {
  return;
};

module.exports = {
  getAllWorkouts,
  getOneWorkout,
  createNewWorkout,
  updateOneWorkout,
  deleteOneWorkout,
};
</code></pre><p>Ritornare tutti gli allenamenti è piuttosto semplice e non dobbiamo fare trasformazioni in quanto è già un file JSON. Non dobbiamo neppure ricevere alcun argomento per ora. Quindi questa implementazione è piuttosto semplice, ma ci torneremo sopra in futuro.</p><p>Tornando al controller in <code>workoutControllers.js</code> riceviamo il valore di ritorno da <code>workoutService.getAllWorkouts()</code> e lo inviamo semplicemente al client. Abbiamo girato al controller la risposta dal database tramite il nostro servizio.</p><pre><code class="language-javascript">// In src/controllers/workoutControllers.js
const workoutService = require("../services/workoutService");

const getAllWorkouts = (req, res) =&gt; {
  const allWorkouts = workoutService.getAllWorkouts();
  // *** AGGIUNGI ***
  res.send({ status: "OK", data: allWorkouts });
};

const getOneWorkout = (req, res) =&gt; {
  const workout = workoutService.getOneWorkout();
  res.send("Get an existing workout");
};

const createNewWorkout = (req, res) =&gt; {
  const createdWorkout = workoutService.createNewWorkout();
  res.send("Create a new workout");
};

const updateOneWorkout = (req, res) =&gt; {
  const updatedWorkout = workoutService.updateOneWorkout();
  res.send("Update an existing workout");
};

const deleteOneWorkout = (req, res) =&gt; {
  workoutService.deleteOneWorkout();
  res.send("Delete an existing workout");
};

module.exports = {
  getAllWorkouts,
  getOneWorkout,
  createNewWorkout,
  updateOneWorkout,
  deleteOneWorkout,
};
</code></pre><p>Nel browser vai a <strong><strong>localhost:3000/api/v1/workouts</strong></strong> e dovresti vedere la risposta in formato JSON.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2022/04/Bildschirmfoto-2022-04-30-um-11.38.14.png" class="kg-image" alt="Bildschirmfoto-2022-04-30-um-11.38.14" width="600" height="400" loading="lazy"></figure><p>È andata alla grande! Rispondiamo con dati in formato JSON. E per quanto riguarda quello che accettiamo? Pensiamo a un endpoint dove riceviamo dati JSON dal client. L'endpoint per creare o aggiornare un allenamento necessita di dati dal client.</p><p>All'interno del controller estraiamo il corpo della richiesta per creare un nuovo allenamento e lo passiamo al servizio. All'interno del servizio lo inseriamo nel nostro <code>DB.json</code> e inviamo l'allenamento appena creato al client.</p><p>Per essere in grado di elaborare JSON inviato all'interno del corpo di una richiesta dobbiamo prima installare <strong><strong>body-parser</strong></strong> e configurarlo.</p><pre><code class="language-bash">npm i body-parser</code></pre><pre><code class="language-javascript">// In src/index.js 
const express = require("express");
// *** AGGIUNGI ***
const bodyParser = require("body-parser");
const v1WorkoutRouter = require("./v1/routes/workoutRoutes");

const app = express();
const PORT = process.env.PORT || 3000;

// *** AGGIUNGI ***
app.use(bodyParser.json());
app.use("/api/v1/workouts", v1WorkoutRouter);

app.listen(PORT, () =&gt; {
  console.log(`API is listening on port ${PORT}`);
});
</code></pre><p>Ora siamo in grado di ricevere dati in formato JSON all'interno del nostro controller in <strong><strong><code>req.body</code></strong></strong>.</p><p>Per eseguire un test appropriato, apriamo il nostro client HTTP preferito (io uso Postman), creiamo una richiesta POST a <strong>localhost:3000/api/v1/workouts</strong> e nel corpo della richiesta inseriamo in formato JSON quanto segue:</p><pre><code class="language-javascript">{
  "name": "Core Buster",
  "mode": "AMRAP 20",
  "equipment": [
    "rack",
    "barbell",
    "abmat"
  ],
  "exercises": [
    "15 toes to bars",
    "10 thrusters",
    "30 abmat sit-ups"
  ],
  "trainerTips": [
    "Split your toes to bars into two sets maximum",
    "Go unbroken on the thrusters",
    "Take the abmat sit-ups as a chance to normalize your breath"
  ]
}</code></pre><p>Come potresti aver notato, ci sono alcune proprietà mancanti come "id", "createdAt" e "updatedAt". È compito della nostra API aggiungere queste proprietà prima dell'inserimento. Ce ne occuperemo all'interno del servizio che gestisce gli allenamenti successivamente.</p><p>All'interno del metodo <strong><strong><code>createNewWorkout</code></strong></strong> nel controller, nel file <code>workoutController.js</code>, possiamo estrarre il corpo dall'oggetto request, effettuare qualche validazione e passarlo come argomento per il nostro servizio.</p><pre><code class="language-javascript">// In src/controllers/workoutController.js
...

const createNewWorkout = (req, res) =&gt; {
  // *** AGGIUNGI ***
  const { body } = req;
  if (
    !body.name ||
    !body.mode ||
    !body.equipment ||
    !body.exercises ||
    !body.trainerTips
  ) {
    return;
  }
  // *** AGGIUNGI ***
  const newWorkout = {
    name: body.name,
    mode: body.mode,
    equipment: body.equipment,
    exercises: body.exercises,
    trainerTips: body.trainerTips,
  };
  // *** AGGIUNGI ***
  const createdWorkout = workoutService.createNewWorkout(newWorkout);
  // *** AGGIUNGI ***
  res.status(201).send({ status: "OK", data: createdWorkout });
};

...</code></pre><p>Per migliorare la validazione della richiesta in genere vorrai usare un pacchetto di terze parti come <a href="https://express-validator.github.io/docs/">express-validator</a>.</p><p>Passiamo al servizio che gestisce gli allenamenti e riceviamo i dati dal controller all'interno del metodo <code>createNewWorkout</code>.</p><p>Dopo avere aggiunto le proprietà mancanti all'oggetto, lo passiamo a un nuovo metodo nel nostro livello di accesso dati per conservarlo nel nostro DB.</p><p>Per prima cosa creiamo una semplice funzione di utilità che sovrascrive il nostro file JSON per salvare i nuovi dati.</p><pre><code class="language-bash"># Crea un file utils.js all'interno della directory database 
touch src/database/utils.js</code></pre><pre><code class="language-javascript">// In src/database/utils.js
const fs = require("fs");

const saveToDatabase = (DB) =&gt; {
  fs.writeFileSync("./db.json", JSON.stringify(DB, null, 2), {
    encoding: "utf-8",
  });
};

module.exports = { saveToDatabase };
</code></pre><p>Poi possiamo usare questa funzione nel nostro file Workout.js.</p><pre><code class="language-javascript">// In src/database/Workout.js
const DB = require("./db.json");
// *** AGGIUNGI ***
const { saveToDatabase } = require("./utils");


const getAllWorkouts = () =&gt; {
  return DB.workouts;
};

// *** AGGIUNGI ***
const createNewWorkout = (newWorkout) =&gt; {
  const isAlreadyAdded =
    DB.workouts.findIndex((workout) =&gt; workout.name === newWorkout.name) &gt; -1;
  if (isAlreadyAdded) {
    return;
  }
  DB.workouts.push(newWorkout);
  saveToDatabase(DB);
  return newWorkout;
};

module.exports = {
  getAllWorkouts,
  // *** AGGIUNGI ***
  createNewWorkout,
};
</code></pre><p>È andato tutto bene! Il passaggio successivo consiste nell'utilizzare i metodi del database all'interno del nostro servizio.</p><pre><code class="language-bash"># Installa il pacchetto uuid 
npm i uuid</code></pre><pre><code class="language-javascript">// In src/services/workoutService.js
// *** AGGIUNGI ***
const { v4: uuid } = require("uuid");
// *** AGGIUNGI ***
const Workout = require("../database/Workout");

const getAllWorkouts = () =&gt; {
    const allWorkouts = Workout.getAllWorkouts();
    return allWorkouts;
};

const getOneWorkout = () =&gt; {
  return;
};

const createNewWorkout = (newWorkout) =&gt; {
  // *** AGGIUNGI ***
  const workoutToInsert = {
    ...newWorkout,
    id: uuid(),
    createdAt: new Date().toLocaleString("en-US", { timeZone: "UTC" }),
    updatedAt: new Date().toLocaleString("en-US", { timeZone: "UTC" }),
  };
  // *** AGGIUNGI ***
  const createdWorkout = Workout.createNewWorkout(workoutToInsert);
  return createdWorkout;
};

const updateOneWorkout = () =&gt; {
  return;
};

const deleteOneWorkout = () =&gt; {
  return;
};

module.exports = {
  getAllWorkouts,
  getOneWorkout,
  createNewWorkout,
  updateOneWorkout,
  deleteOneWorkout,
};
</code></pre><p>Wow! Divertente, non è vero? Ora puoi usare il tuo client HTTP e inviare la richiesta POST nuovamente, dovresti ricevere il nuovo allenamento creato in formato &nbsp;JSON.</p><p>Se provi ad aggiungere lo stesso allenamento nuovamente, riceverai un codice di stato 201, senza che l'allenamento venga inserito.</p><p>Questo significa che il nostro metodo di database per ora cancella gli inserimenti e non ritorna nulla. Infatti la nostra istruzione if verifica se un allenamento con lo stesso nome si trova già nel database. Per ora va bene così, gestiremo questa casistica nella nostra prossima miglior pratica!</p><p>Ora nel tuo browser invia una richiesta GET a <strong><strong>localhost:3000/api/v1/workouts</strong></strong> per ottenere tutti gli allenamenti. Dovresti vedere che il nostro allenamento è stato inserito e salvato con successo:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2022/04/Bildschirmfoto-2022-04-30-um-11.57.23.png" class="kg-image" alt="Bildschirmfoto-2022-04-30-um-11.57.23" width="600" height="400" loading="lazy"></figure><p>Puoi implementare gli altri metodi in autonomia oppure puoi semplicemente copiare le mia implementazioni.</p><p>Per primo il controller (puoi copiare semplicemente tutto il contenuto):</p><pre><code class="language-javascript">// In src/controllers/workoutController.js
const workoutService = require("../services/workoutService");

const getAllWorkouts = (req, res) =&gt; {
  const allWorkouts = workoutService.getAllWorkouts();
  res.send({ status: "OK", data: allWorkouts });
};

const getOneWorkout = (req, res) =&gt; {
  const {
    params: { workoutId },
  } = req;
  if (!workoutId) {
    return;
  }
  const workout = workoutService.getOneWorkout(workoutId);
  res.send({ status: "OK", data: workout });
};

const createNewWorkout = (req, res) =&gt; {
  const { body } = req;
  if (
    !body.name ||
    !body.mode ||
    !body.equipment ||
    !body.exercises ||
    !body.trainerTips
  ) {
    return;
  }
  const newWorkout = {
    name: body.name,
    mode: body.mode,
    equipment: body.equipment,
    exercises: body.exercises,
    trainerTips: body.trainerTips,
  };
  const createdWorkout = workoutService.createNewWorkout(newWorkout);
  res.status(201).send({ status: "OK", data: createdWorkout });
};

const updateOneWorkout = (req, res) =&gt; {
  const {
    body,
    params: { workoutId },
  } = req;
  if (!workoutId) {
    return;
  }
  const updatedWorkout = workoutService.updateOneWorkout(workoutId, body);
  res.send({ status: "OK", data: updatedWorkout });
};

const deleteOneWorkout = (req, res) =&gt; {
  const {
    params: { workoutId },
  } = req;
  if (!workoutId) {
    return;
  }
  workoutService.deleteOneWorkout(workoutId);
  res.status(204).send({ status: "OK" });
};

module.exports = {
  getAllWorkouts,
  getOneWorkout,
  createNewWorkout,
  updateOneWorkout,
  deleteOneWorkout,
};
</code></pre><p>Quindi il servizio (puoi copiare semplicemente tutto il contenuto):</p><pre><code class="language-javascript">// In src/services/workoutServices.js
const { v4: uuid } = require("uuid");
const Workout = require("../database/Workout");

const getAllWorkouts = () =&gt; {
  const allWorkouts = Workout.getAllWorkouts();
  return allWorkouts;
};

const getOneWorkout = (workoutId) =&gt; {
  const workout = Workout.getOneWorkout(workoutId);
  return workout;
};

const createNewWorkout = (newWorkout) =&gt; {
  const workoutToInsert = {
    ...newWorkout,
    id: uuid(),
    createdAt: new Date().toLocaleString("en-US", { timeZone: "UTC" }),
    updatedAt: new Date().toLocaleString("en-US", { timeZone: "UTC" }),
  };
  const createdWorkout = Workout.createNewWorkout(workoutToInsert);
  return createdWorkout;
};

const updateOneWorkout = (workoutId, changes) =&gt; {
  const updatedWorkout = Workout.updateOneWorkout(workoutId, changes);
  return updatedWorkout;
};

const deleteOneWorkout = (workoutId) =&gt; {
  Workout.deleteOneWorkout(workoutId);
};

module.exports = {
  getAllWorkouts,
  getOneWorkout,
  createNewWorkout,
  updateOneWorkout,
  deleteOneWorkout,
};
</code></pre><p>Infine implementiamo i nostri metodi di database all'interno del livello di accesso dati (puoi copiare semplicemente tutto il contenuto):</p><pre><code class="language-javascript">// In src/database/Workout.js
const DB = require("./db.json");
const { saveToDatabase } = require("./utils");

const getAllWorkouts = () =&gt; {
  return DB.workouts;
};

const getOneWorkout = (workoutId) =&gt; {
  const workout = DB.workouts.find((workout) =&gt; workout.id === workoutId);
  if (!workout) {
    return;
  }
  return workout;
};

const createNewWorkout = (newWorkout) =&gt; {
  const isAlreadyAdded =
    DB.workouts.findIndex((workout) =&gt; workout.name === newWorkout.name) &gt; -1;
  if (isAlreadyAdded) {
    return;
  }
  DB.workouts.push(newWorkout);
  saveToDatabase(DB);
  return newWorkout;
};

const updateOneWorkout = (workoutId, changes) =&gt; {
  const indexForUpdate = DB.workouts.findIndex(
    (workout) =&gt; workout.id === workoutId
  );
  if (indexForUpdate === -1) {
    return;
  }
  const updatedWorkout = {
    ...DB.workouts[indexForUpdate],
    ...changes,
    updatedAt: new Date().toLocaleString("en-US", { timeZone: "UTC" }),
  };
  DB.workouts[indexForUpdate] = updatedWorkout;
  saveToDatabase(DB);
  return updatedWorkout;
};

const deleteOneWorkout = (workoutId) =&gt; {
  const indexForDeletion = DB.workouts.findIndex(
    (workout) =&gt; workout.id === workoutId
  );
  if (indexForDeletion === -1) {
    return;
  }
  DB.workouts.splice(indexForDeletion, 1);
  saveToDatabase(DB);
};

module.exports = {
  getAllWorkouts,
  createNewWorkout,
  getOneWorkout,
  updateOneWorkout,
  deleteOneWorkout,
};
</code></pre><p>Grande! Ora passiamo alla prossima miglior pratica e vediamo come possiamo gestire gli errori in modo appropriato.</p><h3 id="rispondere-con-codici-di-errore-standard-http"><strong><strong>R</strong>i<strong>spond</strong>ere con Codici di Errore Standard HTTP</strong></h3><p>Siamo già arrivati piuttosto lontano, ma non abbiamo ancora finito. La nostra API ha ora la capacità di gestire operazioni CRUD di base con conservazione dati. Grande, ma non l'ideale tuttavia.</p><p>Come mai? Fammi spiegare.</p><p>In un mondo perfetto va tutto liscio senza alcun errore. Ma come probabilmente saprai nel modo reale si possono verificare molti errori, da una prospettiva sia umana che tecnica.</p><p>Probabilmente potresti conoscere quella strana sensazione quando le cose funzionano bene fin dall'inizio senza errori. Questo è fantastico e divertente, ma come sviluppatori siamo più abituati a cose che non funzionano correttamente. 😁</p><p>Lo stesso vale per la nostra API. Dovremmo gestire quelle situazioni nelle quali le cose potrebbero andare male oppure generare un errore. Questo contribuirà a rendere la nostra API più robusta.</p><p>Quando qualcosa non va (sia nella richiesta che all'interno della nostra API) restituiamo un codice di errore HTTP. Ho visto e usato API che ritornavano sempre il codice di errore 400 quando una richiesta conteneva errori senza alcun messaggio specifico sul PERCHÉ &nbsp;questo errore si era verificato o quale fosse. Pertanto il debug diventava problematico.</p><p>Ecco la ragione per la quale è sempre buona pratica ritornare il codice di errore HTTP appropriato per ciascun caso. Questo aiuta l'utilizzatore o lo sviluppatore che ha creato l'API a identificare il problema più facilmente.</p><p>Per migliorare l'esperienza possiamo anche inviare un veloce messaggio di errore assieme al codice dell'errore con la risposta. Ma come ho scritto nell'introduzione, questo non è sempre saggio e dovrebbe essere deciso dal programmatore stesso.</p><p>Per esempio, ci si dovrebbe pensare bene prima di ritornare qualcosa come <strong><strong>"</strong>Questo username ha già effettuato il login<strong>"</strong></strong> in quanto si forniscono informazioni sui tuoi utenti che andrebbero tenute nascoste.</p><p>Nella nostra API Crossfit esaminiamo la creazione di un endpoint e vediamo quali errori potrebbero verificarsi e come gestirli. Alla fine di questa sezione troverai nuovamente l'implementazione completa per gli altri endpoint.</p><p>Iniziamo con il metodo <code>createNewWorkout</code> all'interno del controller:</p><pre><code class="language-javascript">// In src/controllers/workoutController.js
...

const createNewWorkout = (req, res) =&gt; {
  const { body } = req;
  if (
    !body.name ||
    !body.mode ||
    !body.equipment ||
    !body.exercises ||
    !body.trainerTips
  ) {
    return;
  }
  const newWorkout = {
    name: body.name,
    mode: body.mode,
    equipment: body.equipment,
    exercises: body.exercises,
    trainerTips: body.trainerTips,
  };
  const createdWorkout = workoutService.createNewWorkout(newWorkout);
  res.status(201).send({ status: "OK", data: createdWorkout });
};

...</code></pre><p>Abbiamo già intercettato il caso nel quale il corpo della richiesta non è composto correttamente e mancano chiavi che sono richieste.</p><p>Questo sarebbe un buon esempio per ritornare un errore HTTP 400 con un relativo messaggio di errore che (tradotto - n.d.t.) dice: "Una delle seguenti chiavi è mancante o vuota nel corpo della richiesta: 'name', 'mode', 'equipment', 'exercises', 'trainerTips".</p><pre><code class="language-javascript">// In src/controllers/workoutController.js
...

const createNewWorkout = (req, res) =&gt; {
  const { body } = req;
  if (
    !body.name ||
    !body.mode ||
    !body.equipment ||
    !body.exercises ||
    !body.trainerTips
  ) {
    res
      .status(400)
      .send({
        status: "FAILED",
        data: {
          error:
            "One of the following key is missing or is empty in request body: 'name', 'mode', 'equipment', 'exercises', 'trainerTips'",
        },
      });
    return;
  }
  const newWorkout = {
    name: body.name,
    mode: body.mode,
    equipment: body.equipment,
    exercises: body.exercises,
    trainerTips: body.trainerTips,
  };
  const createdWorkout = workoutService.createNewWorkout(newWorkout);
  res.status(201).send({ status: "OK", data: createdWorkout });
};

...</code></pre><p>Se cerchi di aggiungere un nuovo allenamento ma dimentichi di passare la proprietà "mode" nel corpo della richiesta, dovresti vedere un messaggio di errore assieme al codice di errore HTTP 400.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2022/04/Bildschirmfoto-2022-04-30-um-15.17.21.png" class="kg-image" alt="Bildschirmfoto-2022-04-30-um-15.17.21" width="600" height="400" loading="lazy"></figure><p>Uno sviluppatore che utilizza l'API è ora meglio informato su cosa cercare. Saprà immediatamente di dover cercare all'interno del corpo della richiesta per verificare se non sia stata fornita una delle proprietà richieste.</p><p>Lasciare questo messaggio di errore più generico per tutte le proprietà per ora va bene. In genere dovresti usare un validatore di schema per gestire questo.</p><p>Andiamo a un livello più profondo nel nostro servizio workout e vediamo quali potenziali errori si potrebbero verificare.</p><pre><code class="language-javascript">// In src/services/workoutService.js
...

const createNewWorkout = (newWorkout) =&gt; {
  const workoutToInsert = {
    ...newWorkout,
    id: uuid(),
    createdAt: new Date().toLocaleString("en-US", { timeZone: "UTC" }),
    updatedAt: new Date().toLocaleString("en-US", { timeZone: "UTC" }),
  };
  const createdWorkout = Workout.createNewWorkout(workoutToInsert);
  return createdWorkout;
};

...</code></pre><p>Una cosa che potrebbe andare male è l'inserimento nel database con il metodo <strong><strong><code>Workout.createNewWorkout()</code>. </strong></strong>Mi piace incapsulare questa parte di codice in un blocco &nbsp;try/catch per intercettare l'errore quando si verifica.</p><pre><code class="language-javascript">// In src/services/workoutService.js
...

const createNewWorkout = (newWorkout) =&gt; {
  const workoutToInsert = {
    ...newWorkout,
    id: uuid(),
    createdAt: new Date().toLocaleString("en-US", { timeZone: "UTC" }),
    updatedAt: new Date().toLocaleString("en-US", { timeZone: "UTC" }),
  };
  try {
    const createdWorkout = Workout.createNewWorkout(workoutToInsert);
    return createdWorkout;
  } catch (error) {
    throw error;
  }
};

...</code></pre><p>Ogni errore che si verifica all'interno del metodo <code>Workout.createNewWorkout()</code> verrà catturato all'interno del blocco catch. Lo reiteriamo semplicemente, in modo da poter modellare le nostre risposte successivamente all'interno del nostro controller.</p><p>Definiamo i nostri errori in Workout.js:</p><pre><code class="language-javascript">// In src/database/Workout.js
...

const createNewWorkout = (newWorkout) =&gt; {
  const isAlreadyAdded =
    DB.workouts.findIndex((workout) =&gt; workout.name === newWorkout.name) &gt; -1;
  if (isAlreadyAdded) {
    throw {
      status: 400,
      message: `Workout with the name '${newWorkout.name}' already exists`,
    };
  }
  try {
    DB.workouts.push(newWorkout);
    saveToDatabase(DB);
    return newWorkout;
  } catch (error) {
    throw { status: 500, message: error?.message || error };
  }
};

...</code></pre><p>Come puoi vedere, un errore consiste di due parti, uno stato e un messaggio. Sto usando semplicemente la parola chiave <code><strong>throw</strong></code><strong> </strong>per ritornare una struttura dati diversa da una stringa, che è richiesta se usi <strong><strong><code>throw new Error()</code></strong></strong>.</p><p>Un piccolo svantaggio nell'inviare l'errore con <code>throw</code> è che non si ha uno <a href="https://it.wikipedia.org/wiki/Stack_trace">stack trace</a>. Normalmente questa generazione dell'errore dovrebbe essere gestita da una libreria di terze parti di nostra scelta (per esempio Mongoose se usi un database MongoDB). Per lo scopo di questo tutorial questo dovrebbe essere sufficiente.</p><p>Ora siamo in grado di generare e catturare gli errori nel servizio e nel livello di accesso dati. Possiamo passare ora al controller, catturare anche qui gli errori e rispondere di conseguenza.</p><pre><code class="language-javascript">// In src/controllers/workoutController.js
...

const createNewWorkout = (req, res) =&gt; {
  const { body } = req;
  if (
    !body.name ||
    !body.mode ||
    !body.equipment ||
    !body.exercises ||
    !body.trainerTips
  ) {
    res
      .status(400)
      .send({
        status: "FAILED",
        data: {
          error:
            "One of the following keys is missing or is empty in request body: 'name', 'mode', 'equipment', 'exercises', 'trainerTips'",
        },
      });
    return;
  }
  const newWorkout = {
    name: body.name,
    mode: body.mode,
    equipment: body.equipment,
    exercises: body.exercises,
    trainerTips: body.trainerTips,
  };
  // *** ADD ***
  try {
    const createdWorkout = workoutService.createNewWorkout(newWorkout);
    res.status(201).send({ status: "OK", data: createdWorkout });
  } catch (error) {
    res
      .status(error?.status || 500)
      .send({ status: "FAILED", data: { error: error?.message || error } });
  }
};

...</code></pre><p>Puoi fare un test aggiungendo un allenamento con lo stesso nome due volte oppure non fornire una proprietà richiesta all'interno del corpo della tua richiesta. Dovresti ricevere i codici di errore HTTP corrispondenti, oltre ai messaggi di errore.</p><p>Per ricapitolare e passare al prossimo suggerimento, puoi copiare l'intera implementazione dei metodi nei seguenti file oppure puoi provare da solo:</p><pre><code class="language-javascript">// In src/controllers/workoutController.js
const workoutService = require("../services/workoutService");

const getAllWorkouts = (req, res) =&gt; {
  try {
    const allWorkouts = workoutService.getAllWorkouts();
    res.send({ status: "OK", data: allWorkouts });
  } catch (error) {
    res
      .status(error?.status || 500)
      .send({ status: "FAILED", data: { error: error?.message || error } });
  }
};

const getOneWorkout = (req, res) =&gt; {
  const {
    params: { workoutId },
  } = req;
  if (!workoutId) {
    res
      .status(400)
      .send({
        status: "FAILED",
        data: { error: "Parameter ':workoutId' can not be empty" },
      });
  }
  try {
    const workout = workoutService.getOneWorkout(workoutId);
    res.send({ status: "OK", data: workout });
  } catch (error) {
    res
      .status(error?.status || 500)
      .send({ status: "FAILED", data: { error: error?.message || error } });
  }
};

const createNewWorkout = (req, res) =&gt; {
  const { body } = req;
  if (
    !body.name ||
    !body.mode ||
    !body.equipment ||
    !body.exercises ||
    !body.trainerTips
  ) {
    res
      .status(400)
      .send({
        status: "FAILED",
        data: {
          error:
            "One of the following keys is missing or is empty in request body: 'name', 'mode', 'equipment', 'exercises', 'trainerTips'",
        },
      });
    return;
  }
  const newWorkout = {
    name: body.name,
    mode: body.mode,
    equipment: body.equipment,
    exercises: body.exercises,
    trainerTips: body.trainerTips,
  };
  try {
    const createdWorkout = workoutService.createNewWorkout(newWorkout);
    res.status(201).send({ status: "OK", data: createdWorkout });
  } catch (error) {
    res
      .status(error?.status || 500)
      .send({ status: "FAILED", data: { error: error?.message || error } });
  }
};

const updateOneWorkout = (req, res) =&gt; {
  const {
    body,
    params: { workoutId },
  } = req;
  if (!workoutId) {
    res
      .status(400)
      .send({
        status: "FAILED",
        data: { error: "Parameter ':workoutId' can not be empty" },
      });
  }
  try {
    const updatedWorkout = workoutService.updateOneWorkout(workoutId, body);
    res.send({ status: "OK", data: updatedWorkout });
  } catch (error) {
    res
      .status(error?.status || 500)
      .send({ status: "FAILED", data: { error: error?.message || error } });
  }
};

const deleteOneWorkout = (req, res) =&gt; {
  const {
    params: { workoutId },
  } = req;
  if (!workoutId) {
    res
      .status(400)
      .send({
        status: "FAILED",
        data: { error: "Parameter ':workoutId' can not be empty" },
      });
  }
  try {
    workoutService.deleteOneWorkout(workoutId);
    res.status(204).send({ status: "OK" });
  } catch (error) {
    res
      .status(error?.status || 500)
      .send({ status: "FAILED", data: { error: error?.message || error } });
  }
};

module.exports = {
  getAllWorkouts,
  getOneWorkout,
  createNewWorkout,
  updateOneWorkout,
  deleteOneWorkout,
  getRecordsForWorkout,
};
</code></pre><pre><code class="language-javascript">// In src/services/workoutService.js
const { v4: uuid } = require("uuid");
const Workout = require("../database/Workout");

const getAllWorkouts = () =&gt; {
  try {
    const allWorkouts = Workout.getAllWorkouts();
    return allWorkouts;
  } catch (error) {
    throw error;
  }
};

const getOneWorkout = (workoutId) =&gt; {
  try {
    const workout = Workout.getOneWorkout(workoutId);
    return workout;
  } catch (error) {
    throw error;
  }
};

const createNewWorkout = (newWorkout) =&gt; {
  const workoutToInsert = {
    ...newWorkout,
    id: uuid(),
    createdAt: new Date().toLocaleString("en-US", { timeZone: "UTC" }),
    updatedAt: new Date().toLocaleString("en-US", { timeZone: "UTC" }),
  };
  try {
    const createdWorkout = Workout.createNewWorkout(workoutToInsert);
    return createdWorkout;
  } catch (error) {
    throw error;
  }
};

const updateOneWorkout = (workoutId, changes) =&gt; {
  try {
    const updatedWorkout = Workout.updateOneWorkout(workoutId, changes);
    return updatedWorkout;
  } catch (error) {
    throw error;
  }
};

const deleteOneWorkout = (workoutId) =&gt; {
  try {
    Workout.deleteOneWorkout(workoutId);
  } catch (error) {
    throw error;
  }
};

module.exports = {
  getAllWorkouts,
  getOneWorkout,
  createNewWorkout,
  updateOneWorkout,
  deleteOneWorkout,
};
</code></pre><pre><code class="language-javascript">// In src/database/Workout.js
const DB = require("./db.json");
const { saveToDatabase } = require("./utils");

const getAllWorkouts = () =&gt; {
  try {
    return DB.workouts;
  } catch (error) {
    throw { status: 500, message: error };
  }
};

const getOneWorkout = (workoutId) =&gt; {
  try {
    const workout = DB.workouts.find((workout) =&gt; workout.id === workoutId);
    if (!workout) {
      throw {
        status: 400,
        message: `Can't find workout with the id '${workoutId}'`,
      };
    }
    return workout;
  } catch (error) {
    throw { status: error?.status || 500, message: error?.message || error };
  }
};

const createNewWorkout = (newWorkout) =&gt; {
  try {
    const isAlreadyAdded =
      DB.workouts.findIndex((workout) =&gt; workout.name === newWorkout.name) &gt; -1;
    if (isAlreadyAdded) {
      throw {
        status: 400,
        message: `Workout with the name '${newWorkout.name}' already exists`,
      };
    }
    DB.workouts.push(newWorkout);
    saveToDatabase(DB);
    return newWorkout;
  } catch (error) {
    throw { status: error?.status || 500, message: error?.message || error };
  }
};

const updateOneWorkout = (workoutId, changes) =&gt; {
  try {
    const isAlreadyAdded =
      DB.workouts.findIndex((workout) =&gt; workout.name === changes.name) &gt; -1;
    if (isAlreadyAdded) {
      throw {
        status: 400,
        message: `Workout with the name '${changes.name}' already exists`,
      };
    }
    const indexForUpdate = DB.workouts.findIndex(
      (workout) =&gt; workout.id === workoutId
    );
    if (indexForUpdate === -1) {
      throw {
        status: 400,
        message: `Can't find workout with the id '${workoutId}'`,
      };
    }
    const updatedWorkout = {
      ...DB.workouts[indexForUpdate],
      ...changes,
      updatedAt: new Date().toLocaleString("en-US", { timeZone: "UTC" }),
    };
    DB.workouts[indexForUpdate] = updatedWorkout;
    saveToDatabase(DB);
    return updatedWorkout;
  } catch (error) {
    throw { status: error?.status || 500, message: error?.message || error };
  }
};

const deleteOneWorkout = (workoutId) =&gt; {
  try {
    const indexForDeletion = DB.workouts.findIndex(
      (workout) =&gt; workout.id === workoutId
    );
    if (indexForDeletion === -1) {
      throw {
        status: 400,
        message: `Can't find workout with the id '${workoutId}'`,
      };
    }
    DB.workouts.splice(indexForDeletion, 1);
    saveToDatabase(DB);
  } catch (error) {
    throw { status: error?.status || 500, message: error?.message || error };
  }
};

module.exports = {
  getAllWorkouts,
  createNewWorkout,
  getOneWorkout,
  updateOneWorkout,
  deleteOneWorkout,
};
</code></pre><h3 id="evitare-verbi-nei-nomi-degli-endpoint">Evitare verbi nei nomi degli endpoint</h3><p>Non ha molto senso usare verbi all'interno degli endpoint, in effetti è piuttosto inutile. Generalmente ogni URL dovrebbe puntare verso una risorsa (ricorda l'esempio della scatola fatto in precedenza). Niente di più e niente di meno.</p><p>Usare un verbo all'interno di un URL mostra un certo comportamento che la risorsa stessa non può avere.</p><p>Abbiamo già implementato gli endpoint correttamente senza usare verbi all'interno degli URL, tuttavia diamo un'occhiata a come sarebbero i nostri URL se avessimo usato i verbi.</p><pre><code class="language-javascript">// Implementazione corrente (senza verbi)
GET "/api/v1/workouts" 
GET "/api/v1/workouts/:workoutId" 
POST "/api/v1/workouts" 
PATCH "/api/v1/workouts/:workoutId" 
DELETE "/api/v1/workouts/:workoutId"  

// Implementazione usando verbi 
GET "/api/v1/getAllWorkouts" 
GET "/api/v1/getWorkoutById/:workoutId" 
CREATE "/api/v1/createWorkout" 
PATCH "/api/v1/updateWorkout/:workoutId" 
DELETE "/api/v1/deleteWorkout/:workoutId"</code></pre><p>Noti la differenza? Avere un URL completamente differente per ogni comportamento può generare confusione e inutile complessità piuttosto in fretta.</p><p>Immagina di avere 300 endpoint diversi. Usare un URL separato per ciascuno potrebbe essere un sovraccarico (compresa la documentazione) infernale.</p><p>Un'altra ragione che mi piacerebbe evidenziare per non usare verbi è che i verbi HTTP stessi indicano già l'azione (in lingua inglese - n.d.t.).</p><p>Cose come <strong><strong>"GET /api/v1/getAllWorkouts"</strong></strong> o <strong><strong>"DELETE api/v1/deleteWorkout/workoutId"</strong></strong> non sono necessarie.</p><p>Quando osservi la nostra implementazione corrente, diventa sempre più pulita in quanto stiamo usando solo due URL diversi e il comportamento effettivo è gestito tramite il verbo HTTP e il corrispondente <a href="https://it.wikipedia.org/wiki/Carico_utile_(informatica)">payload</a> della richiesta.</p><p>Ho sempre immaginato che il verbo HTTP descriva l'azione (quello che vorrei fare) e l'URL stesso (che punta verso una risorsa) l'obiettivo. <strong><strong>"GET /api/v1/workouts"</strong></strong> è anche più fluido come linguaggio umano (in inglese, naturalmente - n.d.t.).</p><!--kg-card-begin: markdown--><p><a name="raggruppare-risorse-associate"></a></p><h3>Raggruppare assieme le risorse (annidamento logico)</h3><p></p>
<!--kg-card-end: markdown--><p>Quando progetti la tua API, potrebbero esserci casi in cui disponi di risorse associate ad altre. È buona pratica raggrupparle in un unico endpoint e annidarle correttamente.</p><p>Considera che, nella nostra API, abbiamo anche un elenco di membri che si sono iscritti al nostro &nbsp;CrossFit box ("box" è il nome per una palestra CrossFit). Per motivare i nostri iscritti tracciamo i risultati complessivi dei box per ogni allenamento.</p><p>Ad esempio, c'è un allenamento in cui devi eseguire un certo ordine di esercizi il più rapidamente possibile. Registriamo i tempi per tutti i membri per avere un elenco del tempo impiegato per ogni membro che ha completato questo allenamento.</p><p>Ora, al frontend serve un endpoint che risponda con tutti i risultati per uno specifico allenamento affinché possa essere visualizzato nell'interfaccia utente.</p><p>Gli allenamenti, i membri e i risultati sono conservati in posti diversi nel database. Quindi quello che ci serve è una scatola (i risultati - records) all'interno di un'altra scatola (gli allenamenti - workout), giusto?</p><p>L'URI per quell'endpoint sarà <strong><strong>/api/v1/workouts/:workoutId/records</strong></strong>. Questa è una buona pratica che consente un annidamento logico dell'URL. L'URL stesso non deve necessariamente rispecchiare la struttura del database.</p><p>Iniziamo l'implementazione dell'endpoint.</p><p>Prima aggiungiamo una nuova tabella nel file &nbsp;<code>db.json</code> chiamata "members" (membri). &nbsp;La piazziamo sotto "workouts" (allenamenti).</p><pre><code class="language-json">{
  "workouts": [ ...
  ],
  "members": [
    {
      "id": "12a410bc-849f-4e7e-bfc8-4ef283ee4b19",
      "name": "Jason Miller",
      "gender": "male",
      "dateOfBirth": "23/04/1990",
      "email": "jason@mail.com",
      "password": "666349420ec497c1dc890c45179d44fb13220239325172af02d1fb6635922956"
    },
    {
      "id": "2b9130d4-47a7-4085-800e-0144f6a46059",
      "name": "Tiffany Brookston",
      "gender": "female",
      "dateOfBirth": "09/06/1996",
      "email": "tiffy@mail.com",
      "password": "8a1ea5669b749354110dcba3fac5546c16e6d0f73a37f35a84f6b0d7b3c22fcc"
    },
    {
      "id": "11817fb1-03a1-4b4a-8d27-854ac893cf41",
      "name": "Catrin Stevenson",
      "gender": "female",
      "dateOfBirth": "17/08/2001",
      "email": "catrin@mail.com",
      "password": "18eb2d6c5373c94c6d5d707650d02c3c06f33fac557c9cfb8cb1ee625a649ff3"
    },
    {
      "id": "6a89217b-7c28-4219-bd7f-af119c314159",
      "name": "Greg Bronson",
      "gender": "male",
      "dateOfBirth": "08/04/1993",
      "email": "greg@mail.com",
      "password": "a6dcde7eceb689142f21a1e30b5fdb868ec4cd25d5537d67ac7e8c7816b0e862"
    }
  ]
}</code></pre><p>Prima che tu me lo chieda, sì, alle password è stato applicato un hash. 😉</p><p>Successivamente aggiungiamo alcuni &nbsp;risultati ("records") sotto "members".</p><pre><code class="language-json">{
  "workouts": [ ...
  ],
  "members": [ ...
  ],
  "records": [
    {
      "id": "ad75d475-ac57-44f4-a02a-8f6def58ff56",
      "workout": "4a3d9aaa-608c-49a7-a004-66305ad4ab50",
      "record": "160 reps"
    },
    {
      "id": "0bff586f-2017-4526-9e52-fe3ea46d55ab",
      "workout": "d8be2362-7b68-4ea4-a1f6-03f8bc4eede7",
      "record": "7:23 minutes"
    },
    {
      "id": "365cc0bb-ba8f-41d3-bf82-83d041d38b82",
      "workout": "a24d2618-01d1-4682-9288-8de1343e53c7",
      "record": "358 reps"
    },
    {
      "id": "62251cfe-fdb6-4fa6-9a2d-c21be93ac78d",
      "workout": "4a3d9aaa-608c-49a7-a004-66305ad4ab50",
      "record": "145 reps"
    }
  ],
}</code></pre><p>Per essere sicuro che tu abbia gli stessi allenamenti con gli stessi miei identificativi, copia anche la sezione "workouts":</p><pre><code class="language-json">{
  "workouts": [
    {
      "id": "61dbae02-c147-4e28-863c-db7bd402b2d6",
      "name": "Tommy V",
      "mode": "For Time",
      "equipment": [
        "barbell",
        "rope"
      ],
      "exercises": [
        "21 thrusters",
        "12 rope climbs, 15 ft",
        "15 thrusters",
        "9 rope climbs, 15 ft",
        "9 thrusters",
        "6 rope climbs, 15 ft"
      ],
      "createdAt": "4/20/2022, 2:21:56 PM",
      "updatedAt": "4/20/2022, 2:21:56 PM",
      "trainerTips": [
        "Split the 21 thrusters as needed",
        "Try to do the 9 and 6 thrusters unbroken",
        "RX Weights: 115lb/75lb"
      ]
    },
    {
      "id": "4a3d9aaa-608c-49a7-a004-66305ad4ab50",
      "name": "Dead Push-Ups",
      "mode": "AMRAP 10",
      "equipment": [
        "barbell"
      ],
      "exercises": [
        "15 deadlifts",
        "15 hand-release push-ups"
      ],
      "createdAt": "1/25/2022, 1:15:44 PM",
      "updatedAt": "3/10/2022, 8:21:56 AM",
      "trainerTips": [
        "Deadlifts are meant to be light and fast",
        "Try to aim for unbroken sets",
        "RX Weights: 135lb/95lb"
      ]
    },
    {
      "id": "d8be2362-7b68-4ea4-a1f6-03f8bc4eede7",
      "name": "Heavy DT",
      "mode": "5 Rounds For Time",
      "equipment": [
        "barbell",
        "rope"
      ],
      "exercises": [
        "12 deadlifts",
        "9 hang power cleans",
        "6 push jerks"
      ],
      "createdAt": "11/20/2021, 5:39:07 PM",
      "updatedAt": "4/22/2022, 5:49:18 PM",
      "trainerTips": [
        "Aim for unbroken push jerks",
        "The first three rounds might feel terrible, but stick to it",
        "RX Weights: 205lb/145lb"
      ]
    },
    {
      "name": "Core Buster",
      "mode": "AMRAP 20",
      "equipment": [
        "rack",
        "barbell",
        "abmat"
      ],
      "exercises": [
        "15 toes to bars",
        "10 thrusters",
        "30 abmat sit-ups"
      ],
      "trainerTips": [
        "Split your toes to bars in two sets maximum",
        "Go unbroken on the thrusters",
        "Take the abmat sit-ups as a chance to normalize your breath"
      ],
      "id": "a24d2618-01d1-4682-9288-8de1343e53c7",
      "createdAt": "4/22/2022, 5:50:17 PM",
      "updatedAt": "4/22/2022, 5:50:17 PM"
    }
  ],
  "members": [ ...
  ],
  "records": [ ...
  ]
}</code></pre><p>Va bene, prendiamoci qualche minuto per pensare all'implementazione.</p><p>Abbiamo una risorsa chiamata "workouts" (gli allenamenti) da una parte e un'altra chiamata "records" (i risultati) dall'altra.</p><p>Per continuare nella nostra architettura sarebbe consigliabile creare un altro controller, un altro servizio e un'altra collezione di metodi di database che sono responsabili per i risultati.</p><p>È molto probabile che dobbiamo implementare gli endpoint CRUD anche per i risultati, perché anch'essi dovrebbero essere aggiunti, aggiornati o eliminati in futuro. Ma questo non sarà il compito principale per ora.</p><p>Avremo anche bisogno di un router per &nbsp;gestire le richieste specifiche per i risultati, ma non ne abbiamo bisogno al momento. Questa potrebbe essere una grande opportunità per te per implementare le operazioni CRUD per i record con le proprie rotte ed esercitarti un po'.</p><pre><code class="language-bash"># Crea il controller per i risultati
touch src/controllers/recordController.js 

# Crea il servizio per i risultati 
touch src/services/recordService.js 

# Crea i metodi del database per i risultati
touch src/database/Record.js</code></pre><p>Questo era facile. Passiamo oltre, partiamo dalla fine implementando i metodi del database.</p><pre><code class="language-javascript">// In src/database/Record.js
const DB = require("./db.json");

const getRecordForWorkout = (workoutId) =&gt; {
  try {
    const record = DB.records.filter((record) =&gt; record.workout === workoutId);
    if (!record) {
      throw {
        status: 400,
        message: `Can't find workout with the id '${workoutId}'`,
      };
    }
    return record;
  } catch (error) {
    throw { status: error?.status || 500, message: error?.message || error };
  }
};
module.exports = { getRecordForWorkout };
</code></pre><p>Piuttosto semplice, giusto? Filtriamo tutti i record che sono relativi all'allenamento il cui codice identificativo riceviamo come parametro di query.</p><p>Il prossimo è il servizio per i risultati:</p><pre><code class="language-javascript">// In src/services/recordService.js
const Record = require("../database/Record");

const getRecordForWorkout = (workoutId) =&gt; {
  try {
    const record = Record.getRecordForWorkout(workoutId);
    return record;
  } catch (error) {
    throw error;
  }
};
module.exports = { getRecordForWorkout };</code></pre><p>Anche qui niente di nuovo.</p><p>Ora siamo in grado di creare una nuova rotta nel router e dirigere la richiesta verso il servizio per i risultati.</p><pre><code class="language-javascript">// In src/v1/routes/workoutRoutes.js
const express = require("express");
const workoutController = require("../../controllers/workoutController");
// *** AGGIUNGI ***
const recordController = require("../../controllers/recordController");

const router = express.Router();

router.get("/", workoutController.getAllWorkouts);

router.get("/:workoutId", workoutController.getOneWorkout);

// *** AGGIUNGI ***
router.get("/:workoutId/records", recordController.getRecordForWorkout);

router.post("/", workoutController.createNewWorkout);

router.patch("/:workoutId", workoutController.updateOneWorkout);

router.delete("/:workoutId", workoutController.deleteOneWorkout);

module.exports = router;
</code></pre><p>Grande! Facciamo un test con il browser.</p><p>Per prima cosa recuperiamo tutti gli allenamenti per ricavare l'id dell'allenamento.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2022/04/Bildschirmfoto-2022-04-30-um-15.36.48.png" class="kg-image" alt="Bildschirmfoto-2022-04-30-um-15.36.48" width="600" height="400" loading="lazy"></figure><p>Vediamo se possiamo recuperare tutti i record per quell'id:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2022/04/Bildschirmfoto-2022-04-30-um-15.36.32.png" class="kg-image" alt="Bildschirmfoto-2022-04-30-um-15.36.32" width="600" height="400" loading="lazy"></figure><p>Come puoi vedere, l'annidamento logico ha senso se hai risorse che possono essere in relazione tra loro. In teoria puoi annidare in profondità a piacimento, ma nella pratica è meglio non superare i tre livelli di annidamento al massimo.</p><p>Se vuoi annidare più in profondità, potresti fare una piccola modifica al database. Ti mostro un piccolo esempio.</p><p>Immagina che al frontend serva anche un endpoint per ottenere informazioni relative a quale membro esattamente detenga il record corrente e vuole ricevere metadati al proposito.</p><p>Naturalmente potremmo implementare l'URI seguente:</p><pre><code class="language-javascript">GET /api/v1/workouts/:workoutId/records/members/:memberId</code></pre><p>L'endpoint ora diventa meno gestibile mano a mano che aggiungiamo livelli di annidamento. Pertanto è buona pratica inserire l'URI per ricevere informazioni su un dato membro direttamente nel record.</p><p>Considera quanto segue all'interno del database:</p><pre><code class="language-json">{
  "workouts": [ ...
  ],
  "members": [ ...
  ],
  "records": [ ... {
      "id": "ad75d475-ac57-44f4-a02a-8f6def58ff56",
      "workout": "4a3d9aaa-608c-49a7-a004-66305ad4ab50",
      "record": "160 reps",
      "memberId": "11817fb1-03a1-4b4a-8d27-854ac893cf41",
      "member": "/members/:memberId"
    },
  ]
}</code></pre><p>Come puoi vedere abbiamo aggiunto due proprietà: "memberId" e "member" per i nostri record all'interno del database. Questo presenta il grosso vantaggio che non dobbiamo annidare più in profondità il nostro endpoint esistente.</p><p>Il frontend deve semplicemente chiamare <strong><strong>GET /api/v1/workouts/:workoutId/records </strong></strong>per ricevere automaticamente tutti i risultati che sono relativi a questo allenamento.</p><p>Inoltre ottiene l'id del membro e l'endpoint per recuperare le informazioni su quel membro. Quindi, abbiamo evitato l'annidamento più profondo del nostro endpoint.</p><p>Naturalmente, questo funziona solo se siamo in grado di gestire le richieste a "/members/:memberId" 😁 Sembra un'ottima opportunità di formazione per te per implementare questa situazione!</p><h3 id="integrare-filtri-ordinamento-e-paginazione"><strong><strong>Integra</strong>r<strong>e </strong>filtri, ordinamento e paginazione</strong></h3><p>In questo momento siamo in grado di eseguire alcune operazioni con la nostra API. Questo è un grande progresso, ma c'è di più.</p><p>Durante le ultime sezioni ci siamo concentrati sul miglioramento della nostra esperienza di sviluppatore e su come sia possibile interagire con la nostra API. Ma le prestazioni complessive della nostra API sono un altro fattore chiave su cui dovremmo lavorare.</p><p>Ecco perché anche l'integrazione di filtri, ordinamento e paginazione è un fattore essenziale nella mia lista.</p><p>Immagina di avere 2.000 allenamenti, 450 record e 500 membri memorizzati nel nostro database. Quando chiamiamo il nostro endpoint per ottenere tutti gli allenamenti, non vogliamo inviare tutti i 2.000 &nbsp;contemporaneamente. Questa sarà una risposta molto lenta ovviamente, o farà crollare i nostri sistemi (forse con 200.000 😁).</p><p>Questo è il motivo per cui il filtro e la paginazione sono importanti. Il filtro, come dice già il nome, è utile perché ci consente di ottenere dati specifici da tutta la nostra collezione. Ad esempio tutti gli allenamenti che hanno la modalità "For Time".</p><p>La paginazione è un altro meccanismo per suddividere la nostra intera raccolta di allenamenti in più "pagine" in cui ogni pagina è composta solo da venti allenamenti, ad esempio. Questa tecnica ci aiuta ad assicurarci di non inviare più di venti allenamenti contemporaneamente con la nostra risposta al client.</p><p>L'ordinamento può essere un compito complesso. Quindi è più efficace farlo nella nostra API e inviare i dati ordinati al client.</p><p>Cominciamo con l'integrazione di alcuni meccanismi di filtro nella nostra API. Miglioreremo il nostro endpoint che invia tutti gli allenamenti accettando parametri per il filtro. Normalmente in una richiesta GET aggiungiamo i criteri di filtro come parametro di query.</p><p>Il nostro nuovo URI sarà simile a questo, quando vorremmo ottenere solo gli allenamenti che sono nella modalità di "AMRAP" (As Many Rounds As Possible - tante ripetizioni quante possibile): <strong>/api/v1/workouts?mode=amrap</strong>.</p><p>Per renderlo più divertente dobbiamo aggiungere altri allenamenti. Incolla questi allenamenti nella tua raccolta "workouts" all'interno di db.json:</p><pre><code class="language-json">{
  "name": "Jumping (Not) Made Easy",
  "mode": "AMRAP 12",
  "equipment": [
    "jump rope"
  ],
  "exercises": [
    "10 burpees",
    "25 double-unders"
  ],
  "trainerTips": [
    "Scale to do 50 single-unders, if double-unders are too difficult"
  ],
  "id": "8f8318f8-b869-4e9d-bb78-88010193563a",
  "createdAt": "4/25/2022, 2:45:28 PM",
  "updatedAt": "4/25/2022, 2:45:28 PM"
},
{
  "name": "Burpee Meters",
  "mode": "3 Rounds For Time",
  "equipment": [
    "Row Erg"
  ],
  "exercises": [
    "Row 500 meters",
    "21 burpees",
    "Run 400 meters",
    "Rest 3 minutes"
  ],
  "trainerTips": [
    "Go hard",
    "Note your time after the first run",
    "Try to hold your pace"
  ],
  "id": "0a5948af-5185-4266-8c4b-818889657e9d",
  "createdAt": "4/25/2022, 2:48:53 PM",
  "updatedAt": "4/25/2022, 2:48:53 PM"
},
{
  "name": "Dumbbell Rower",
  "mode": "AMRAP 15",
  "equipment": [
    "Dumbbell"
  ],
  "exercises": [
    "15 dumbbell rows, left arm",
    "15 dumbbell rows, right arm",
    "50-ft handstand walk"
  ],
  "trainerTips": [
    "RX weights for women: 35-lb",
    "RX weights for men: 50-lb"
  ],
  "id": "3dc53bc8-27b8-4773-b85d-89f0a354d437",
  "createdAt": "4/25/2022, 2:56:03 PM",
  "updatedAt": "4/25/2022, 2:56:03 PM"
}</code></pre><p>Tutto quello che dobbiamo fare è accettare e gestire i parametri di query. Il nostro controller che gestisce gli allenamenti sarà il posto giusto da cui partire:</p><pre><code class="language-javascript">// In src/controllers/workoutController.js
...

const getAllWorkouts = (req, res) =&gt; {
  // *** ADD ***
  const { mode } = req.query;
  try {
    // *** ADD ***
    const allWorkouts = workoutService.getAllWorkouts({ mode });
    res.send({ status: "OK", data: allWorkouts });
  } catch (error) {
    res
      .status(error?.status || 500)
      .send({ status: "FAILED", data: { error: error?.message || error } });
  }
};

...</code></pre><p>Estraiamo &nbsp;"mode" dall'oggetto &nbsp;<code>req.query</code> e ne passiamo il valore a <code>workoutService.getAllWorkouts</code>. Questo valore sarà un oggetto che contiene tutti i parametri di filtro.</p><p>Uso la sintassi scorciatoia qui, per creare una chiave chiamata <code>mode</code> all'interno dell'oggetto, il cui valore è qualunque cosa sia associata a <code>req.query.mode</code>. Potrebbe essere sia un valore <em>truthy </em>oppure <code>undefined</code> se non esiste un parametro di query chiamato <code>mode</code>. Possiamo estendere questo oggetto con ulteriori parametri di filtro che vorremmo accettare.</p><p>Nel servizio workout, passalo al tuo metodo di database:</p><pre><code class="language-javascript">// In src/services/workoutService.js
...

const getAllWorkouts = (filterParams) =&gt; {
  try {
    // *** AGGIUNGI ***
    const allWorkouts = Workout.getAllWorkouts(filterParams);
    return allWorkouts;
  } catch (error) {
    throw error;
  }
};

...</code></pre><p>Ora possiamo usare il nostro metodo di database e applicare il filtro:</p><pre><code class="language-javascript">// In src/database/Workout.js
...

const getAllWorkouts = (filterParams) =&gt; {
  try {
    let workouts = DB.workouts;
    if (filterParams.mode) {
      return DB.workouts.filter((workout) =&gt;
        workout.mode.toLowerCase().includes(filterParams.mode)
      );
    }
    // Altre istruzioni if vanno qui per gestire parametri differenti
    return workouts;
  } catch (error) {
    throw { status: 500, message: error };
  }
};

...</code></pre><p>Piuttosto semplice, giusto? Tutto quello che dobbiamo fare qui è verificare se abbiamo effettivamente un valore <em>truthy</em> per la chiave <code>mode</code> all'interno di <code>filterParams</code>. Se questo è vero, filtriamo tutti gli allenamenti che hanno lo stesso valore per <code>mode</code>, altrimenti non c'è un parametro di query con quel nome e ritorniamo tutti gli allenamenti in quanto non è necessario filtrarli.</p><p>Abbiamo dichiarato la variabile <code>workouts</code> con <code>let</code> poiché quando aggiungiamo ulteriori istruzioni if per diversi filtri possiamo sovrascrivere <code>workouts</code> e concatenare i filtri.</p><p>All'interno del tuo browser puoi visitare <strong>localhost:3000/api/v1/workouts?mode=amrap</strong> e riceverai tutti gli allenamenti nel database che hanno la proprietà "<code>mode</code>" uguale a &nbsp;"AMRAP":</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2022/04/Bildschirmfoto-2022-04-30-um-15.48.57.png" class="kg-image" alt="Bildschirmfoto-2022-04-30-um-15.48.57" width="600" height="400" loading="lazy"></figure><p>Se ometti il parametro di query, dovresti ottenere tutti gli allenamenti come prima. Puoi provare ancora aggiungendo "for%20time" come valore per il parametro <code>mode</code> (ricorda --&gt; "%20" significa "spazio") e ora dovresti ricevere tutti gli allenamenti che hanno il valore di <code>mode</code> uguale a "For Time", se esistono nel database.</p><p>Se digiti un valore che non è nel database, dovresti ricevere un array vuoto.</p><p>I parametri per ordinare e la paginazione seguono la stessa filosofia. Diamo un'occhiata ad alcune funzionalità che potremmo implementare:</p><ul><li>Ricevere tutti gli allenamenti che richiedono un barbell (bilanciere): <strong><strong>/api/v1/workouts?equipment=barbell</strong></strong></li><li>Ottenere solo 5 allenamenti: <strong><strong>/api/v1/workouts?length=5</strong></strong></li><li>Quando usiamo la paginazione ricevere la seconda pagina: <strong><strong>/api/v1/workouts?page=2</strong></strong></li><li>Ordinare gli allenamenti nella risposta in ordine discendente, per data di creazione: <strong><strong>/api/v1/workouts?sort=-createdAt</strong></strong></li><li>Puoi anche combinare i parametri, per ottenere gli ultimi 10 allenamenti aggiornati, per esempio: <strong><strong>/api/v1/workouts?sort=-updatedAt&amp;length=10</strong></strong></li></ul><h3 id="usare-cache-dei-dati-per-migliorare-le-prestazioni"><strong><strong>Us</strong>are cache dei dati per migliorare le prestazioni</strong></h3><p>Anche utilizzare una cache per i dati è una ottima pratica per migliorare l'esperienza complessiva e le prestazioni della nostra API.</p><p>Ha molto senso usare una cache dalla quale servire i dati, quando i dati provengono da una risorsa richiesta di frequente e/o quando l'interrogazione di quei dati dal database è un'operazione molto pesante e potrebbe richiedere più secondi.</p><p>Puoi conservare questo tipo di dati all'interno della tua cache e servirli da lì invece di interrogare ogni volta il database per ottenere i dati.</p><p>Una cosa importante che devi tenere a mente è che quando si servono dati da una cache, potrebbero essere diventati obsoleti. Quindi devi assicurarti che i dati all'interno della cache siano sempre aggiornati.</p><p>Ci sono molte soluzioni diverse a disposizione. Un esempio appropriato è usare <a href="https://www.npmjs.com/package/redis">redis</a> o il middleware di express <a href="https://www.npmjs.com/package/apicache">apicache</a>.</p><p>Preferirei usare apicache, ma se vuoi usare Redis, consiglio caldamente che tu dia un'occhiata alla loro ottima <a href="https://docs.redis.com/latest/rs/references/client_references/client_nodejs/">documentazione</a>.</p><p>Pensiamo un secondo a uno scenario nella nostra API nel quale una cache avrebbe senso. Penso che le richieste per ottenere tutti gli allenamenti potrebbero essere esaudite con efficacia all'interno della nostra cache.</p><p>Per prima cosa installiamo il nostro middleware:</p><pre><code class="language-bash">npm i apicache</code></pre><p>Ora dobbiamo importarlo nel router che gestisce gli allenamenti e configurarlo.</p><pre><code class="language-javascript">// In src/v1/routes/workoutRoutes.js
const express = require("express");
// *** AGGIUNGI ***
const apicache = require("apicache");
const workoutController = require("../../controllers/workoutController");
const recordController = require("../../controllers/recordController");

const router = express.Router();
// *** AGGIUNGI ***
const cache = apicache.middleware;

// *** AGGIUNGI ***
router.get("/", cache("2 minutes"), workoutController.getAllWorkouts);

router.get("/:workoutId", workoutController.getOneWorkout);

router.get("/:workoutId/records", recordController.getRecordForWorkout);

router.post("/", workoutController.createNewWorkout);

router.patch("/:workoutId", workoutController.updateOneWorkout);

router.delete("/:workoutId", workoutController.deleteOneWorkout);

module.exports = router;
</code></pre><p>È piuttosto semplice impostarla, vero? Possiamo definire una nuova cache chiamando <strong><strong><code>apicache.middleware</code> </strong></strong>e usarla come middleware all'interno della nostra rotta GET. Devi solo inserirla come parametro tra il percorso effettivo e il controller che gestisce gli allenamenti.</p><p>Lì dentro puoi definire per quanto tempo i tuoi dati dovrebbero rimanere nella cache. Per le esigenze di questo tutorial ho scelto due minuti. Il tempo dipende dalla velocità o dalla frequenza con cui cambiano i dati all'interno della cache.</p><p>Ora proviamo!</p><p>All'interno di Postman o di un altro client HTTP di tua scelta, definisci una nuova richiesta che ottiene tutti gli allenamenti. Fino ad ora l'ho fatto dal browser, ma vorrei meglio visualizzare il tempo di risposta per te. Ecco la ragione per la quale ora sto richiedendo la risorsa tramite Postman.</p><p>Eseguiamo la nostra richiesta per la prima volta:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2022/04/Bildschirmfoto-2022-04-26-um-15.36.46-1.png" class="kg-image" alt="Bildschirmfoto-2022-04-26-um-15.36.46-1" width="600" height="400" loading="lazy"></figure><p>Come puoi vedere la nostra API ha risposto in 22.93 ms. Una volta che la nostra cache viene nuovamente svuotata (dopo due minuti) deve essere riempita nuovamente. Questo succede con la nostra prima richiesta.</p><p>Quindi nel caso qui sopra, i dati NON sono stati serviti dalla nostra cache. Sono stati presi &nbsp;"normalmente" dal database e sono stati inseriti nella cache.</p><p>Ora con la nostra seconda richiesta riceviamo i dati con un tempo di risposta minore, in quanto i dati sono serviti direttamente dalla cache:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2022/04/Bildschirmfoto-2022-04-26-um-15.36.59-1.png" class="kg-image" alt="Bildschirmfoto-2022-04-26-um-15.36.59-1" width="600" height="400" loading="lazy"></figure><p>Siamo stati in grado di servire i dati in un terzo del tempo impiegato nella nostra richiesta precedente! Tutto grazie alla nostra cache.</p><p>Nel nostro esempio abbiamo usato la cache con una sola rotta, ma puoi anche utilizzarla su tutte le rotte con un'implementazione come questa:</p><pre><code class="language-javascript">// In src/index.js
const express = require("express");
const bodyParser = require("body-parser");
// *** AGGIUNGI ***
const apicache = require("apicache");
const v1WorkoutRouter = require("./v1/routes/workoutRoutes");

const app = express();
// *** AGGIUNGI ***
const cache = apicache.middleware;
const PORT = process.env.PORT || 3000;

app.use(bodyParser.json());
// *** AGGIUNGI ***
app.use(cache("2 minutes"));
app.use("/api/v1/workouts", v1WorkoutRouter);

app.listen(PORT, () =&gt; {
  console.log(`API is listening on port ${PORT}`);
});
</code></pre><p>C'è una cosa <strong><strong>important</strong>e</strong> che vorrei far notare qui per quanto riguarda la cache. Anche se sembra che ti risolva molti problemi, puoi anche portarne altri alla tua applicazione.</p><p>Alcune cose delle quali devi essere a conoscenza quando utilizzi una cache:</p><ul><li>devi sempre assicurarti che i dati all'interno della cache siano aggiornati in quanto tu non vuoi servire dati obsoleti</li><li>quando la prima richiesta viene elaborata e la cache sta per essere riempita e ci sono ulteriori richieste in arrivo, devi decidere se ritardarle e servire i dati dalla cache oppure se esse riceveranno i dati direttamente dal database come nella prima richiesta</li><li>è un altro componente da inserire all'interno della tua infrastruttura, se stai scegliendo una cache distribuita come Redis (quindi devi domandarti se ha veramente senso usarla)</li></ul><p>Ecco cosa faccio io in genere:</p><p>Vorrei partire il più semplice e pulito possibile con qualsiasi cosa stia creando. Lo stesso vale per le API.</p><p>Quando inizio la creazione di un'API e non ci sono ragioni particolari per usare una cache immediatamente, la tengo da parte e vedo cosa succede nel tempo. Se si verificano ragioni per le quali usare una cache, la implemento.</p><h3 id="buone-pratiche-di-sicurezza"><strong>Buone pratiche di sicurezza</strong></h3><p>Wow! Questo è stato un bel viaggio finora. Abbiamo toccato molti punti importanti e abbiamo esteso la nostra API di conseguenza.</p><p>Abbiamo parlato delle migliori pratiche per accrescere l'usabilità e le prestazioni della nostra API. Anche la sicurezza è un fattore chiave per l'API. Puoi creare l'API migliore, ma quando si tratta di una parte di software vulnerabile in esecuzione in un server diventa inutile e pericolosa.</p><p>La prima indispensabile misura di sicurezza da includere è l'uso di SSL/TLS in quanto al giorno d'oggi è lo standard per quanto riguarda la comunicazione su internet. È ancora più importante dove i dati privati vengono scambiati tra il client e l'API.</p><p>Se hai risorse che dovrebbero essere disponibili solo a utenti autenticati, dovresti proteggerle con una verifica di autenticazione.</p><p>In Express, per esempio, puoi implementarla come middleware come abbiamo fatto per la nostra cache per determinate rotte e verificare prima se la richiesta è autenticata per accedere a una risorsa.</p><p>Potrebbero esserci risorse o interazioni con la nostra API che non vogliamo vengano richieste da qualunque utente. In questo caso dovresti impostare un sistema di ruoli per i tuoi utenti. Pertanto devi aggiungere altra logica di verifica per quella rotta e validare se l'utente ha il privilegio di accedere alla risorsa.</p><p>I ruoli utenti avrebbero senso anche nel nostro caso d'uso quando vogliamo che solo utenti specifici (come gli allenatori) possano creare, aggiornare ed eliminare gli allenamenti e i risultati. La lettura può essere aperta a tutti (anche ai membri "normali").</p><p>Si può gestire la cosa tramite un altro middleware che usiamo per le rotte che vogliamo proteggere. Per esempio la nostra richiesta POST per <strong>/api/v1/workouts</strong> per creare un nuovo allenamento.</p><p>All'interno del primo middleware verificheremo se l'utente è autenticato. In caso positivo passeremo al middleware successivo, vale a dire la verifica del ruolo dell'utente. Se l'utente ha il ruolo appropriato per accedere alla risorsa la richiesta viene passata al controller corrispondente.</p><p>All'interno del gestore delle rotte il codice sarà come questo:</p><pre><code class="language-javascript">// In src/v1/routes/workoutRoutes.js
...

// Middleware personalizzato
const authenticate = require("../../middlewares/authenticate");
const authorize = require("../../middlewares/authorize");

router.post("/", authenticate, authorize, workoutController.createNewWorkout);

...</code></pre><p>Per ulteriori informazioni e migliori pratiche su questo argomento, suggerisco di leggere <a href="https://restfulapi.net/security-essentials/">questo articolo</a> (in lingua inglese).</p><h3 id="documentare-correttamente-la-tua-api"><strong><strong>Document</strong>are correttamente la tua<strong> API</strong></strong></h3><p>So che la documentazione non è esattamente l'attività preferita dagli sviluppatori, ma è una cosa necessaria da fare. Specialmente quando si parla di API.</p><p>Qualcuno dice che:</p><blockquote>"Un'API è tanto buona quanto la sua documentazione"</blockquote><p>Credo che ci sia molto di vero in questa affermazione in quanto se un'API non è ben documentata non può essere usata adeguatamente, pertanto diventa inutile. La documentazione aiuta anche a rendere la vita degli sviluppatori molto più facile.</p><p>Ricorda sempre che la documentazione in genere è la prima interazione che gli utilizzatori hanno con la tua API. Più velocemente gli utenti saranno in grado di capire la documentazione, più velocemente potranno usare la tua API.</p><p>Pertanto è nostro compito implementare una documentazione esauriente e precisa. Ci sono degli ottimi strumenti a disposizione che ci faciliteranno la vita.</p><p>Come in altri campi dell'informatica, c'è una sorta di standard per documentare le API chiamata <a href="https://swagger.io/specification/">OpenAPI Specification</a> (Specifiche OpenAPI).</p><p>Vediamo come possiamo creare una qualche documentazione che giustifichi quelle specifiche. Useremo i pacchetti <a href="https://www.npmjs.com/package/swagger-ui-express">swagger-ui-express</a> e <a href="https://www.npmjs.com/package/swagger-jsdoc">swagger-jsdoc</a> per farlo. In un attimo resterai stupito di quanto sia fantastico!</p><p>Innanzitutto, impostiamo la struttura base per la nostra documentazione. Poiché abbiamo pianificato di avere diverse versioni della nostra API, anche i documenti saranno leggermente diversi. Questo è il motivo per cui vorrei definire il nostro file swagger per lanciare la nostra documentazione all'interno della cartella della versione corrispondente.</p><pre><code class="language-bash"># Installa i pacchetti npm richiesti 
npm i swagger-jsdoc swagger-ui-express 

# Crea un nuovo file per impostare i documenti swagger 
touch src/v1/swagger.js</code></pre><pre><code class="language-javascript">// In src/v1/swagger.js
const swaggerJSDoc = require("swagger-jsdoc");
const swaggerUi = require("swagger-ui-express");

// Meta Informazioni base sulla nostra API
const options = {
  definition: {
    openapi: "3.0.0",
    info: { title: "Crossfit WOD API", version: "1.0.0" },
  },
  apis: ["./src/v1/routes/workoutRoutes.js", "./src/database/Workout.js"],
};

// Documentazione in formato JSON
const swaggerSpec = swaggerJSDoc(options);

// Funzione per impostare la nostra documentazione
const swaggerDocs = (app, port) =&gt; {
  // Gestore della rotta per visitare la documentazione
  app.use("/api/v1/docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec));
  // Rendiamo disponibile la nostra documentazione in formato JSON
  app.get("/api/v1/docs.json", (req, res) =&gt; {
    res.setHeader("Content-Type", "application/json");
    res.send(swaggerSpec);
  });
  console.log(
    `Version 1 Docs are available on http://localhost:${port}/api/v1/docs`
  );
};

module.exports = { swaggerDocs };
</code></pre><p>L'impostazione è piuttosto semplice. Abbiamo definito qualche metadato base per la nostra API, creata la documentazione in formato JSON e creata una funzione che rende disponibile la nostra documentazione.</p><p>Per controllare se tutto è a posto e in esecuzione, registriamo un semplice messaggio alla console che indica dove possiamo trovare la documentazione.</p><p>Questa sarà la funzione che useremo nel file radice (index.js) dove abbiamo creato il server Express per assicurarci che anche la documentazione sia caricata.</p><pre><code class="language-javascript">// In src/index.js
const express = require("express");
const bodyParser = require("body-parser");
const v1WorkoutRouter = require("./v1/routes/workoutRoutes");
// *** AGGIUNGI ***
const { swaggerDocs: V1SwaggerDocs } = require("./v1/swagger");

const app = express();
const PORT = process.env.PORT || 3000;

app.use(bodyParser.json());
app.use("/api/v1/workouts", v1WorkoutRouter);

app.listen(PORT, () =&gt; {
  console.log(`API is listening on port ${PORT}`);
  /// *** AGGIUNGI ***
  V1SwaggerDocs(app, PORT);
});
</code></pre><p>Ora dovresti vedere quanto segue all'interno del terminale dove è in esecuzione il tuo server di sviluppo:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/04/Bildschirmfoto-2022-04-28-um-20.23.51-1.png" class="kg-image" alt="Bildschirmfoto-2022-04-28-um-20.23.51-1" width="600" height="400" loading="lazy"></figure><p>Quando visiti &nbsp;<strong>localhost:3000/api/v1/docs </strong>nel browser, dovresti già vedere la nostra pagina di documentazione:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2022/04/Bildschirmfoto-2022-04-28-um-20.25.00-1.png" class="kg-image" alt="Bildschirmfoto-2022-04-28-um-20.25.00-1" width="600" height="400" loading="lazy"></figure><p>Ogni volta mi stupisco di quanto funzioni bene. Ora, la struttura di base è configurata e possiamo iniziare a implementare la documentazione per i nostri endpoint. Andiamo!</p><p>Quando dai un'occhiata al contenuto della chiave <strong><strong>options.apis</strong></strong> nel file <code>swagger.js</code>, vedrai che abbiamo incluso il percorso alle nostre rotte per l'allenamento e per il file degli allenamenti all'interno della cartella database. Questa è la cosa più importante da impostare per fare in modo che la magia si verifichi.</p><p>Avendo definito questi file all'interno delle opzioni swagger ci consente di usare commenti che fanno riferimento a OpenAPI e ad avere la sintassi come nei file <a href="https://it.wikipedia.org/wiki/YAML">yaml</a>, necessaria per l'impostazione della nostra documentazione.</p><p>Ora siamo pronti a creare la documentazione per il nostro primo endpoint! Facciamolo subito.</p><pre><code class="language-javascript">// In src/v1/routes/workoutRoutes.js
...

/**
 * @openapi
 * /api/v1/workouts:
 *   get:
 *     tags:
 *       - Workouts
 *     responses:
 *       200:
 *         description: OK
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 status:
 *                   type: string
 *                   example: OK
 *                 data:
 *                   type: array 
 *                   items: 
 *                     type: object
 */
router.get("/", cache("2 minutes"), workoutController.getAllWorkouts);

...</code></pre><p>Questo è praticamente quanto serve per aggiungere un endpoint alla nostra documentazione swagger. Puoi vedere tutte le specifiche per descrivere un endpoint nella loro <a href="https://swagger.io/docs/specification/about/">ottima documentazione</a>.</p><p>Quando ricarichi la tua pagina di documentazione dovresti vedere quanto segue:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2022/04/Bildschirmfoto-2022-04-29-um-07.21.51-1.png" class="kg-image" alt="Bildschirmfoto-2022-04-29-um-07.21.51-1" width="600" height="400" loading="lazy"></figure><p>Questo dovrebbe esserti piuttosto familiare se hai già lavorato con API che hanno una documentazione secondo le specifiche OpenAPI. Questa è la vista dove sono elencati tutti gli endpoint e puoi estendere ciascuno di essi per ottenere maggiori informazioni.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2022/04/Bildschirmfoto-2022-04-29-um-07.41.46-1.png" class="kg-image" alt="Bildschirmfoto-2022-04-29-um-07.41.46-1" width="600" height="400" loading="lazy"></figure><p>Se osservi attentamente la nostra risposta, vedrai che non abbiamo definito il corretto valore di ritorno perché stiamo semplicemente dicendo che la nostra proprietà "data" sarà un array di oggetti vuoti.</p><p>Ecco quando entrano in gioco gli schemi.</p><pre><code class="language-javascript">// In src/databse/Workout.js
...

/**
 * @openapi
 * components:
 *   schemas:
 *     Workout:
 *       type: object
 *       properties:
 *         id: 
 *           type: string
 *           example: 61dbae02-c147-4e28-863c-db7bd402b2d6
 *         name: 
 *           type: string
 *           example: Tommy V  
 *         mode:
 *           type: string
 *           example: For Time
 *         equipment:
 *           type: array
 *           items:
 *             type: string
 *           example: ["barbell", "rope"]
 *         exercises:
 *           type: array
 *           items:
 *             type: string
 *           example: ["21 thrusters", "12 rope climbs, 15 ft", "15 thrusters", "9 rope climbs, 15 ft", "9 thrusters", "6 rope climbs, 15 ft"]
 *         createdAt:
 *           type: string
 *           example: 4/20/2022, 2:21:56 PM
 *         updatedAt: 
 *           type: string
 *           example: 4/20/2022, 2:21:56 PM
 *         trainerTips:
 *           type: array
 *           items:
 *             type: string
 *           example: ["Split the 21 thrusters as needed", "Try to do the 9 and 6 thrusters unbroken", "RX Weights: 115lb/75lb"]
 */

...</code></pre><p>Nell'esempio qui sopra abbiamo creato il nostro primo schema. Tipicamente questa definizione sarà all'interno del nostro file schema o modello dove hai definito i modelli del database.</p><p>Come puoi vedere è piuttosto semplice. Abbiamo definito tutte le proprietà che compongono un allenamento incluso il tipo e un esempio.</p><p>Puoi visitare la nostra pagina di documentazione nuovamente e riceverai un'altra sezione che contiene gli schemi.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2022/04/Bildschirmfoto-2022-04-29-um-07.29.49-1.png" class="kg-image" alt="Bildschirmfoto-2022-04-29-um-07.29.49-1" width="600" height="400" loading="lazy"></figure><p>Questo schema può ora essere referenziato nella risposta del nostro endpoint.</p><pre><code class="language-javascript">// In src/v1/routes/workoutRoutes.js
...

/**
 * @openapi
 * /api/v1/workouts:
 *   get:
 *     tags:
 *       - Workouts
 *     responses:
 *       200:
 *         description: OK
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 status:
 *                   type: string
 *                   example: OK
 *                 data:
 *                   type: array 
 *                   items: 
 *                     $ref: "#/components/schemas/Workout"
 */
router.get("/", cache("2 minutes"), workoutController.getAllWorkouts);

...</code></pre><p>Guarda attentamente al fondo della riga di commento sotto "items". Stiamo usando "$ref" per creare un riferimento e stiamo referenziando il percorso del nostro schema che abbiamo definito all'interno del file degli allenamenti.</p><p>Ora siamo in grado di mostrare un allenamento completo nella nostra risposta.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2022/04/Bildschirmfoto-2022-04-29-um-07.44.12-1.png" class="kg-image" alt="Bildschirmfoto-2022-04-29-um-07.44.12-1" width="600" height="400" loading="lazy"></figure><p>Abbastanza bello, vero? Potresti pensare che "digitare questi commenti a mano può essere un compito noioso".</p><p>Questo potrebbe essere vero, ma pensala in questo modo. Quei commenti che si trovano all'interno del tuo codice sono anche un'ottima documentazione per te stesso come sviluppatore dell'API. Non è necessario consultare sempre la documentazione quando si desidera conoscere le informazioni su un endpoint specifico. Puoi semplicemente cercarlo in un posto all'interno del tuo codice sorgente.</p><p>La documentazione degli endpoint ti aiuta anche a comprenderli meglio e ti "costringe" a pensare a qualsiasi cosa che potresti aver dimenticato di implementare.</p><p>Come vedi ho dimenticato davvero qualcosa. Mancano ancora le possibili risposte di errore e i parametri di query!</p><p>Risolviamolo:</p><pre><code class="language-javascript">// In src/v1/routes/workoutRoutes.js
...

/**
 * @openapi
 * /api/v1/workouts:
 *   get:
 *     tags:
 *       - Workouts
 *     parameters:
 *       - in: query
 *         name: mode
 *         schema:
 *           type: string
 *         description: The mode of a workout
 *     responses:
 *       200:
 *         description: OK
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 status:
 *                   type: string
 *                   example: OK
 *                 data:
 *                   type: array 
 *                   items: 
 *                     $ref: "#/components/schemas/Workout"
 *       5XX:
 *         description: FAILED
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 status: 
 *                   type: string
 *                   example: FAILED
 *                 data:
 *                   type: object
 *                   properties:
 *                     error:
 *                       type: string 
 *                       example: "Some error message"
 */
router.get("/", cache("2 minutes"),  workoutController.getAllWorkouts);

...</code></pre><p>Quando guardi la parte superiore del nostro commento sotto "tags", puoi vedere che ho aggiunto un'altra chiave chiamata "parameters", dove ho definito il nostro parametro di ricerca per il filtro.</p><p>La nostra documentazione ora lo visualizza correttamente:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2022/04/Bildschirmfoto-2022-04-29-um-08.03.00-1.png" class="kg-image" alt="Bildschirmfoto-2022-04-29-um-08.03.00-1" width="600" height="400" loading="lazy"></figure><p>Per documentare un possibile caso di errore, a questo punto stiamo generando solo un errore 5XX. Quindi sotto "responses" puoi vedere che ho anche definito un'altra documentazione per questo..</p><p>La nostra pagina di documentazione ora è come questa:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2022/04/Bildschirmfoto-2022-04-29-um-08.04.44-2.png" class="kg-image" alt="Bildschirmfoto-2022-04-29-um-08.04.44-2" width="600" height="400" loading="lazy"></figure><p>Fantastico! Abbiamo appena creato la documentazione completa per un endpoint. Ti consiglio vivamente di implementare il resto degli endpoint da solo per acquisire pratica. Imparerai molto mentre lo fai!</p><p>Come potresti aver visto, documentare la tua API non deve essere sempre un mal di testa. Penso che gli strumenti che ti ho presentato riducano il tuo sforzo complessivo e impostare tutto sia piuttosto semplice.</p><p>Quindi possiamo concentrarci sulla cosa importante, la documentazione stessa. A mio parere, la documentazione di swagger/OpenAPI è molto buona e ci sono molti ottimi esempi su internet.</p><p>Non avere una documentazione a causa del troppo lavoro "extra" non dovrebbe più essere una scusa.</p><h2 id="conclusione"><strong><strong>Conclusion</strong>e</strong></h2><p>Finalmente, è stata una corsa piuttosto divertente. Mi è davvero piaciuto scrivere questo articolo per te e ho anche imparato molto.</p><p>Potrebbero esserci migliori pratiche che sono importanti mentre altre potrebbero non essere applicabili alla tua situazione attuale. Va bene, perché come ho detto prima, è responsabilità di ogni sviluppatore scegliere le migliori pratiche che possono essere applicate alla propria situazione attuale.</p><p>Ho fatto del mio meglio per mettere insieme tutte quelle che ho attuato fino ad ora creando la nostra API lungo il percorso. Mi sono davvero divertito!</p><p>Mi piacerebbe ricevere un feedback di qualsiasi tipo. Se c'è qualcosa che vorresti dirmi (buona o cattiva), non esitare a contattarmi:</p><p>Ecco il mio <a href="https://www.instagram.com/jean_marc.dev/">Instagram</a> (puoi anche seguire il mio percorso di sviluppatore di software)</p><p>Ci vediamo alla prossima!</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Le Migliori Pratiche REST API – Esempi di Progettazione di Endpoint REST ]]>
                </title>
                <description>
                    <![CDATA[ Nello sviluppo web, le API REST giocano un importante ruolo nell'assicurare una comunicazione regolare tra il client e il server. Puoi pensare al client come al frontend e al server come al backend. La comunicazione tra il client (frontend) e il server (backend) in genere non è proprio diretta. Quindi ]]>
                </description>
                <link>https://www.freecodecamp.org/italian/news/le-migliori-pratiche-rest-api-esempi-di-progettazione-di-endpoint-rest/</link>
                <guid isPermaLink="false">641ac3b802ca030845afba42</guid>
                
                    <category>
                        <![CDATA[ API Rest ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Roberto Pauletto ]]>
                </dc:creator>
                <pubDate>Tue, 28 Mar 2023 05:30:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/italian/news/content/images/2023/03/api.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Articolo originale:</strong> <a href="https://www.freecodecamp.org/news/rest-api-best-practices-rest-endpoint-design-examples/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">REST API Best Practices – REST Endpoint Design Examples</a>
      </p><p>Nello sviluppo web, le API REST giocano un importante ruolo nell'assicurare una comunicazione regolare tra il client e il server.</p><p>Puoi pensare al client come al frontend e al server come al backend.</p><p>La comunicazione tra il client (frontend) e il server (backend) in genere non è proprio diretta. Quindi usiamo un'interfaccia chiamata <a href="https://it.wikipedia.org/wiki/Application_programming_interface">Application Programming Interface (API)</a> che agisce da intermediario tra il client e il server.</p><p>Poiché l'API gioca un ruolo cruciale in questa comunicazione client-server, dovremmo sempre progettare le API tenendo sempre presenti le migliori pratiche. Questo aiuta gli sviluppatori a mantenerle, e coloro che le adottano, allo stesso tempo, non incontrano problemi nell'utilizzarle.</p><p>In questo articolo, ti illustrerò 9 buone pratiche da seguire nella creazione di API REST. Questo ti aiuterà a creare le migliori API possibili, e anche a facilitare la vita ai suoi utilizzatori.</p><h2 id="innanzitutto-cos-una-api-rest"><strong>Innanzitutto, Cos'è una API <strong>REST?</strong></strong></h2><p>REST sta per Representational State Transfer. È uno stile architetturale di software creato da Roy Fielding nel 2000 per orientare la progettazione di architetture per il web.</p><p>Qualunque API (Application Programming Interface) che segua i principi di progettazione &nbsp;REST è detta RESTful.</p><p>In parole semplici, un'API REST è un mezzo per comunicare tra due computer via HTTP (Hypertext Transfer Protocol), allo stesso modo nel quale comunicano client e server.</p><h2 id="migliori-pratiche-di-progettazione-di-api-rest"><strong>Migliori Pratiche di Progettazione di API REST</strong></h2><h3 id="1-usare-json-come-formato-per-inviare-e-ricevere-dati"><strong><strong>1. U</strong>sare<strong> JSON </strong>come Formato per Inviare e Ricevere Dati</strong></h3><p>In passato, l'accettazione e la risposta relative a richieste API era effettuata per la maggior parte in XML e anche in HTML. Ma ai giorni nostri, JSON (JavaScript Object Notation) è diventato di gran lunga il formato de-facto per inviare e ricevere dati dalle API.</p><p>Questo perché, con il formato XML ad esempio, decodificare e codificare i dati è spesso una seccatura, pertanto il formato XML non è più ampiamente supportato dai framework.</p><p>JavaScript, per esempio, ha un metodo integrato per elaborare i dati JSON acquisiti via API in quanto JSON era stato creato principalmente per questo. Tuttavia se stai usando altri linguaggi di programmazione, come Python o PHP, anch'essi ora sono dotati di tutti i metodi per elaborare e manipolare i dati JSON.</p><p>Python, ad esempio, fornisce i metodi &nbsp;<code>json.loads()</code> e <code>json.dumps()</code> per lavorare con i dati JSON.</p><p>Per assicurarti che il client interpreti correttamente i dati JSON, dovresti impostare il valore di <code>Content-Type</code> nell'header della risposta come <code>application/json</code> quando componi la richiesta.</p><p>Per quanto riguarda i framework lato server, molti di essi impostano automaticamente il valore di <code>Content-Type</code>. Express, per esempio, ora ha il middleware &nbsp;<code>express.json()</code> per questo scopo. Anche il pacchetto NPM <code>body-parser</code> funziona ancora per lo stesso scopo.</p><h3 id="2-usare-i-sostantivi-invece-che-i-verbi-per-gli-endpoint"><strong><strong>2. Us</strong>are i Sostantivi Invece che i Verbi per gli<strong> Endpoint</strong></strong></h3><p>Nella progettazione di un'API REST, non dovresti usare verbi nei percorsi degli endpoint. Gli endpoint dovrebbero usare sostantivi, che identificano ciò che ciascuno rappresenta.</p><p>Questo perché i metodi HTTP come <code>GET</code>, <code>POST</code>, <code>PUT</code>, <code>PATCH</code> e <code>DELETE</code> esprimono già una forma verbale (in lingua inglese) per l'esecuzione di operazioni <a href="https://it.wikipedia.org/wiki/CRUD">CRUD</a> di base.</p><p><code>GET</code>, <code>POST</code>, <code>PUT</code>, <code>PATCH</code> e <code>DELETE</code> sono i verbi HTTP più comuni. Ce ne sono altri come <code>COPY</code>, <code>PURGE</code>, <code>LINK</code>, <code>UNLINK</code> e così via.</p><p>Quindi, per esempio, un endpoint non dovrebbe essere come questo:</p><p><code>https://mysite.com/ottieniPost</code> </p><p>oppure </p><p><code>https://mysite.com/creaPost</code></p><p>Viceversa dovrebbe essere qualcosa come:</p><p><code>https://mysite.com/posts</code></p><p>In breve, dovresti lasciare che siano i verbi HTTP a rappresentare quello che fa un endpoint (ovviamente nel loro significato inglese - n.d.t). Pertanto <code>GET</code> otterrà dei dati, <code>POST</code> andrà a creare dati, <code>PUT</code> aggiornerà dati e <code>DELETE</code> si sbarazzerà dei dati.</p><h3 id="3-attribuire-alle-collezioni-nomi-al-plurale"><strong><strong>3. </strong>Attribuire alle Collezioni Nomi al Plurale</strong></h3><p>Puoi pensare ai dati della tua API come una collezione di risorse diverse per i tuoi consumatori.</p><p>Se hai un endpoint come <code>https://mysite.com/post/123</code>, potrebbe andare bene per richieste di eliminazione (<code>DELETE</code>) o di aggiornamento (<code>PUT</code> o <code>PATCH</code>), ma non dice all'utente che potrebbero esserci altri post nella collezione. Ecco perché la tua collezione dovrebbe usare nomi plurali.</p><p>Quindi, invece di &nbsp;<code>https://mysite.com/post/123</code>, dovrebbe essere <code>https://mysite.com/posts/123</code>.</p><h3 id="4-usare-codici-di-stato-nella-gestione-degli-errori"><strong><strong>4. Us</strong>are Codici di Stato nella Gestione degli Errori</strong></h3><p>Dovresti sempre usare normali codici di stato HTTP nelle risposte alle richieste fatte alla tua API. Questo aiuterà i tuoi utenti a sapere cosa sta succedendo: se la richiesta ha avuto successo, se ha fallito, o qualcosa d'altro.</p><p>Qui sotto c'è una tabella che mostra i vari gruppi di Codici di Stato HTTP e il loro significato:</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>GRUPPI DI CODICI DI STATO</th>
<th>SIGNIFICATO</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td>100 – 199</td>
<td>Risposte informative.<br>Per esempio 102 indica che la risorsa è stata elaborata</td>
</tr>
<tr>
<td>200 - 299</td>
<td>Azione ricevuta con successo, compresa e accettata<br>Per esempio 200 significa "Ok", 201 "Risorsa creata", 202 "Richiesta accettata"</td>
</tr>
<tr>
<td>300 – 399</td>
<td>Ridirezioni<br>Per esempio 301 significa "Spostato permanentemente"</td>
</tr>
<tr>
<td>400 – 499</td>
<td>Errori lato client<br>400 significa "Richiesta non valida" e 404 "Risorsa non trovata"</td>
</tr>
<tr>
<td>500 – 599</td>
<td>Errori lato server<br>Per esempio 500 significa "Errore interno"</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><h3 id="5-usare-annidamenti-negli-endpoint-per-mostrare-relazioni"><strong><strong>5. Us</strong>ar<strong>e </strong>Annidamenti negli Endpoint per Mostrare Relazioni</strong></h3><p>Spesso, endpoint diversi possono essere interconnessi, pertanto li dovresti annidare per facilitarne la comprensione.</p><p>Per esempio, nel caso di una piattaforma di blog multi utente, diversi post potrebbero essere scritti da autori diversi, quindi un endpoint come <code>https://mysite.com/posts/author</code>, sarebbe un valido caso per l'annidamento.</p><p>Restando nello stesso ambito, i post potrebbero avere i loro commenti individuali, quindi per recuperare tutti i commenti di uno specifico post, un endpoint come <code>https://mysite.com/posts/&lt;postId&gt;/comments</code> dovrebbe avere senso.</p><p>Dovresti evitare di avere più di 3 livelli di annidamento, in quanto un numero superiore potrebbe rendere la tua API meno elegante e leggibile.</p><h3 id="6-usare-filtri-ordinamento-e-paginazione-per-recuperare-i-dati-richiesti"><strong><strong>6. Us</strong>are Filtri, Ordinamento e Paginazione per Recuperare i Dati Richiesti</strong></h3><p>Talvolta un database di un'API potrebbe diventare incredibilmente grande. Se questo accade, ottenere dati da questo database potrebbe essere un'operazione molto lenta.</p><p>Il filtro, l'ordinamento e la paginazione sono tutte azioni che possono essere eseguite su una collezione di un'API REST. Ciò ti consente di recuperare, ordinare e disporre solo i dati necessari disposti in pagine in modo che il server non sia troppo occupato con le richieste.</p><p>Un esempio di un endpoint con filtro potrebbe essere il seguente:<br><code>https://mysite.com/posts?tags=javascript</code><br>Questo endpoint fornirà tutti i post che hanno un tag "JavaScript" associato.</p><h3 id="7-usare-ssl-per-la-sicurezza"><strong><strong>7. Us</strong>are<strong> SSL </strong>per la Sicurezza</strong></h3><p>SSL sta per Secure Socket Layer. È cruciale per la sicurezza nella progettazione di API REST. Questo renderà sicura la tua API, che sarà meno vulnerabile ad attacchi dannosi.</p><p>Altre misure di sicurezza che dovrebbero essere prese in considerazione includono: rendere la comunicazione tra server e client privata e assicurarsi che nessun consumatore dell'API possa ottenere più di quello che sta richiedendo.</p><p>I certificati SSL non sono difficili da caricare su un server e sono sempre gratuiti come <a href="https://letsencrypt.org">Let's Encrypt</a>, molti altri solo per il primo anno. Non sono costosi da acquistare nel caso non fossero disponibili gratuitamente.</p><p>La chiara differenza tra l'url di un'API REST che viene eseguita su SSL rispetto a un'altra che non lo usa è la "s" che manca in HTTP per le richieste non sicure:<br><code>https://mysite.com/posts</code> viene eseguita su SSL.<br><code>http://mysite.com/posts</code> non viene eseguita su SSL.</p><h3 id="8-essere-chiari-con-il-versionamento"><strong><strong>8. </strong>Essere Chiari con il Versionamento</strong></h3><p>Le API REST dovrebbero avere versioni differenti, in modo da non forzare i client (gli utenti) a migrare a nuove versioni. Questo potrebbe anche non fare più funzionare la loro applicazione, se non sei cauto.</p><p>Uno dei più comuni sistemi di <a href="https://it.wikipedia.org/wiki/Versione_(sviluppo_software)#Nomenclatura:_il_versionamento">versionamento</a> nello sviluppo web è il versionamento semantico.</p><p>Un esempio di versionamento semantico è <code>1.0.0</code>, <code>2.1.2</code> e <code>3.3.4</code>. Il primo numero rappresenta la versione major, il secondo la versione minor e il terzo la versione patch.</p><p>Molte API RESTful di giganti tecnologici e singoli in genere si presentano così:<br><code>https://mysite.com/v1/</code> per la versione 1<br><code>https://mysite.com/v2</code> per la versione 2</p><p>Facebook adotta questo versionamento per le proprie API:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/09/facebook-versioning.jpg" class="kg-image" alt="facebook-versioning" width="600" height="400" loading="lazy"></figure><p>Spotify adotta lo stesso sistema:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/09/spotify-versioning.jpg" class="kg-image" alt="spotify-versioning" width="600" height="400" loading="lazy"></figure><p>Questo non è il caso per tutte le API. Il versionamento adottato da Mailchimp per la sua API è diverso:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/09/mailchimp-ersioning.jpg" class="kg-image" alt="mailchimp-ersioning" width="600" height="400" loading="lazy"></figure><p>Quando rendi disponibile la tua API REST in questo modo, non forzi i client a migrare alla nuova versione nel caso scelgano di non farlo.</p><h3 id="9-fornire-una-documentazione-accurata"><strong><strong>9. </strong>Fornire una Documentazione Accurata</strong></h3><p>Quando crei un'API REST, devi aiutare i client (chi utilizza la tua API) a conoscerla e scoprire come usarla correttamente. Il miglior modo per farlo è dotare l'API di una buona documentazione.</p><p>La documentazione dovrebbe contenere:</p><ul><li>gli endpoint rilevanti dell'API</li><li>esempi di richieste per gli endpoint</li><li>implementazioni in vari linguaggi di programmazione</li><li>messaggi che elencano i diversi errori con i loro codici di stato</li></ul><p>Uno degli strumenti comuni che puoi usare per documentare la tua API è Swagger. Puoi anche usare Postman, uno degli strumenti più diffusi tra gli sviluppatori web per i test delle API, per documentare le tue API.</p><h2 id="conclusione"><strong><strong>Conclusion</strong>e</strong></h2><p>In questo articolo, hai appreso diverse migliori pratiche da tenere a mente quando crei delle API REST. </p><p>È importante metterle in pratica in modo che tu possa costruire applicazioni altamente funzionali che funzionano bene, sono sicure e in ultima analisi facilitano la vita di chi utilizza la tua API.</p><p>Grazie per la lettura. Ora vai a creare qualche API usando queste migliori pratiche.</p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
