"Sono stato bocciato a parecchi esami. Un mio amico invece, li ha passati tutti a pieni voti. Ora è ingegnere e lavora in Microsoft. Io sono il proprietario."  - Bill Gates  •  "Se riesco a fare qualcosa in 10 minuti, è perché ho passato anni a studiarlo."  - Bill Gates  •  "Il vostro lavoro riempirà gran parte della vostra vita, e l'unico modo per essere veramente soddisfatti è fare ciò che ritenete sia un grande lavoro."  - Steve Jobs  •  "Siate affamati. Siate folli."  - Steve Jobs  •  

cinque 

ZERO

{Node.js} API – Interrogare il webserver: mapping, filtring, finding con Route Param

home / javascript /

Lucio Paolo Asciolla

Senior Full Stack Developer

Veicolare la restituzione delle risposte

Finora ci siamo occupati di:

  • Avviare un server
  • Gestire il routing di pagine statiche
  • Creare un nostra API per interrogare un server e gestire le risposte

Il tipo di risposta che abbiamo imparato ad utilizzare ci consente di restituire un dato JSON in base all'URL di richiesta.
Questo però ci consente di restituire "in blocco" tutti i dati senza poter effettuare una sorta di selezione e/o filtratura.

Vedremo ora come lavorare con i dati JSON e conseguenzialmente con il tipo di risposte.

Lavorare con i dati JSON

Immaginiamo di avere un grande dato JSON contenente molteplici oggetti, "utenti", e di voler che siano restituiti solo alcune proprietà di nostro interesse o solo alcuni oggetti-utenti che nelle loro proprietà hanno determinati valori.

Per poter operare in questo senso è buona prassi prevedere una proprietà univoca ed onnipresente in ciascun oggetto contenuto nel nostro dato JSON che normalmente corrisponde alla proprietà "id":

const persone = [
{
id: "1",
nome: "Mario",
cognome: "Rossi"
},
{
id: "2",
nome: "Guido",
cognome: "Lavespa"
},
{
id: "3",
nome: "Giovanni",
cognome: "Bianchi"
},
{
id: "4",
nome: "Rosa",
cognome: "Fiorita"
},
{
id: "5",
nome: "Andrea",
cognome: "Andreani"
}
]

module.exports = persone;
Note: la proprietà "id" può assumere sia valore numerico sia valore di stringa; normalmente nei database relazionali, come MongoDB e MySQL, la proprietà "id" è presente sotto forma di stringa.
const express = require('express');
const persone = require('./persone');
const app = express();

app.get('/tuttelepersone', function(request, response){         
    response.json(persone);
})

app.listen(3000);

Sinora abbiamo visto come gestire una richiesta mediante un preciso url (url relativo) e la restituzione per intero del dato JSON.

Effettuare il mapping

Il mapping è quell'operazione che ci consente di recuperare da un insieme di valori solo i dati di nostro interesse.

Più precisamente è quel processo che identifica la localizzazione delle informazioni all'interno di un database, un dato JSON o comunque in un sistema organizzato, il cui fine è la rielaborazione.

Nel nostro esempio potremmo avere l'esigenza di non mostrare tutte le proprietà presenti negli oggetti del dato JSON ma solo di mostrarne alcune; per esempio solo gli id ed i nomi escludendo il cognome.

Per fare ciò dovremo occuparci di "mappare" i dati che ci interessano:

...
app.get('/soloidenome', function(request, response){

const filtroProprietaPersone = persone.map(function(persona){
const id = persona.id;
const nome = persona.nome;
return { id: id, nome: nome }

})
       
response.json(filtroProprietaPersone);
})
...

Per farlo, essendo il dato JSON un array di oggetti, possiamo utilizzare il metodo ".map()" appartenente agli array stessi.

Quello che facciamo è recuperare per ogni oggetto "persona" l'id e il nome dal dato JSON totale e fare in modo che ci venga restituito, con "return", un nuovo oggetto JSON contenente le stesse "persone" ma con solo le proprietà richieste.

Stiamo di fatto creando una copia del dato JSON principale omettendo delle proprietà che non sono di nostro interesse.

Chiaramente sullo schermo, mediante "response.json()", manderemo il nuovo oggetto.

Volendo potremo utilizzareuna sintassi più breve, potremmo utilizzare le arrow-fuction; mentre per una sintassi più diretta potremo utilizzare una nuova sintassi, di decostruzione, che ci consente di spacchettare direttamente i dati che ci interessano da un dato JSON:

...
app.get('/soloidenome', function(request, response){

const filtroProprietaPersone = persone.map((persona)=>{
const { id, nome } = persona
return {  id, nome }
})
       
response.json(filtroProprietaPersone);
})
...

con "const { id, nome } = persona"
stiamo dicendo di creare due costanti, omonime delle proprietà presenti nel dato "persona" (singolo oggetto del dato JSON), e valorizzale con il contenuto di quest'ultime.

Restituire una o più corrispondenza

