Articolo originale: REST API Tutorial – REST Client, REST Service, and API Calls Explained With Code Examples

Ti sei mai chiesto come funziona la registrazione o l'accesso a un sito web dietro le quinte? Oppure come mai quando cerchi  "gattini carini" su YouTube ottieni una serie di risultati e sei in grado di riprodurre il contenuto del relativo video situato in una macchina remota?

In questa guida rivolta ai principianti, ti guiderò attraverso il processo dell'impostazione di una API RESTful. Declassificheremo alcuni dei termini gergali e daremo un'occhiata a come possiamo scrivere codice per creare un server in NodeJS. Addentriamoci nelle profondità di JavaScript!

Sbarazziamoci del gergo

Cos'è REST? Secondo Wikipedia (estratto):

REpresentational State Transfer (REST) è uno schema architetturale per software che definisce una serie di vincoli da usare per creare servizi Web. I servizi Web RESTful consentono ai sistemi richiedenti di accedere a rappresentazioni testuali di risorse Web e manipolarle utilizzando un insieme uniforme e predefinito di operazioni stateless (prive di stato).

Demistifichiamo il suo significato. REST è fondamentalmente un insieme di regole per la comunicazione tra un client e un server. Ci sono alcuni vincoli sulla definizione di REST:

  1. Architettura client-server: l'interfaccia utente del sito web o app dovrebbe essere separata dalla richiesta/memorizzazione dei dati, in modo che ciascuna parte possa essere scalabile in modo indipendente.
  2. Privo di stato (stateless): la comunicazione non dovrebbe avere alcun contesto client memorizzato sul server. Il che significa che ogni richiesta al server dovrebbe essere fatta con tutti i dati richiesti e che non si può contare sul fatto che il server conservi dati da richieste precedenti.
  3. Sistema stratificato: il client non dovrebbe essere in grado di determinare se sta comunicando direttamente con il server oppure con un qualche intermediario. Questi server intermediari (potrebbero essere proxy o bilanciatori di carico) consentono stabilità e sicurezza per il server sottostante.

Va bene, ora che sai cosa sono i servizi REST, ecco alcuni dei termini usati nel titolo dell'articolo:

  1. Client REST: il codice o un'app che può accedere a questi servizi REST. Ne stai usando una proprio adesso! Esatto, il browser può fungere da client REST non controllato (è il sito web che gestire le richieste del browser). Il browser, per lungo tempo, ha usato una funzione integrata chiamata XMLHttpRequest per tutte le richieste REST. Tuttavia è stata rimpiazzata da FetchAPI, un approccio moderno alle richieste, basato sulle promise. Altri esempi sono librerie di codice come axios, superagent e got o qualche app dedicata come Postman (o una versione online, hoppscotch), oppure uno strumento da riga di comando come cURL!.
  2. Servizio REST: il server. Ci sono molte popolari librerie che rendono la creazione di questi server un gioco da ragazzi, come ExpressJS per NodeJS e Django per Python.
  3. API REST: definisce i punti di contatto (endpoint) e i metodi consentiti per ottenere/inviare dati al server. Parleremo di ciò molto dettagliatamente in seguito. Alcune alternative sono: GraphQL, JSON-Pure e oData.

Allora dimmi, come funziona REST?

In termini molto ampi, tu chiedi al server di ottenere o di salvare certi dati e il server risponde alla richiesta.

In termini di programmazione, c'è un endpoint (un URL) sul quale il server è in attesa di ricevere una richiesta. Ci connettiamo a questo endpoint e inviamo alcuni dati su di noi (ricorda, REST è privo di stato, non vengono memorizzati i dati di una richiesta) e il server risponde in modo appropriato.

Le parole annoiano, ti darò una dimostrazione. Userò Postman per mostrarti richiesta e risposta:

image-162
postman: visualizzazione di una risposta

I dati restituiti sono in formato JSON (JavaScript Object Notation) e possono essere consultati direttamente.

L'url https://official-joke-api.appspot.com/random_joke viene detto endpoint dell'API. Ci sarà un server in ascolto su quell'endpoint per richieste come quella che abbiamo fatto noi.

Anatomia di REST:

