Blog

Salvare i dati in un database MongoDb

Salvare i dati in un database mongodb

Tempo di lettura: 24 minuti

Nella scorsa guida abbiamo servito la nostra applicazione su Heroku, tuttavia questa ancora mostra dei dati finti, statici. E’ giunto, quindi il momento, di salvarli in un database. Nello specifico vedremo come salvare i dati in un database MongoDb.

Debug dell’applicazione

Prima di proseguire vi  ricordo che ogni buon sviluppatore non può far meno di eseguire il debug delle applicazioni, per prima cosa non può esimersi dall’ utilizzare il fatidico console.log() il quale può fornire tante risposte ai nostri dubbi 🙂 🙂 .

Eseguire il debug su Visual Studio Code

Eseguire il debug su Visual Studio Code è molto semplice in quanto basta fare click su:

debug su visual studio code

quindi in questo caso visto che si tratta di un’applicazione NodeJs scegli:

debug su visual studio code

se aprite il terminale noterete che il server si avvia alla porta 3001 in modalità debug:

console di debug

quindi aprite il browser all’indirizzo http://localhost:3001:

console di debug

noterete che la console cambia restituendovi i dettagli delle operazioni fatte, nello specifico i metodi GET dell’api interrogata. In questo caso non ci sono errori, nel caso contrario questi sarebbero stati messi in evidenza nei punti come da immagine:

console di debug

Per istruzioni di configurazione più dettagliate, visita la documentazione di debug di Visual Studio Code .

Nota che l’applicazione non dovrebbe essere in esecuzione su un’altra console, altrimenti la porta sarà già in uso.

NB Una versione più recente di Visual Studio Code potrebbe avere Run invece di Debug . Inoltre, potrebbe essere necessario configurare il file launch.json per avviare il debug. Questo può essere fatto scegliendo Aggiungi configurazione … nel menu a tendina, che si trova accanto al pulsante verde di riproduzione e sopra il menu VARIABILI , e seleziona Esegui “npm start” in un terminale di debug . Per istruzioni di configurazione più dettagliate, visita la documentazione di debug di Visual Studio Code .

Di seguito puoi vedere uno screenshot in cui l’esecuzione del codice è stata sospesa durante il salvataggio di una nuovo eroe:

Strumenti di sviluppo di Chrome

Tuttavia io personalmente preferisco eseguire il debug mediante la console per sviluppatori di Chrome, avviate l’applicazione con il comando:

node --inspect index.js

quindi apri il browser ed accedi al debugger mediante l’iconcina verde a sinistra che appare nella console per gli sviluppatori di Chome come in figura:

Salvare i dati un database MongoDb

La vista di debug funziona allo stesso modo delle applicazioni React. La scheda Source può essere utilizzata per impostare i punti di interruzione in cui l’esecuzione del codice verrà sospesa.

Salvare i dati un database MongoDb

 

Salvare i dati in un database mongodb

MongoDb è un database di documenti, non relazionale. I database di documenti differiscono dai database relazionali per il modo in cui organizzano i dati e per i linguaggi di query supportati. Questi sono generalmente classificati sotto il termine generico NoSQL .

Anche se potremmo installare ed eseguire MongoDB sul nostro computer, in questo tutorial utilizzeremo  provider MongoDB MongoDB Atlas .

Dopo aver creato ed effettuato l’accesso al tuo account, Atlas ti consiglierà di creare un cluster:

Salvare i dati un database MongoDb

Nello step successivo:

Salvare i dati un database MongoDb

 

Successivamente scegliete AWS come provider e Francoforte come regione e creiamo un cluster, come in figura:

Salvare i dati un database MongoDb

 

Aspettiamo che il cluster sia pronto per l’uso. Questo può richiedere circa 10 minuti.

Salvare i dati un database MongoDb

NB non continuare prima che il cluster sia pronto.

Usiamo la scheda di accesso al database per creare le credenziali utente per il database. Tieni presente che queste non sono le stesse credenziali che utilizzi per accedere a MongoDB Atlas. Questi verranno utilizzati per consentire alla tua applicazione di connettersi al database.

Salvare i dati un database MongoDb

 

