REST API con NodeJS ed ExpressJS

L’interazione tra software e applicativi molto diversi tra loro è possibile dotandoli di API (Application Programming Interface). In questo modo, è possibile creare una galassia di strumenti che comunicano tra di loro e garantire la scalabilità e la riutilizzabilità di ogni componente del sistema.

Cos'è un webservice REST?

Un webservice REST (o RESTful) è un’API che permette a due applicazioni di comunicare tra loro usando il protocollo HTTP. REST sta per REpresentational State Transfer e si basa su alcuni principi fondamentali:

  • Le risorse sono identificate da un URI univoco. Per esempio, se devo ottenere una lista di utenti o aggiungerne uno, opererò sull’endpoint /users
  • Le operazioni sulle risorse sono eseguite usando i metodi HTTP standard: GET per la lettura, POST per la creazione, PUT per la modifica e DELETE per l’eliminazione
  • Le risposte del server sono rappresentate in un formato standard (JSON, XML, ecc.)
  • Lo stato dell’applicazione è trasferito tra client e server tramite le risposte. Il server non memorizza informazioni sul client e sulla sessione perchè queste vengono reinviate a ogni richiesta HTTP.

 

Diversi applicativi e siti web conosciuti impiegano o forniscono le proprie REST API. Alcuni esempi sono: Twitter, Facebook, PayPal, Discord e Google Docs.

Perché usare ExpressJS?

ExpressJS è un framework web per Node.js che facilita la creazione di applicazioni web e API. ExpressJS offre diverse funzionalità, tra cui:

  • Funzioni di facile uso per diverse operazioni comuni con NodeJS, che permettono di risparmiare molto tempo di analisi e sviluppo
  • Un sistema di routing intuitivo, basato sui middleware, con cui definire le routes per le diverse operazioni sulle risorse
  • Un set di middlewares integrati per aggiungere caratteristiche di base (ad esempio il supporto al formato JSON o l’uso di una cartella statica per salvare dei files)
  • Un motore di template che consente di generare pagine HTML dinamiche
  • Estensibilità con moduli e plugin, con un vasto ecosistema che permette di aggiungere ulteriori funzionalità, come ad esempio il supporto per l’upload di files.
 

ExpressJS è uno dei framework web più popolari per Node.js e viene usato per costruire sia REST API che interi CMS proprietari.

Come creare un webservice REST con ExpressJS?

Per creare un webservice REST con ExpressJS, avremo bisogno di:

  • Conoscenza di Javascript
  • Node.js installato sul nostro computer
  • Un IDE adeguato come Visual Studio Code o JetBrains Intellij Ultimate
  • Un client HTTP come Postman o cURL per testare le nostre API
 
Per iniziare, bisogna creare una cartella vuota che ospiterà il nostro primo webservice; quindi apriamo un terminale nella cartella e digitiamo il seguente comando:
npm init

Dopo aver dato tutte le conferme richieste, è il momento di installare tutte le dipendenze del progetto. In questo caso si tratta di un applicativo molto semplice e l’unica dipendenza sarà express:

npm i --save express

A questo punto possiamo iniziare a creare la nostra prima REST API con NodeJS ed Express.

Creare il server ExpressJS

Per creare il server ExpressJS, dobbiamo creare un file app.js all’interno della stessa cartella che abbiamo precedentemente creato. Qui, dobbiamo importare il modulo express e creare una sua istanza e chiamare il metodo express.listen(), che si occupa di avviare il server nella porta specificata e permette di specificare le azioni da intraprendere a ogni richiesta mediante una callback.

// Importiamo il modulo express
const express = require('express');

// Creiamo un'istanza di express
const app = express();

// Avviamo il server sulla porta 3000 e per ora ci limitiamo a stampare un messaggio in console
app.listen(3000, () => {
console.log('Server listening on port 3000');
});

Per testarlo, possiamo avviare da terminale il seguente comando:

node app.js

e aprire con un browser l’indirizzo http://localhost:3000. Dovremmo ricevere l’errore Cannot GET /, che per ora è quello che vogliamo.