Ora sappiamo che i dati possono essere richiesti dal client e che il server risponderà in modo appropriato. Esaminiamo nel dettaglio come viene composta una richiesta.

  1. Endpoint: di questo ho già parlato. Per ricapitolare si tratta di un URL dove è in ascolto un server REST.
  2. Method (metodo): in precedenza ho scritto che puoi sia richiedere dati che modificarli, ma in che modo il server sa quale tipo di operazione è stata richiesta dal client? REST implementa diversi metodi per diversi tipi di richiesta, i più popolari sono:
    - GET: ottiene risorse dal server.
    - POST: crea risorse sul server.
    - PATCH o PUT: aggiorna risorse esistenti sul server.
    - DELETE: elimina risorse esistenti dal server.
  3. Headers (intestazioni): i dettagli aggiuntivi forniti per la comunicazione tra client e server (ricorda, REST è stateless). Alcuni dei più comuni sono:
    Nella richiesta:
    - host: l'indirizzo IP del client (o da dove ha avuto origine la richiesta)
    - accept-language: il linguaggio comprensibile dal client
    - user-agent: dati di client, sistema operativo e browser
    Nella risposta:
    - status: lo stato oppure il codice HTTP della richiesta
    - content-type: il tipo di risorsa inviata dal server
    - set-cookie: i cookie impostati dal server
  4. Data (dati): chiamato anche body (corpo) o message (messaggio); contiene le informazioni che vuoi inviare al server.

Basta con i dettagli fammi vedere il codice

Iniziamo a scrivere del codice per un servizio REST in Node. Implementeremo tutte le cose che abbiamo imparato qui sopra. Inoltre useremo la sintassi ES6 e superiore per scrivere il nostro servizio.

Assicurati di avere Node.JS installato e che i comandi node e npm siano inclusi nel tuo percorso. Userò Node 12.16.2 e NPM 6.14.4.

Crea una directory rest-service-node e portati su di essa:

mkdir rest-service-node
cd rest-service-node

Inizializza il progetto node:

npm init -y

Con l'opzione -y  accetti tutte le risposte predefinite alle domande poste dallo script. Se vuoi compilare personalmente l'intero questionario esegui semplicemente npm init.

Installiamo alcuni pacchetti. Useremo il framework ExpressJS per sviluppare il server REST. Esegui il comando seguente per installarlo:

npm install --save express body-parser

A cosa serve body-parser? Express, nella modalità predefinita, non è in grado di gestire i dati inviati tramite una richiesta POST in formato JSON. body-parser consente a Express di superare questo ostacolo.

Crea un file chiamato server.js e aggiungi il codice seguente:

const express = require("express");
const bodyParser = require("body-parser");

const app = express();

app.use(bodyParser.json());

app.listen(5000, () => {
  console.log(`Server is running on port 5000.`);
});

Le prime due righe importano Express e body-parser.

La terza riga inizializza il server Express e lo associa a una variabile chiamata app.

La riga, app.use(bodyParser.json()); inizializza il plugin body-parser.

Per ultimo impostiamo il nostro server per ricevere le richieste sulla porta 5000.

Ottenere dati da un server REST:

Per ottenere dati da un server ci serve una richiesta di tipo GET. Aggiungi il seguente codice prima di  app.listen:

const sayHi = (req, res) => {
  res.send("Hi!");
};

app.get("/", sayHi);

Abbiamo creato una funzione sayHi che riceve due parametri: req e res (la spiegazione in seguito) e invia un 'Hi!' come risposta.

app.get() riceve due parametri, il percorso della rotta e la funzione da chiamare quando il percorso viene richiesto dal client. Pertanto l'ultima riga significa: ehi server, mettiti in ascolto per richieste sul percorso  '/' (pensa homepage) e chiama la funzione sayHi se viene effettuata una richiesta.

app.get ci fornisce anche un oggetto request che contiene tutti i dati inviati dal client e un oggetto response che contiene tutti i metodi con i quali possiamo rispondere al client. Anche se sono accessibili come parametri di funzione, è consigliato per convenzione generale che vengano chiamati res per response e req per request.

Bando alle ciance. Lanciamo il server! Esegui il seguente comando:

node server.js

Se tutto va bene dovresti vedere un messaggio nella console che dice: Server is running on port 5000 (il server è in esecuzione sulla porta 5000).

Nota: Puoi cambiare la porta di ascolto con un altro numero, tuttavia evitando di usare quelle standard utilizzate da altri servizi.

image-160

Apri il tuo browser e nella barra degli indirizzi inserisci http://localhost:5000/. Dovresti vedere qualcosa tipo questo:

image-161

Fatto! La tua prima richiesta GET ha avuto successo!

Inviare dati al server REST

