Contattaci

Express.js e Mongoose

express e mongoose
  • Data: 19 Settembre 2022
  • Autore: Daniele Lo Preiato
  • Categorie

  • Giuneco Tech

    Il nostro settore richiede uno studio continuo e una forte attenzione all’innovazione. Incentiviamo quindi dei programmi formativi annuali per tutti i componenti del team, con ore dedicate (durante l’orario di lavoro) e serate formative sia online che in presenza. Sponsorizziamo eventi, sia come partner che semplicemente come partecipanti, e scriviamo articoli su quello che abbiamo imparato per essere, a nostra volta, dei divulgatori.

    Vai alla sezione Team
  • Applicazioni web ed API con minimal effort

    Express.js

    Cos’è Express.js?

    Express.js (o comunemente chiamato Express) è un framework back-end per Node.js, gratis ed open-source (licenza MIT). 

    Express è il framework “de-facto”  quando si tratta di sviluppare applicazioni web ed API, dimostrato dall’ampiezza dell’engagement della community a riguardo (11.3M di utilizzatori e 55k su GitHub) e dal Developer Survey annuale di StackOverflow (2021), dove è risultato il terzo web-framework più utilizzato (dietro solo a React.js – salito addirittura al 40% – e jQuery).

     

     

    Web framework
    Web framework più popolari (Dev Survey 2021 di StackOverflow)

    E’ notevole menzionare come Express venga usato (in produzione) da aziende come PayPal, Uber e IBM. 

    Come si usa Express?

    Express è molto semplice da utilizzare e fare il setup del backend di una web application o di una API minimale è, praticamente, effortless. 

    E’ necessario, però, disporre di Node e npm (node package manager). 


    Caveat: Installazione di Node e npm 

    Node e il suo package manager sono scaricabili da  https://nodejs.org/en/download/. 

    Una volta installato basta aprire un cmd (o terminale) ed eseguire: 

    node -v 

    npm -v 

    In modo da verificare che entrambi siano correttamente installati. 


    Per usare Express abbiamo bisogno una web application, possiamo quindi creare una nuova directory ed usare npm init  (-y per defaultare le scelte) così da creare un package.json. 

    Adesso possiamo usare npm install express –save per aggiungere Express alla web application e salvarlo tra le dipendenze necessarie. 

    Nella root della directory dove è stato fatto l’init della web app possiamo quindi creare l’entry point della web application, index.js, così che il project tree sia il seguente. 

    Aggiungendo un banale console.log(“Hello World”) nel nostro index.js e utilizzando in seguito il comando npm start, l’applicazione dovrebbe rispondere con un “Hello World!” nella console e terminare. 

    Abbiamo appena creato un’applicazone con Node! 🎉 

    Adesso dobbiamo usare Express!

    Torniamo nell’entry point della nostra web application e modifichiamolo come segue: 

    const express = require('express')
    const app = express()
    
    app.get('/', (req, res) =>{
    res.send("Hello world!");
    })
    
    app.listen(3000);

    Nelle righe sopra viene importato il package e creata un’istanza di express, in seguito aggiunto un endpoint sulla root (“/”) che risponde con un banale “Hello World!” e infine indicato che l’applicazione deve ascoltare richieste sulla porta 3000. 

    Se lanciamo nuovamente npm start (ndr. o node index.js) notiamo che in questo caso l’applicazione non termina, questo perché il server è in attesa di richieste. 

    Navigando su http://localhost:3000, il browser risponderà con “Hello World!”; ecco pronta la nostra web application con Node.js ed Express.js! 

    hello world

    Mongoose 

    Cos’è Moongoose?

    Mongoose è un ODM (Object Document Mapping) che fornisce un layer di astrazione sopra MongoDB. 

    Tramite Mongoose è possibile creare dei Modelli, che di fatto rappresentano i dati delle collection documentali e lanciare query in maniera molto più semplice rispetto ad usare le funzionalità native di MongoDB. 

    Mongoose Schemas (e Models) 

    Mongoose utilizza gli “Schemas” per modellare i dati di un’applicazione 

    Ogni schema definisce quali dati fanno parte di una determinata collection, il loro tipo, se sono dati obbligatori o opzionali, se sono dati con valore unico, etc. 

    Prima di continuare è necessario installare mongoose, e per farlo basta lanciare il comando npm install –save mongoose dalla root della web application creata in precedenza. 

    Creiamo adesso uno schema di esempio, all’interno della root della web application aggiungiamo una directory “models” e all’interno creiamo pet.model.js. 

    pet.model.js dovrà contenere ciò che segue: 

    const mongoose = require('mongoose');
    
    const petSchema = new mongoose.Schema({
      name: {
        type: String,
        required: true,
      },
      petType: {
        type: String,
        required: true,
      },
      petRace: {
        type: String,
        required: true,
      },
      dob: {
        type: Date,
        required: true,
      },
    });
    
    module.exports = mongoose.model('Pet', petSchema);
    

    Mongoose Model:  _id 

    Mongoose aggiunge automaticamente una proprietà _id che identifica un documento, quindi ogni volta che viene creato un nuovo documento viene automaticamente generato anche un _id di tipo ObjectId corrispondente. 

    Mongoose Model: Instance e Static methods, Query Helpers 

    Le istanze di un Model sono dette “documents” (mapping 1-1 ai documenti storati in MongoDB). 

    I “documents” hanno dei metodi built-in che permettono varie operazioni detti Instance Methods, utilizzabili su specifiche istanze. E’ possibile definire anche Instance Methods customizzati. 

    Per i Models generici invece, possiamo aggiungere dei metodi statici che permettono di definire un behavior statico utile a livello di modello (es. La ricerca di un elementi con una proprietà specifica). 

    I Query Helper invece sono molto simili agli Instance Methods, ma vengono utilizzati per effettuare delle query. 

    Mongoose Connections 

    Mongoose offre l’interfaccia per collegarsi direttamente a MongoDB: 

    mongoose.connect('mongodb://localhost:27017'); 

    In questo caso, MongoDB sta runnando su localhost, utilizzando la porta 27017 (default) (nb. questa è la connection string minimale che serve per accedere a Mongo). 

    E’ possibile catchare errori sulla mongoose.connect() con un semplice catch o wrappando la chiamata in un try catch. 

    Interruzione pagina 

    Mongoose Queries 

    Mongoose permette di eseguire numerose query sui Modelli (qui di seguito alcune): 

    Model.create() 
    
    Model.deleteMany() 
    
    Model.deleteOne() 
    
    Model.find() 
    
    Model.findById() 
    
    Model.findByIdAndDelete() 
    
    Model.findByIdAndRemove() 
    
    Model.findByIdAndUpdate() 
    
    Model.findOne() 
    
    Model.findOneAndDelete() 
    
    Model.findOneAndRemove() 
    
    Model.findOneAndReplace() 
    
    Model.findOneAndUpdate() 
    
    Model.replaceOne() 
    
    Model.updateMany() 
    
    Model.updateOne() 
    
    ... 

    Ognuna di queste query viene eseguita in maniera asincrona e il risultato viene in seguito passato alla funzione di callback. 

    Interruzione pagina 

    Sviluppo della REST API 

    Per fare un wrap-up del discorso, concludiamo lo sviluppo della REST API utilizzando Express e Mongoose, aggiungendo anche una piccola interfaccia per la manipolazione dei dati. 

     

    routes/pet.route.js

    const express = require('express');
    const router = express.Router();
    const controller = require('../controllers/pet.controller');
    
    router.get('/', async (req, res) => {
      let pets = await controller.GetPets();
      res.render('pets', { pets });
    });
    
    router.get('/:id', async (req, res) => {
      let pet = await controller.GetPet(req.params.id);
      res.render('pet', { pet });
    });
    
    router.post('/', async (req, res) => {
      await controller.AddPet(req);
      res.redirect('/pets');
    });
    
    router.put('/:id', async (req, res) => {
      await controller.UpdatePet(req);
      res.redirect('/pets');
    });
    
    router.delete('/:id', async (req, res) => {
      await controller.DeletePet(req);
      res.redirect('/pets');
    });
    
    module.exports = router;
    

    pet.route.js è il file di routing, al suo interno possiamo definire tutti gli endpoint per “pets”. 

    Nel nostro caso, sono definiti 5 endpoint: 

    • GET /pets 
    • GET /pets/:id 
    • POST /pets 
    • PUT /pets/:id 
    • DELETE /pets/:id 

    Ogni endpoint utilizza un metodo dedicato implementato all’interno del controller apposito, che si occuperà di effettuare le query utilizzando Mongoose.

    controllers/pet.controller.js

    const Pet = require('../models/pet.model');
    
    const GetPets = async () => {
      try {
        const pets = await Pet.find();
        return pets;
      } catch (error) {
        return error;
      }
    };
    
    const GetPet = async (id) => {
      try {
        const pet = await Pet.findById(id);
        return pet;
      } catch (error) {
        return error;
      }
    };
    
    const AddPet = async (req) => {
      try {
        const pet = new Pet(req.body);
        await pet.save();
        return pet;
      } catch (error) {
        return error;
      }
    };
    
    const UpdatePet = async (req) => {
      console.log('im here');
      console.log(req.body);
      try {
        const pet = await Pet.findByIdAndUpdate(req.params.id, req.body);
        return pet;
      } catch (error) {
        return error;
      }
    };
    
    const DeletePet = async (req) => {
      try {
        const pet = await Pet.findByIdAndDelete(req.params.id);
        return pet;
      } catch (error) {
        return error;
      }
    };
    
    module.exports = { GetPets, GetPet, AddPet, UpdatePet, DeletePet };
    

     

    Come possiamo vedere, ogni metodo definito all’interno del controller utilizza le query di mongoose dedicate che abbiamo specificato in precedenza. I nomi sono self-explanatory e ben documentati sulle dispense ufficiali. 

    index.js

    const express = require('express');
    const app = express();
    const bodyParser = require('body-parser');
    const ejs = require('ejs');
    const mongoose = require('mongoose');
    const path = require('path');
    const port = 3000;
    
    const pets = require('./routes/pet.route');
    
    mongoose
      .connect('mongodb://root:[email protected]:27017')
      .then(() => {
        console.log('| Connesso a MongoDB | HOST: localhost:27017');
      })
      .catch((error) => {
        console.log(
          '| Si è verificato un errore durante la connessione a MongoDB: ',
          error
        );
      });
    
    app.set('views', path.join(__dirname, 'views'));
    app.set('view engine', 'ejs');
    app.use(bodyParser.urlencoded({ extended: false }));
    app.use(bodyParser.json());
    
    app.use('/pets', pets);
    
    app.use('/', (req, res, next) => {
      return res.render('index', {});
    });
    
    app.listen(3000, () => {
      console.log(`| SERVER ATTIVO | URL: http://localhost:${port}`);
    });
    

    Per finire la index, che oltre all’init di express adesso contiene il collegamento a mongoose, il set del view engine (per questo esempio ho usato EJS, che però non trattiamo in questo articolo!), il bodyParser in modo da poter parsare i dati che arrivano dalle richieste e le routes. 

    Il risultato è il seguente:

    La home page, dove è possibile aggiungere un nuovo animale da compagnia (seguendo il modello di dati che abbiamo definito in precedenza); 

    La lista di animali registrati, dove si possono vedere tutti gli animali che sono stati inseriti e cancellarli o modificarli se necessario; 

    La pagina di modifica, molto simile a quella di inserimento, che si presenterà con i dati pre-compilati da modificare. 

    Come abbiamo visto, creare una Web App che possa interagire con una REST API utilizzando ExpressJS e Mongoose è veramente semplice, veloce e la configurazione è minimale. 

    E’ possibile vedere il progetto completo sulla seguente repository pubblica:
    https://github.com/lopreiatodaniele/giuneco-pfa-ExpressMongooseREST/ 

    Sulla repo è presente anche un docker-compose.yml che può essere utilizzato, se sulla macchina è presente Docker Desktop, per avviare in locale un’istanza di MongoDB, qualora non fosse già presente.
    Le credenziali che vengono usate dal progetto sono quelle impostate nell’ENVIRONMENT di Docker.