Concediamo all’utente i permessi per leggere e scrivere nei database.

Salvare i dati un database MongoDb

Successivamente dobbiamo definire gli indirizzi IP a cui è consentito l’accesso al database.

Salvare i dati un database MongoDb

 

Per semplicità consentiremo l’accesso da tutti gli indirizzi IP:

Salvare i dati un database MongoDb

Finalmente siamo pronti per connetterci al nostro database, molto semplicemente fai click su Connect:

Salvare i dati un database MongoDb

quindi nello step successivo scegli di connettere la tua applicazione:

Salvare i dati un database MongoDb

ancora quindi copia i parametri di connessione da incollare successivamente nella nostra applicazione:

Salvare i dati un database MongoDb

successivamente sostituiremo <password> e </dbname> con la nostra password ed il nome del db 😉

Salvare i dati in un database mongodb: pronti per utilizzare il db

Potremmo usare il database direttamente dal nostro codice JavaScript con la libreria dei driver MongoDb Node.js , ma è piuttosto complicato da usare. Useremo invece la libreria Mongoose che offre un’API di livello superiore.

Mongoose potrebbe essere descritto come un ODM ( Object Document Mapper ) e il salvataggio di oggetti JavaScript come documenti Mongo è semplice con questa libreria.

Installiamo Mongoose:

npm install mongoose

nella directory radice del progetto crea il file mongo.js ed inserisci:

const mongoose = require('mongoose')

if (process.argv.length < 3) {
    console.log('Please provide the password as an argument: node mongo.js <password>')
    process.exit(1)
}

const password = process.argv[2]


const url =
    `mongodb+srv://lucioTicali:${password}@cluster0.pwxkn.mongodb.net/hero-db?retryWrites=true&w=majority`

mongoose.connect(url, { useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false, useCreateIndex: true })

const heroSchema = new mongoose.Schema({
    name: String,
    date: Date,
    important: Boolean,
})

const Hero = mongoose.model('Hero', heroSchema)

const hero = new Hero({
    name: 'Batman',
    date: new Date(),
    important: true,
})

hero.save().then(result => {
    console.log('hero saved!')
    mongoose.connection.close()
})

quindi avviatelo con node mediante :

node mongo.js <tuapassword>

quindi aprite mongoDb, cluster/collections:

e vi trovate:

Salvare i dati un database MongoDb

I dati sono ora archiviati nel database corretto. La vista offre anche la funzionalità di creazione del database , che può essere utilizzata per crearne dei  nuovi . La creazione del database in questo modo non è necessaria, poiché MongoDB Atlas lo crea automaticamente quando un’applicazione tenta di connettersi ad uno non esistente.

Salvare i dati in un database mongodb: Spiegazione codice mongo.js

Dopo aver stabilito la connessione al database, definiamo lo schema per un eroe e il modello di corrispondenza :

const heroSchema = new mongoose.Schema({
    name: String,
    date: Date,
    important: Boolean,
})

const Hero = mongoose.model('Hero', heroSchema)

Per prima cosa definiamo lo schema di un eroe che viene memorizzata nella variabile heroSchema . Lo schema dice a Mongoose come memorizzare gli oggetti eroi nel database.

Nella definizione del modello Hero , il primo parametro “Hero” è il nome singolare del modello. Il nome della raccolta saranno gli eroi al plurale minuscole , perché la convenzione Mongoose è di denominare automaticamente le raccolte come plurale (ad esempio heroes ) quando lo schema si riferisce ad esse al singolare (ad esempio Hero ).

I database di documenti come Mongo sono privi di schemi , il che significa che il database stesso non si preoccupa della struttura dei dati archiviati nel database. È possibile archiviare documenti con campi completamente diversi nella stessa raccolta.

L’idea alla base di Mongoose è che ai dati archiviati nel database viene fornito uno schema a livello dell’applicazione che definisce la forma dei documenti archiviati in una data raccolta.

Creare e salvare gli oggetti

Successivamente, l’applicazione crea un nuovo oggetto hero con l’aiuto del modello Hero:

const hero = new Hero({
    name: 'Batman',
    date: new Date(),
    important: true,
})