Con il mapping siamo riusciti ad ottenere un dato JSON con le sole proprietà che ci interessano; ma se volessimo ci fosse restituito un solo oggetto "persona", o solo degli oggetti "persona" che tra le loro proprietà hanno determinati valori?
Cosi come abbiamo fatto per il mapping attraverso il metodo ".filter()" degli array possiamo gestire la risposta del server in modo che ci restituisca l'elemento richiesto:

var express = require('express');
var app = express();
var persone = require('./persone');

app.get('/persona/id1', function(request, response){
    var personaFiltrataId1 = persone.filter(function(persona){
    return persona.id === "1";
    });
response.json(personaFiltrataId1);

    });

app.listen(3000);

Aggiungiamo al nostro modulo "persone.js" ulteriori oggetti di utenti con il nome "Andrea":

...
...
{
    id: "5",
    nome: "Andrea",
    cognome: "Andreani"
    },
    {
        id: "6",
        nome: "Andrea",
        cognome: "Giallo"
        },
        {
            id: "7",
            nome: "Andrea",
            cognome: "Rosso"
            },
            {
                id: "8",
                nome: "Andrea",
                cognome: "Verde"
                },
                {
                    id: "9",
                    nome: "Andrea",
                    cognome: "Blue"
                    },
                    {
                        id: "10",
                        nome: "Andrea",
                        cognome: "Grigio"
                        }

    ]
    
    module.exports = persone;

Possiamo utilizzare sempre il metodo ".filter()" per farci restituire tutti gli oggetti/utenti con nome "Andrea":

app.get('/personeconnomeandrea', function(request,response){
    var personeAndrea = persone.filter(function(persona){
    //var id = persona.id;
    //var nome = persona.nome;
    //var cognome = persona.cognome;
    const { id,nome,cognome } = persona;
        if(persona.nome ==="Andrea"){
            //return { id:id,nome:nome,cognome:cognome }
            return { id,nome,cognome };
        }
    });
    response.json(personeAndrea);
})

Route Param

Seppur la logica con cui abbiamo realizzato operazioni di mappatura e filtratura risulta corretta, così com'è, ci costringe a gestire il tutto manualmente mettendo ogni volta mani al codice.
Allo stato attuale queste soluzione risultano poco scalabili; dobbiamo adottare il principio di programmazione basato sull'astrazione che ci consente di raggiungere un risultato valido per tutte le occorrenze possibili.
In soccorso ci vengono incontro i "Route Params".

...
app.get('/persone/:id',function(request,response){
console.log(request.params);
});
...

I ":" indicano che il valore che sarà passato sarà un valore generico/dinamico che può quindi variare.
Attraverso la proprietà della richiesta "request.params" possiamo recuperare e visualizzare il valore del parametro inserito all'interno della Node.js CLI (Command-line Interface)

http://localhost:3000/persona/2

Restituzione di una corrispondenza specifica

Quello che è restituito è un oggetto la cui proprietà è in nome attribuito dopo i ":" ed il valore è quello contenuto nell'url.
A questo punto abbiamo tutti i mezzi per gestire il tutto dinamicamente e in maniera scalabile:

app.get('/utente/:id', function(request,response){
    var {id} = request.params;
    // var id = request.params.id;
    console.log(id);

    var personaTrovata = persone.find(function(persona){
       return persona.id === id;
    })

    response.json(personaTrovata);

})

In questo esempio, per il nostro scopo, abbiamo utilizzato il metodo ".find()" appartenente agli array

Gestire non-corrispondenza

Dobbiamo inevitabilmente gestire la non-corrispondenza, ovvero l'evento in qui non viene trovato alcun valore.
Gestiremo, l'error-handing, con una semplice istruzione if e alcune proprietà della "response":

Error-Handing: Gestiore degli errori
Statement & Single Statment: Dichiarazione; Singola dichiarazione.
if(!personaTrovata){
        return response.status(404).send("Utente non trovato");
        //oppure
        //return response.send({messagge: "Utente non trovato", code: 404});
        //oppure
        //return response.json({messagge: "Utente non trovato", code: 404});
    }else{
        response.json(personaTrovata);
    }

Con l'istruzione if-else verifichiamo che "personaTrovata" non esista, e che il suo valore sia "undefined"; se il valore è "undefined" impostiamo lo stato della risposta, con il metodo ".status()", su "404" (not found) e con il metodo ".send()", restituiamo un messaggio sotto forma di stringa.
Un'alternativa è quella di restituire direttamente un oggetto con delle proprietà, contenenti il messaggio ed il codice di errore, da poter gestire liberamente front-end.

Concatenazione di corrispondenze

Con "Route Params" possiamo gestire una concatenazione di corrispondenze.
Cominciamo con il prendere per esempio un modulo js al cui interno è presente un elenco di prodotti:

var prodotti = [
    {
        id: "1",  
        titolo: "Iphone 13 PRO",
        prezzo: 750,
        recensioni: [
            {
            idReview: "1",
            utente: "utente001",
            content: "Iphone 13 PRO Ottimo prodotto"
            },
            {
            idReview: "2",
            utente: "utente002",
            content: "Iphone 13 PRO Dicreto prodotto"
            },
            {
            idReview: "3",
            utente: "utente003",
            content: "Iphone 13 PRO Non soddisfacente"
            }
        ]
    },
    {
        id: "2",  
        titolo: "Redmi note 11R",
        prezzo: 450,
        recensioni: [
            {
            idReview: "1",
            utente: "utente001",
            content: "Redmi note 11R Ottimo prodotto"
            },
            {
            idReview: "2",
            utente: "utente002",
            content: "Redmi note 11R Dicreto prodotto"
            },
            {
            idReview: "3",
            utente: "utente003",
            content: "Redmi note 11R Non soddisfacente"
            }
        ]
    }
]