Come accennato in precedenza, implementiamo la gestione di una richiesta POST nel nostro server che restituisce la somma di due numeri interi ricevuti nella richiesta. Invieremo due numeri e il server in risposta restituirà la loro somma. Aggiungi questo nuovo metodo dopo app.get:

app.post("/add", (req, res) => {
  const { a, b } = req.body;
  res.send(`The sum is: ${a + b}`);
});

Noi invieremo i dati in formato JSON, in questo modo:

{
    "a":5,
    "b":10
}

Esaminiamo il codice:

Nella prima riga chiamiamo il metodo .post() di ExpressJS, che consente al server di gestire richieste POST. Questa funzione riceve gli stessi parametri del metodo .get(). Il percorso che passiamo è /add, quindi si potrà contattare l'endpoint come http://il-tuo-indirizzo-ip:porta/add oppure, nel nostro caso, localhost:5000/add. Invece di scrivere una funzione altrove gestiamo tutto all'interno.

Nella seconda riga abbiamo usato un po' di sintassi ES6, nella fattispecie la destrutturazione di un oggetto. Qualsiasi dato inviamo tramite richiesta viene conservato ed è disponibile nella chiave body dell'oggetto req. Quindi in sostanza avremmo potuto sostituire la seconda riga con qualcosa tipo:

const num1 = req.body.a;
const num2 = req.body.b;

Nella terza riga usiamo il metodo send() dell'oggetto res per inviare il risultato dell'addizione. Ancora una volta usiamo i template literals da ES6. Ora facciamo un test (usando Postman):

image-163

Abbiamo inviato i dati  5 e 10 associati alle chiavi a e b inserendoli come body. Postman allega questi dati alla richiesta e la invia. Quando il server riceve la richiesta può elaborare i dati da req.body, come abbiamo fatto nel codice qui sopra. Il risultato è mostrato più sotto.

Ecco il codice fino a ora:

const express = require("express");
const bodyParser = require("body-parser");

const app = express();

app.use(bodyParser.json());

const sayHi = (req, res) => {
  res.send("Hi!");
};

app.get("/", sayHi);

app.post("/add", (req, res) => {
  const { a, b } = req.body;
  res.send(`The sum is: ${a + b}`);
});

app.listen(5000, () => {
  console.log(`Server is running on port 5000.`);
});

Client REST:

Va bene, abbiamo creato il server, ma come vi accediamo dal nostro sito web o app web? Qui le librerie client REST torneranno utili.

Andremo a costruire una pagina web che conterrà un form, dove potrai digitare i due numeri da sommare, poi visualizzeremo il risultato. Iniziamo.

Per prima cosa modifichiamo un poco server.js:

const path = require("path");
const express = require("express");
const bodyParser = require("body-parser");

const app = express();

app.use(bodyParser.json());

app.get("/", (req, res) => {
  res.sendFile(path.join(__dirname, "index.html"));
});

app.post("/add", (req, res) => {
  const { a, b } = req.body;
  res.send({
    result: parseInt(a) + parseInt(b)
  });
});

app.listen(5000, () => {
  console.log(`Server is running on port 5000.`);
});

Abbiamo importato un nuovo pacchetto,  path, fornito da Node, per manipolare i percorsi indipendentemente dalla piattaforma. Poi abbiamo cambiato la richiesta GET su / e usato un altro metodo disponibile in res, vale a dire sendFile, che ci consente di inviare qualunque tipo di file come risposta. Quindi quando qualcuno si porterà sul percorso /, otterrà la nostra pagina index.html.

Infine abbiamo cambiato la nostra funzione app.post affinché restituisca la somma in formato JSON e converta in interi sia a che b.

Creiamo una pagina html, che chiameremo index.html, con uno stile di base:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>REST Client</title>
  </head>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    .container {
      height: 100vh;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }
    form {
      display: flex;
      flex-direction: column;
      margin-bottom: 20px;
    }
    label,
    input[type="submit"] {
      margin-top: 20px;
    }
  </style>
  <body>
    <div class="container">
      <h1>Simple POST Form</h1>
      </h1>
      <form>
        <label>Number 1:</label>
        <input id="num1" type="number" />
        <label>Number 2:</label>
        <input id="num2" type="number" />
        <input type="submit" value="Add"/>
      </form>
      <div class="result">Click Add!</div>
    </div>
  </body>
</html>

Aggiungiamo un tag script appena prima del tag di chiusura body per evitare di dover gestire un file .js separato. Inizieremo con l'inserire un event listener per l'evento submit che chiamerà la funzione sendData:

<script>
	document.addEventListener("submit", sendData);
</script>

All'interno della funzione, per prima cosa, dobbiamo impedire l'aggiornamento della pagina quando viene fatto click sul pulsante 'Add'. Questo si può fare chiamando il metodo preventDefault() dell'evento. Poi catturiamo i valori delle caselle di testo, vale a dire i numeri da sommare:

function sendData(e) {
    e.preventDefault();
    const a = document.querySelector("#num1").value;
    const b = document.querySelector("#num2").value;
}

Ora facciamo una chiamata al server passando i valori di a e b. Useremo l'API Fetch, integrata su tutte le versioni più recenti dei browser.

Fetch riceve due parametri, l'URL dell'endpoint e un oggetto JSON con i dettagli della richiesta, e ritorna una promise. Spiegare cos'è una promise va oltre lo scopo di questo articolo pertanto lascio a te scoprirlo.

Proseguiamo aggiungendo quanto segue all'interno della funzione sendData():

fetch("/add", {
        method: "POST",
        headers: {
            Accept: "application/json",
            "Content-Type": "application/json"
        },
        body: JSON.stringify({
            a: parseInt(a),
            b: parseInt(b)
        })
    })
    .then(res => res.json())
    .then(data => {
        const {
            result
        } = data;
        document.querySelector(
            ".result"
        ).innerText = `The sum is: ${result}`;
    })
    .catch(err => console.log(err));

Per prima cosa chiamiamo la funzione fetch passando l'URL che corrisponde all'endpoint desiderato come primo parametro. Come secondo parametro passiamo un oggetto che contiene:

  • nella chiave method il nome del tipo di metodo, POST in questo caso che, dovrà essere usato per effettuare la richiesta
  • nella chiave headers un oggetto con le informazioni relative al tipo di dati che stiamo inviando (chiave Content-Type) e tipo di dati che accettiamo come risposta (chiave Accept)
  • nella chiave body passiamo il corpo del messaggio, vale a dire i numeri che devono essere sommati, forzando la conversione a valori interi, visto che non abbiamo implementato sul server una verifica sul tipo dei dati, sotto forma di stringa JSON, come specificato nell'header Content-Type, tramite la funzione JSON.stringify()

Infine, se la promise (ritornata da fetch) si risolve, otteniamo la risposta e la convertiamo in JSON. Successivamente recuperiamo il risultato dalla chiave data ritornato dalla risposta, che andremo a visualizzare sullo schermo.

Se la promise viene rigettata, visualizzeremo un messaggio di errore nella console.

Ecco il codice finale per index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>REST Client</title>
  </head>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    .container {
      height: 100vh;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }
    form {
      display: flex;
      flex-direction: column;
      margin-bottom: 20px;
    }
    label,
    input[type="submit"] {
      margin-top: 20px;
    }
  </style>
  <body>
    <div class="container">
      <h1>Simple POST Form</h1>
      </h1>
      <form>
        <label>Number 1:</label>
        <input id="num1" type="number" />
        <label>Number 2:</label>
        <input id="num2" type="number" />
        <input type="submit" value="Add"/>
      </form>
      <div class="result">Click Add!</div>
    </div>
    <script>
      document.addEventListener("submit", sendData);
      function sendData(e) {
        e.preventDefault();
        const a = document.querySelector("#num1").value;
        const b = document.querySelector("#num2").value;

        fetch("/add", {
          method: "POST",
          headers: {
            Accept: "application/json",
            "Content-Type": "application/json"
          },
          body: JSON.stringify({
            a: parseInt(a),
            b: parseInt(b)
          })
        })
          .then(res => res.json())
          .then(data => {
            const { result } = data;
            document.querySelector(
              ".result"
            ).innerText = `The sum is: ${result}`;
          })
          .catch(err => console.log(err));
      }
    </script>
  </body>
</html>

Ho inserito una piccola app su glitch per te da provare.

Conclusione

In questo post abbiamo imparato cos'è l'architettura REST e l'anatomia delle richieste REST. Abbiamo creato un semplice server REST che risponde a richieste GET e POST e costruito una semplice pagina web che usa un client REST per visualizzare la somma di due numeri.

Puoi ulteriormente ampliare il codice del server codificando anche le risposte per gli altri tipi di richiesta e addirittura implementare una completa app back-end per operazioni CRUD.

Spero che tu abbia imparato qualcosa da questo post. Se hai una qualsiasi domanda, sentiti libero di contattarmi su twitter. Buona programmazione!