I modelli sono le cosiddette funzioni di costruzione che creano nuovi oggetti JavaScript in base ai parametri forniti. Poiché gli oggetti vengono creati con la funzione di costruzione del modello, hanno tutte le proprietà del modello, che includono metodi per salvare l’oggetto nel database.

Il salvataggio dell’oggetto nel database avviene con il metodo save opportunamente denominato , che può essere fornito con un gestore di eventi con il metodo then :

hero.save().then(result => {
    console.log('hero saved!')
    mongoose.connection.close()
})

Quando l’oggetto viene salvato nel database, il gestore di eventi fornito da allora viene chiamato. Il gestore eventi chiude la connessione al database con il comando mongoose.connection.close(). Se la connessione non viene chiusa, il programma non terminerà mai la sua esecuzione.

Il risultato dell’operazione di salvataggio si trova nel parametro result del gestore eventi. Il risultato non è così interessante quando memorizziamo un oggetto nel database. È possibile stampare l’oggetto sulla console se si desidera esaminarlo più da vicino durante l’implementazione dell’applicazione o durante il debug.

Salviamo anche qualche eroe in più modificando i dati nel codice ed eseguendo nuovamente il programma.

NB: Sfortunatamente la documentazione di Mongoose non è molto coerente, con parti di essa che utilizzano callback nei suoi esempi e altre parti, altri stili, quindi non è consigliabile copiare il codice incollato direttamente da lì. Non è consigliabile combinare promesse con callback della vecchia scuola nello stesso codice.

Recupero di oggetti dal database

Commentiamo il codice per la generazione di nuovi eroi e lo sostituiamo con il seguente:

...
// hero.save().then(result => {
//     console.log('hero saved!')
//     mongoose.connection.close()
// })
Hero.find({}).then(result => {
    result.forEach(hero => {
        console.log(hero)
    })
    mongoose.connection.close()
})
...

eseguire il codice:

node mongo <tuapassword>

noterete che il programma stamperà tutti gli eroi memorizzati nel db:

Gli oggetti vengono recuperati dal database con il metodo find del modello Hero . Il parametro del metodo è un oggetto che esprime le condizioni di ricerca. Poiché il parametro è un oggetto vuoto {}, otteniamo tutte gli eroi memorizzate nella raccolta di hero.

Le condizioni di ricerca aderiscono alla sintassi della query di ricerca Mongo .

Potremmo limitare la nostra ricerca per includere solo eroi importanti come questa:

Hero.find({important: true}).then(result => {
...
})

Backend connesso al database mongoDb

Ora che abbiamo capito come connetterci al database di dati di MongoDb copiamo le definizioni di mongo.js nel file index.js:

...
const mongoose = require('mongoose')
const password = 'secret'
const url =
    `mongodb+srv://lucioTicali:${password}@cluster0.pwxkn.mongodb.net/hero-db?retryWrites=true&w=majority`

mongoose.connect(url, { useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false, useCreateIndex: true })

const heroSchema = new mongoose.Schema({
    name: String,
    date: Date,
    important: Boolean,
})

const Hero = mongoose.model('Hero', heroSchema)
...

aggiorna il gestore per il recupero di tutti gli eroi nel seguente modo:

...
app.get('/api/heroes', (request, response) => {
    Hero.find({}).then(heroes => {
      response.json(heroes)  
    })    
})
...

file index.js completo:

const express = require('express')
const app = express();
app.use(express.json())
app.use(express.static('build'))
const cors = require('cors')
app.use(cors())
const requestLogger = (request, response, next) => {
    console.log('Method:', request.method)
    console.log('Path:  ', request.path)
    console.log('Body:  ', request.body)
    console.log('---')
    next()
}
const unknownEndpoint = (request, response) => {
    response.status(404).send({ error: 'unknown endpoint' })
}

app.use(requestLogger)

app.use(requestLogger)
// let heroes = [
//     {
//         "id": 1,
//         "name": "Captain America",
//         "important":true
//     },
//     {
//         "id": 2,
//         "name": "Iron Man",
//         "important": true
//     },
//     {
//         "id": 3,
//         "name": "Hulk",
//         "important": false
//     }
// ]
const mongoose = require('mongoose')
const password = 'secret'
const url =
    `mongodb+srv://lucioTicali:${password}@cluster0.pwxkn.mongodb.net/hero-db?retryWrites=true&w=majority`