Per supportare richieste e risposte in formato JSON, dobbiamo usare il middleware express.json. Per usare il middleware, dobbiamo aggiungere questa riga dopo aver creato l’istanza di express:

// Usiamo il middleware express.json aggiungere il supporto al formato JSON in entrata e uscita
app.use(express.json());

Per simulare un database, creiamo un array di utenti come dati di esempio. Ogni utente ha un id, un nome e un’età

// Creiamo un array di utenti come dati di esempio
let users = [
  { id: 1, name: 'Anna', age: 20 },
  { id: 2, name: 'Bruno', age: 25 },
  { id: 3, name: 'Carla', age: 30 },
];

Accedere ai dati in lettura: la route GET

Per definire la route GET per la risorsa /users, dobbiamo usare il metodo express.get() che accetta due argomenti: l’URI della risorsa relativo al base URI dell’API e una funzione callback che gestisce la richiesta e la risposta. La funzione callback ha due parametri: req e res. Il parametro req rappresenta la richiesta HTTP e contiene informazioni come l’URI, il metodo, i parametri e il corpo. Il parametro res rappresenta la risposta HTTP e ci permette di inviare dati al client in diversi formati. Per restituire tutti gli utenti in formato JSON, usiamo il metodo json di res e passiamo l’array degli utenti come argomento.

// Definiamo una route GET per la risorsa /users che restituisce tutti gli utenti in formato JSON
app.get('/users', (req, res) => {
return res.json(users); //return è facoltativo, ma è buona pratica includerlo
});

Effettuando una richiesta /GET all’indirizzo http://localhost:3000/users, l’applicativo ora risponde con i dati del nostro database simulato.

È possibile definire una route GET anche per visualizzare i dati di un singolo utente. Per farlo, aggiungeremo all’URI un parametro dimamico :id; sarà restituito l’utente in formato JSON, oppure uno status code 404 con un messaggio d’errore quando l’utente non viene trovato.

// Definiamo una route GET per la risorsa /users/:id che rimuove l'utente con l'id specificato nell'URI dall'array degli utenti
app.get('/users/:id', (req, res) => {
    // Leggiamo l'id dall'URI
    const id = parseInt(req.params.id);

    // Cerchiamo l'utente con l'id corrispondente nell'array degli utenti
    const index = users.findIndex((u) => u.id === id);

    // Se non troviamo l'utente, restituiamo un errore con il codice di stato 404 (Non trovato)
    if (index === -1) {
        return res.status(404).json({message: 'User not found'});
    }

    // Altrimenti, restituiamo l'utente
    return res.json(users[index]);
});

Effettuando una richiesta /GET all’indirizzo http://localhost:3000/1, la nostra REST API restituisce i dati del nostro utente 1.

Creare un nuovo utente: la route POST

Per definire la route POST per la risorsa /users, dobbiamo usare il metodo express().post e passare due argomenti: l’URI relativo della risorsa e una funzione callback che gestisce la richiesta e la risposta. La funzione callback è simile a quella che abbiamo visto per la route GET, quindi ha due parametri: req e res. In questo caso l’obiettivo è creare un nuovo utente: la route accetterà un oggetto utente, lo salverà nel nostro database e lo restituirà nella risposta in formato JSON. Restituirà, inoltre, uno stato 201 Created, come è buona pratica per le REST API.

// Definiamo una route POST per la risorsa /users che accetta un oggetto utente nel corpo della richiesta e lo aggiunge all'array degli utenti
app.post('/users', (req, res) => {
// Leggiamo l'oggetto utente dal corpo della richiesta
    const user = req.body;

// Assegniamo un id univoco all'utente
    user.id = users.length + 1;

// Aggiungiamo l'utente all'array degli utenti
    users.push(user);

// Restituiamo l'utente creato in formato JSON con il codice di stato 201 (Created)
    return res.status(201).json(user);
});

Per testarla, stavolta faremo una richiesta POST sempre all’indirizzo http://localhost:3000/users. Se usiamo Postman, imposteremo la richiesta in modo simile a questo:

Endpoint POST: serve per creare un nuovo elemento per una risorsa

Dovrebbe restituirci esattamente il nostro nuovo utente. Ripetendo la precedente richiesta GET vedremo la lista di utenti aggiornata con l’ultimo appena aggiunto.

Modificare un utente: la route PUT

Per definire la route PUT per la risorsa /users/:id, dobbiamo usare il metodo express().put e passare due argomenti: l’URI della risorsa con un parametro dinamico :id e una funzione callback che gestisce la richiesta e la risposta, in modo simile a quanto già visto con le routes precedenti. Per accettare un oggetto utente nel corpo della richiesta e usarlo per aggiornare l’utente con l’id specificato nell’URI, dobbiamo fare queste operazioni:

  • Leggere l’id dall’URI usando la proprietà req.params
  • Leggere l’oggetto utente dal corpo della richiesta usando la proprietà req.body
  • Cerchiamo l’utente con l’id corrispondente nel database. Nel nostro caso, usiamo il metodo Array.findIndex(…) su users
  • Se non troviamo l’utente, restituiamo un errore con il codice di stato 404 (Not Found) e una risposta opportuna
  • Se troviamo una corrispondenza, aggiorniamo l’utente con i dati ricevuti
  • Restituiamo l’utente aggiornato in formato JSON con il codice di stato 200 (OK)
// Definiamo una route PUT per la risorsa /users/:id che accetta un oggetto utente nel corpo della richiesta e lo usa per aggiornare l'utente con l'id specificato nell'URI
app.put('/users/:id', (req, res) => {
// Leggiamo l'id dall'URI
    const id = parseInt(req.params.id); // req.params.id è di tipo string e va convertito in int

// Leggiamo l'oggetto utente dal corpo della richiesta
    const user = req.body;

// Cerchiamo l'utente con l'id corrispondente nell'array degli utenti
    const index = users.findIndex((u) => u.id === id);

// Se non troviamo l'utente, restituiamo un errore con il codice di stato 404 (Not Found)
    if (index === -1) {
        return res.status(404).json({message: 'User not found'});
    }

// Altrimenti, aggiorniamo l'utente con i dati ricevuti
    users[index] = { ...users[index], ...user };

// Restituiamo l'utente aggiornato in formato JSON con il codice di stato 200 (OK)
    return res.json(users[index]);
});

Possiamo testarlo con Postman, ad esempio per modificare l’utente con id = 2.

Endpoint PUT: serve per modificare una risorsa

Eliminare un utente: la route DELETE

Per definire la route DELETE per la risorsa /users/:id, dobbiamo usare il metodo delete di express e passare due argomenti: l’URI della risorsa con un parametro dinamico :id e una funzione callback che gestisce la richiesta e la risposta. In questo caso, la funzione di callback non farà altro che restituire uno status code 204 con una risposta generica (che non verrà mostrata), oppure uno status 404 con un errore nel caso in cui l’utente non venga trovato.

// Definiamo una route DELETE per la risorsa /users/:id che rimuove l'utente con l'id specificato nell'URI dall'array degli utenti
app.delete('/users/:id', (req, res) => {
    // Leggiamo l'id dall'URI
    const id = parseInt(req.params.id);

    // Cerchiamo l'utente con l'id corrispondente nell'array degli utenti
    const index = users.findIndex((u) => u.id === id);

    // Se non troviamo l'utente, restituiamo un errore con il codice di stato 404 (Non trovato)
    if (index === -1) {
        return res.status(404).json({message: 'User not found'});
    }

    // Altrimenti, rimuoviamo l'utente dal database simulato
    users.splice(index, 1);

    // Restituiamo un messaggio di successo con il codice di stato 204 (Nessun contenuto)
    return res.status(204).send('Eliminato');
});

L'esempio completo

Di seguito il codice completo, fornito di commenti nei punti principali. L’esempio che abbiamo visto è un buon punto di partenza, che può essere integrato con altre funzionalità e riorganizzato in diversi file, a seconda dell’esigenza.