module.exports=prodotti;

Iniziamo con il gestire la root principale ed un url-relativo che ci consenta di visualizzare tutti i prodotti disponibili

const express = require('express');
const prodotti = require('./prodotti');
const app = express();

app.get('/', function(request,response){
    response.send("<a href='/prodotti'>Lista prodotti</a>");
})

app.get('/prodotti', function(request,response){
    response.json(prodotti);
})

app.listen(3000);

provvediamo quindi ad effettuare il primo routing-params accodando un attributo dinamico all'url contenente tutti prodotti

app.get('/prodotti/:id', function(request,response){
    var {id} = request.params;
    var prodottoTrovato = prodotti.find(function(prodotto){
        return prodotto.id === id;
    })
    response.json(prodottoTrovato);
})

ora provvediamo a mostrare tutte le recensioni di un prodotto specifico

app.get('/prodotti/:id/recensioni/', function(request,response){
    var {id} = request.params;
    var prodottoTrovato = prodotti.find(function(prodotto){
        return prodotto.id === id;
    })

var recensioniProdotto = prodottoTrovato.recensioni;

response.json(recensioniProdotto);

})

trattandosi di una nuova richiesta abbiamo dovuto rigestire il route-params ":id" prima di procedere al mapping dell'oggetto individuato.

Chiarimento metodo .map()

Gli oggetti "{}" in JavaScript non hanno il metodo ".map()", questo è disponibile solo per gli array "[]".

Potremmo trovarci spesso di fronte a mal funzionamenti, ingannati dal fatto che gli oggetti in JS possono sembrare talune volte simili a degli array.

Se ri-analizziamo il modulo "prodotti.js" poniamo l'attenzione sul fatto che esso inizia con una dichiarazione di una variabile "prodotti" che contiene tutto il contenuto all'interno di un array.

Alcune possibili soluzioni, quando ci troviamo di fronte ad un oggetto la cui proprietà contiene un array. è quella di accedere direttamente alla proprietà che contiene l'array prima di iterarla:

prodottoTrovato.recensioni.map(...);

Un alternativa è rappresentata dal convertire l'oggetto in un array ingabbiandolo all'interno di parentesi quadre "[ ]" o utilizzando il metodo ".from()" del costruttore, oggetto predefinito, "Array".

[oggettoTrovato].map(...);
//oppure
Array.from(oggettoTrovato);

Nonostante queste soluzione e bene tenere presente che di fronte a più concatenazione di istruzione potremmo comunque cadere in errore.
E' bene quindi esaminare con attenzione la struttura dei dati JSON che ci sono restituiti utilizzando gli strumenti di debuging come lo stesso "console.log()"

Terminato

Vediamo ora come mostrare solo recensioni specifiche dell'oggetto:

app.get('/prodotti/:id/recensioni/:idReview', function(request,response){
    
    var {id} = request.params;
    var prodottoTrovato = prodotti.find(function(prodotto){
        return prodotto.id === id;
    })
    
    var recensioniProdotto = prodottoTrovato.recensioni;
    
    var {idReview} = request.params;
  
    var recensioneSpecifica = recensioniProdotto.find(function(recensione){
        return recensione.idReview === idReview;

    })
    response.json(recensioneSpecifica);

})

Quello che emerge è che ad ogni query string composita dobbiamo gestire un blocco "app.get()" ripetendo talune volte blocchi di gestione dei frammenti che compongono l'url della richiesta.

In realtà non è così, ci sarà sufficiente omettere la parola chiave "var" in modo da definire le variabili a livello globale; così facendo, ci sarà consentito di riutilizzarle nelle varie richieste senza doverle recuperarle e ridefinire ad ogni gestione.

const express = require('express');
const prodotti = require('./prodotti');
const app = express();

app.get('/', function(request,response){
    response.send("<a href='/prodotti'>Lista prodotti</a>");
})

app.get('/prodotti', function(request,response){
    response.json(prodotti);
})

app.get('/prodotti/:id', function(request,response){
    var {id} = request.params;
    prodottoTrovato = prodotti.find(function(prodotto){
        return prodotto.id === id;
    })
    response.json(prodottoTrovato);
})

app.get('/prodotti/:id/recensioni/', function(request,response){
recensioniProdotto = prodottoTrovato.recensioni;
response.json(recensioniProdotto);
})


app.get('/prodotti/:id/recensioni/:idReview', function(request,response){
var {idReview} = request.params;
console.log(idReview);
console.log(recensioniProdotto);

recensioneSpecifica = recensioniProdotto.find(function(recensione){
return recensione.idReview === idReview;
})
response.json(recensioneSpecifica);
})

app.listen(3000);