mongoose.connect(url, { useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false, useCreateIndex: true })

const heroSchema = new mongoose.Schema({
    name: String,
    date: Date,
    important: Boolean,
})

const Hero = mongoose.model('Hero', heroSchema)

app.get('/', (request, response) => {
    response.send('<h1>Hello world</h1>')
})

app.get('/api/heroes', (request, response) => {
    Hero.find({}).then(heroes => {
      response.json(heroes)  
    })    
})
app.get('/api/heroes/:id', (request, response) => {
    const id = Number(request.params.id)
    const hero = heroes.find(hero => hero.id === id)
    if(hero){
       response.json(hero) 
    } else {
        response.status(404).end()
    }
    
})
const generateId =() => {
    const maxId = heroes.length > 0 ? Math.max(...heroes.map(h => h.id)): 0
    return maxId + 1
}
app.post('/api/heroes', (request, response) => {
    const body = request.body
    if(!body.name){
        return response.status(404).json({
            error: 'Contenuto vuoto'
        })
    }

    const hero = {
        name: body.name,
        important: body.important || false,
        date: new Date(),
        id: generateId(),
    }

    heroes = heroes.concat(hero)

    response.json(hero)
})
app.delete('/api/heroes/:id', (request, response)=> {
    const id = Number(request.params.id)
    heroes = heroes.filter(hero => hero.id === id)
    response.status(204).end()
})
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`)})

ora avviate l’applicazione ed apritela nel browser http://localhost:3001/api/heroes :

l’applicazione risponde quasi perfettamente 🙂 🙂 .

Tuttavia bisogna migliorare ancora qualche cosa, difatti non vogliamo che Mongo restituisca il campo con la versione dello stesso –v e vogliamo, anche far si, che il campo ID sia di tipo stringa, non un oggetto ed univoco.

Un modo per formattare gli oggetti restituiti da Mongoose è modificare il metodo toJSON dello schema, che viene utilizzato su tutte le istanze dei modelli prodotti con quello schema. La modifica del metodo funziona in questo modo:

...
heroSchema.set('toJSON', {
    transform: (document, returnedObject) => {
        returnedObject.id = returnedObject._id.toString()
        delete returnedObject.__v
        delete returnedObject._id
    }
})
...

Anche se la proprietà _id degli oggetti Mongoose sembra una stringa, in realtà è un oggetto. Il metodo toJSON che abbiamo definito lo trasforma in una stringa solo per sicurezza. Se non apportassimo questa modifica, ci causerebbe più danni in futuro una volta che avremo iniziato a scrivere i test.

Rispondiamo alla richiesta HTTP con un elenco di oggetti formattati con il metodo toJSON :

...
app.get('/api/heroes', (request, response) => {
    Hero.find({}).then(heroes => {
      response.json(heroes)  
    })    
})
...

Ora la variabile heroes è assegnata a un array di oggetti restituiti da Mongo. Quando la risposta viene inviata nel formato JSON, il metodo toJSON di ogni oggetto nell’array viene chiamato automaticamente dal metodo JSON.stringify .

Salvare i dati un database mongodb: configurare il database in un proprio modulo

Prima di eseguire il refactoring del resto del backend per utilizzare il database, estraiamo il codice specifico di Mongoose nel suo modulo.

Creiamo una nuova directory per il modulo chiamato models e aggiungiamo un file chiamato hero.js :

const mongoose = require('mongoose')

const url = process.env.MONGODB_URI

console.log('connecting to', url)

mongoose.connect(url, { useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false, useCreateIndex: true })
    .then(result => {
        console.log('connected to MongoDB')
    })
    .catch((error) => {
        console.log('error connecting to MongoDB:', error.message)
    })

const heroSchema = new mongoose.Schema({
    name: String,
    date: Date,
    important: Boolean,
})

heroSchema.set('toJSON', {
    transform: (document, returnedObject) => {
        returnedObject.id = returnedObject._id.toString()
        delete returnedObject._id
        delete returnedObject.__v
    }
})

module.exports = mongoose.model('Hero', heroSchema)

 

L’interfaccia pubblica del modulo viene definita impostando un valore sulla variabile module.exports . Impostiamo il valore come modello Hero . Le altre cose definite all’interno del modulo, come le variabili mongoose e url non saranno accessibili o visibili agli utenti del modulo.

L’importazione del modulo avviene aggiungendo la seguente riga a index.js :

...
const Hero = require('./models/hero')
...

In questo modo la variabile Hero verrà assegnata allo stesso oggetto definito dal modulo.

Il modo in cui viene effettuata la connessione è leggermente cambiato:

..
const url = process.env.MONGODB_URI
console.log('Connecting to' , url)

mongoose.connect(url, { useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false, useCreateIndex: true })
.then(result => {
    console.log('connect to MongoDb')
})
.catch((error) => {
    console.log('error connecting to MongoDb:', error.message)
})
...

Non è una mai  buona idea codificare l’indirizzo del database nel codice, quindi l’indirizzo del database viene passato all’applicazione tramite la variabile d’ambiente MONGODB_URI .

Al metodo per stabilire la connessione vengono ora fornite le funzioni per gestire un tentativo di connessione riuscito e non riuscito. Entrambe le funzioni registrano semplicemente un messaggio sulla console sullo stato di successo:

Definire la variabile d’ambiente

Esistono diversi modi per definire il valore di una variabile di ambiente. Uno sarebbe quello di  definirla all’avvio dell’applicazione:

MONGODB_URI=address_here npm run dev

ma un modo , a mio avviso, più comodo è sicuramente quello di utilizzare la libreria dotenv. La puoi installare nel seguente modo:

npm install dotenv

ora per utilizzarla , nella root radice del progetto, crea il file .env (chi conosce Laravel sa di cosa parlo) e definisci:

MONGODB_URI ='mongodb+srv://lucioTicali:tuapassword@cluster0.pwxkn.mongodb.net/hero-db?retryWrites=true&w=majority'
PORT = 3001

quindi importa nel file index.js :

require('dotenv').config()
const express = require('express')
const app = express();
app.use(express.json())
...

Le variabili di ambiente definite nel file .env possono essere utilizzate con l’espressione require (‘dotenv’). Config () e puoi fare riferimento ad esse nel tuo codice proprio come faresti con normali variabili di ambiente, con il familiare process.env Sintassi .MONGODB_URI .

Cambia il file index.js così:

require('dotenv').config()
const express = require('express')
const app = express()
const Hero = require('./models/hero')

// ..

const PORT = process.env.PORT
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`)
})

 

Inoltre abbiamo anche aggiunto la porta hardcoded del server nella variabile d’ambiente PORT .

È importante che dotenv venga importato prima dell’importazione del modello di eroe. Ciò garantisce che le variabili di ambiente del file .env siano disponibili globalmente prima che il codice dagli altri moduli venga importato.

Utilizzare il database nei gestori di rotte

Cambiamo il resto della funzionalità del backend per utilizzare il database.

La creazione di una nuovo eroe viene eseguita in questo modo:

app.post('/api/heroes', (request, response) => {
    const body = request.body
    if(body.name === undefined){
        return response.status(404).json({
            error: 'Contenuto vuoto'
        })
    }

    const hero = new Hero({
        name: body.name,
        important: body.important || false,
        date: new Date()
    })

    hero.save().then(savedHeroe => {
        response.json(hero)
    })

Gli oggetti hero vengono creati con la funzione di costruzione Hero . La risposta viene inviata all’interno della funzione di callback per l’ operazione di salvataggio . Ciò garantisce che la risposta venga inviata solo se l’operazione è riuscita. Discuteremo più tardi la gestione degli errori.

Il parametro savedHero nella funzione di callback è l’eroe salvato e appena creato. I dati restituiti nella risposta sono la versione formattata creata con il metodo toJSON :

response.json(savedHeroe)

Per quanto riguarda il recupero di un singolo eroe procediamo nel seguente modo:

app.get('/api/heroes/:id', (request, response) => {
    Hero.findById(request.params.id).then(h =>{
        response.json(h)
    })
    
})

come potete notare utilizzo il metodo findById di javascript.

Testiamo l’applicazione con Postman

Prima di proseguire è buona norma testare backend e frontend con Postman o client Rest di Vs Code.

Facciamolo con client Rest:

nel mio caso il metodo get funziona alla grande.

Prima di testare il metodo per ricevere un singolo eroe createvi un file sotto la directory request e chiamatelo get_id_heroes.rest, quindi aggiungete:

GET http://localhost:3001/api/heroes/<idHero>

chiaramente inserite il vostro id dopo heroes/, quindi:

anche questo funziona perfettamente 🙂 🙂

Testaimo il metodo Post con Postman come di seguito:

complimenti hai appena aggiunto un Eroe !!

Apri il tuo cluster su MongoDb e verifica di persona:

Gestione degli errori

Se proviamo a visitare l’URL di un eroe con un ID che in realtà non esiste, ad esempio http: // localhost: 3001 / api / heroes/ 5c41c90e84d891c15dfa3431 dove 5c41c90e84d891c15dfa3431 non è un ID memorizzato nel database, la risposta sarà nulla .

Cambiamo questo comportamento in modo che se l’eroe con l’id dato non esiste, il server risponderà alla richiesta con il codice di stato HTTP 404 non trovato. Inoltre implementiamo un semplice blocco catch per gestire i casi in cui la promessa restituita dal metodo findById viene rifiutata :

...
app.get('/api/heroes/:id', (request, response) => {
    Hero.findById(request.params.id).then(h =>{
        if(h){
         response.json(h)   
        }else {
            response.status(404).end()
        }      
    })
    .catch(error => {
        console.log(error)
        response.status(500).end()
    })
...

Se nel database non viene trovato alcun oggetto corrispondente, il valore di hero sarà nullo e verrà eseguito il blocco else . Ciò si traduce in una risposta con il codice di stato 404 non trovato . Se la promessa restituita dal metodo findById viene rifiutata, la risposta avrà il codice di stato 500 errore interno del server . La console visualizza informazioni più dettagliate sull’errore.

Oltre all’eroe inesistente, è necessario gestire un’altra situazione di errore. In questa situazione, stiamo cercando di recuperare un eroe con un tipo di ID sbagliato , ovvero un ID che non corrisponde al formato dell’identificatore mongo.

Se effettuiamo la seguente richiesta, riceveremo il messaggio di errore mostrato di seguito:

Dato un id non valido come argomento, il metodo findById genererà un errore che causa il rifiuto della promessa restituita. Ciò causerà la chiamata della funzione di callback definita nel blocco catch .

Apportiamo alcune piccole modifiche alla risposta nel blocco catch :

...
    .catch(error => {
        console.log(error)
        response.status(400).send({error: 'malformatted id'})
    })
...

Se il formato dell’id non è corretto, finiremo nel gestore degli errori definito nel blocco catch . Il codice di stato appropriato per la situazione è 400 Bad Request , perché la situazione si adatta perfettamente alla descrizione:

La richiesta non è stata compresa dal server a causa di una sintassi non corretta. Il cliente NON DOVREBBE ripetere la richiesta senza modifiche.

Abbiamo anche aggiunto alcuni dati alla risposta per far luce sulla causa dell’errore.

Quando si ha a che fare con Promises, è quasi sempre una buona idea aggiungere la gestione degli errori e delle eccezioni, perché altrimenti ti ritroverai a dover affrontare strani bug.

Non è mai una cattiva idea stampare l’oggetto che ha causato l’eccezione alla console nel gestore degli errori:

.catch(error => {
  console.log(error)
  response.status(400).send({ error: 'malformatted id' })
})

Il motivo per cui viene chiamato il gestore degli errori potrebbe essere qualcosa di completamente diverso da quello che avevi previsto. Se registri l’errore sulla console, potresti salvarti da lunghe e frustranti sessioni di debug. Inoltre, la maggior parte dei servizi moderni in cui distribuisci la tua applicazione supporta una qualche forma di sistema di registrazione che puoi utilizzare per controllare questi registri. Heroku lo fa.

Ogni volta che lavori su un progetto con un backend, è fondamentale tenere d’occhio l’output della console del backend . Se stai lavorando su uno schermo piccolo, è sufficiente vedere solo una piccola fetta dell’output in background. Eventuali messaggi di errore attireranno la tua attenzione anche quando la console è molto indietro in background:

Gestione degli errori nel middleware

Fino ad ora abbiamo implementato il codice per ila gestione degli errori all’interno del nostro codice. Questa a volte potrebbe essere una soluzione, comunque è meglio implementarlo in un file suo. Ciò può essere particolarmente utile se in seguito si desidera segnalare i dati relativi agli errori a un sistema di tracciamento degli errori esterno come Sentry .

Cambiamo il gestore per la rotta / api / heroes /: id , in modo che passi l’errore in avanti con la funzione successiva . La funzione successiva viene passata al gestore come terzo parametro:

...
app.get('/api/heroes/:id', (request, response, next) => {
    Hero.findById(request.params.id).then(h =>{
        if(h){
         response.json(h)   
        }else {
            response.status(404).end()
        }      
    })
    .catch(error => next(error))   
})
...

L’errore passato in avanti viene assegnato alla funzione successiva come parametro. Se next è stato chiamato senza un parametro, l’esecuzione si sposterebbe semplicemente sulla route o sul middleware successivo. Se la funzione successiva viene chiamata con un parametro, l’esecuzione continuerà al middleware del gestore degli errori .

gestori di errori Express sono middleware definiti con una funzione che accetta quattro parametri . Il nostro gestore degli errori ha questo aspetto:

const errorHandler = (error, request, response, next) => {
  console.error(error.message)

  if (error.name === 'CastError') {
    return response.status(400).send({ error: 'malformatted id' })
  } 

  next(error)
}

app.use(errorHandler)

Il gestore degli errori controlla se l’errore è un’eccezione CastError , nel qual caso sappiamo che l’errore è stato causato da un ID oggetto non valido per Mongo. In questa situazione il gestore degli errori invierà una risposta al browser con l’oggetto risposta passato come parametro. In tutte le altre situazioni di errore, il middleware trasmette l’errore al gestore degli errori Express predefinito.

L’ordine di caricamento del Middleware

L’ordine di esecuzione del middleware è lo stesso dell’ordine in cui vengono caricati in express con la funzione app.use . Per questo motivo è importante fare attenzione quando si definisce il middleware.

L’ordine corretto è il seguente:

app.post('/api/heroes', (request, response) => {
    const body = request.body
    ...
})
const unknownEndpoint = (request, response) => {
    response.status(404).send({ error: 'unknown endpoint' })
}
app.use(unknownEndpoint)

const errorHandler = (error, request, response, next) => {
   ...
}
app.use(errorHandler)

Il middleware json-parser dovrebbe essere tra i primissimi middleware caricato in Express. Se l’ordine era il seguente:

app.use(logger) // request.body is undefined!

app.post('/api/notes', (request, response) => {
  // request.body is undefined!
  const body = request.body
  // ...
})

app.use(express.json())

Quindi i dati JSON inviati con le richieste HTTP non sarebbero disponibili per il middleware del logger o per il gestore di route POST, poiché request.body a quel punto sarebbe indefinito .

È anche importante che il middleware per la gestione delle rotte non supportate si trovi vicino all’ultimo middleware caricato in Express, subito prima del gestore degli errori.

Ad esempio, il seguente ordine di caricamento potrebbe causare un problema:

const unknownEndpoint = (request, response) => {
  response.status(404).send({ error: 'unknown endpoint' })
}

// handler of requests with unknown endpoint
app.use(unknownEndpoint)

app.get('/api/notes', (request, response) => {
  // ...
})

Ora la gestione degli endpoint sconosciuti viene ordinata prima del gestore delle richieste HTTP . Poiché il gestore dell’endpoint sconosciuto risponde a tutte le richieste con 404 endpoint sconosciuto , nessuna route o middleware verrà chiamata dopo che la risposta è stata inviata dal middleware dell’endpoint sconosciuto. L’unica eccezione a ciò è il gestore degli errori che deve arrivare proprio alla fine, dopo il gestore degli endpoint sconosciuti.

Salvare i dati un database mongodb: aggiornamento eliminazione dato

Bene ora non ci resta che aggiornare il metodo di eliminazione di un eroe dalla lista. Il modo più semplice per farlo è grazie al metodo findByIdRemove:

app.delete('/api/heroes/:id', (request, response, next)=> {
    Hero.findByIdAndRemove(request.params.id)
    .then(result => {
        response.status(204).end()
    })
    .catch(error => next(error))    
})

Il backend risponde con il codice di stato 204 senza contenuto . I due diversi casi eliminano un eroe esistente e l’eliminazione di un eroe che non esiste nel database. Il parametro di callback dei risultati potrebbe essere utilizzato per verificare se una risorsa è stata effettivamente eliminata e potremmo utilizzare tali informazioni per restituire codici di stato diversi per i due casi se lo ritenessimo necessario. Qualsiasi eccezione che si verifica viene passata al gestore degli errori.

NB: ricorda di scrivere il metodo prima del gestore di errori middleware, ciò causerebbe un non funzionamento dello stesso

Aggiornare il metodo update dato

La modifica dell’importanza dell’eroe viene fatta mediante il metodo findByIdAndUpdate :

app.put('/api/heroes/:id', (request, response, next) => {
    const body = request.body

    const hero = {
        name: body.name,
        important: body.important,
    }

    Hero.findByIdAndUpdate(request.params.id, hero, { new: true })
        .then(updatedHero => {
            response.json(updatedHero)
        })
        .catch(error => next(error))
})

Da notare che il metodo findByIdAndUpdate riceve un normale oggetto JavaScript come parametro e non un nuovo oggetto eroe creato con la funzione di costruzione Hero.

C’è un dettaglio importante relativo all’uso del metodo findByIdAndUpdate . Per impostazione predefinita, il parametro updatedHero del gestore eventi riceve il documento originale senza le modifiche . Abbiamo aggiunto il { new: true }parametro facoltativo , che farà chiamare il nostro gestore di eventi con il nuovo documento modificato invece dell’originale.

Dopo aver testato il backend direttamente con Postman e il client REST di VS Code, possiamo verificare che funzioni. Il frontend sembra funzionare anche con il backend utilizzando il database.

Puoi trovare il codice per la nostra attuale applicazione nella sua interezza nel ramo part_4.1 di questo repository GitHub .

Caricate su heroku mediante i comandi:

npm run deploy

questo è il risultato https://intense-taiga-53023.herokuapp.com/, nel mio caso.

Riepilogo e saluti

Anche per oggi è tutto, nelle scorse lezioni abbiamo servito l’applicazione su Heroku, quindi in questo tutorial siamo riusciti a realizzare un database di dati su MongoDb ed a collegarlo all’App. Il risultato è una web App che funziona perfettamente in remoto, complimenti!!

Se volete approfondire gli argomenti e/o lo studio potete seguire i miei corsi su Udemy.

Se volete Approfondire le conoscenze su Git e GIT hub potete seguire il  corso completo su Udemy sempre scontato a 9.99/12.99 € , inoltre, nel momento in cui non foste soddisfatti, avete la possibilità di ottenere il rimborso completo dello stesso entro 30 giorni dalla data di acquisto!

Se volete Approfondire le conoscenze su REACT e REDUX potete seguire il  corso completo su Udemy sempre scontato a 9.99/12.99 € , inoltre, nel momento in cui non foste soddisfatti, avete la possibilità di ottenere il rimborso completo dello stesso entro 30 giorni dalla data di acquisto!

Se volete Approfondire le conoscenze su ES7 ed Angular potete seguire il  corso completo su Udemy sempre scontato a 9.99/12.99 € , inoltre, nel momento in cui non foste soddisfatti, avete la possibilità di ottenere il rimborso completo dello stesso entro 30 giorni dalla data di acquisto!

Share and Enjoy !

0Shares
0 0

Chi è ?

Lucio Ticali è un Web & App developer con la passione per il web marketing,si occupa anche di tecniche di indicizzazione del sito e di promozione dello stesso (SEO e SEM).

Lascia la tua opinione