Creare un'applicazione web completa con Node JS, Express e MongoDB

Corso completo Web Developer - Parte 3 - 3.3

Introduzione

Questa sarà la grande sfida del corso. La lezione più attesa. Svilupperemo insieme un'applicazione web completa simulando le funzioni base di un e-commerce andando ad unire tutte le conoscenze acquisite fino ad ora.

Nel caso in cui non avessi seguito le lezioni precedenti, ti consiglio di farlo prima di iniziare questa lezione.

Ti lascio il link del corso completo qui sotto:

👉 Corso completo Web Developer 👈

Allora sei pronto/a? Partiamo! 🚀

Prerequisiti

Creare la cartella del progetto ed inizializzare NPM

Per prima cosa creiamo la cartella del progetto e inizializziamo NPM.

mkdir ecommerce
cd ecommerce
npm init -y

Installare le varie dependencies necessarie

Adesso installiamo le varie dependencies necessarie per lo sviluppo dell'applicazione.

npm install express ejs mongoose body-parser method-override

Hai notato che abbiamo installato più dependencies nella stessa riga con un comando soltanto?

Sarebbe stata la stessa cosa se avessimo fatto:

npm install express
npm install ejs
npm install mongoose
npm install body-parser
npm install method-override

Creare la struttura delle cartelle del progetto

Passiamo adesso alla struttura del progetto. Andremo a creare le principali cartelle e files che ci serviranno dall'inizio. In seguito creeremo altri files e cartelle che potrebbero servirci.

mkdir models
mkdir views
touch index.js (in Windows PowerShell usa il comando "New-Item" al posto di "touch")

Finora dovresti avere questa struttura:

Ed il tuo file package.json dovrebbe essere simile a questo:

Configurare il server

Passiamo alla configurazione del server. Apriamo il file index.js e scriviamo il codice seguente:

const express = require("express");
const mongoose = require("mongoose");
const app = express();
const path = require('path');
const PORT = 3000;
const bodyParser = require('body-parser');

// Configurazione body-parser per gestire i dati del body delle richieste
app.use(bodyParser.urlencoded({ extended: true }));

// Configurazione EJS
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, '/views'));

// Configurazione method-override per le richieste POST, PUT e DELETE dai forms in HTML
const methodOverride = require('method-override');
app.use(methodOverride('_method'));

// Configurazione dei file statici
app.use(express.static(path.join(__dirname, 'public')));

// Connessione a MongoDB
mongoose.connect('mongodb://127.0.0.1:27017/e-commerce-app', { useNewUrlParser: true, useUnifiedTopology: true })
  .then(() => console.log('Connesso al database'))
  .catch(err => console.error('Impossibile connettersi al database', err));

// Definizione degli endpoints
app.get('/', (req, res) => {
  res.render('homepage.ejs');
});

// Avvio del server
app.listen(PORT, () => {
  console.log(`Server in esecuzione sulla porta ${PORT}`);
})

Prima di poter avviare il server e testare che la base del nostro progetti funzioni correttamente dobbiamo creare il file homepage.ejs nella cartella views. Nella prossima sezione andremo a fare esattaemnte questo.

Creazione della view per la Homepage

Creiamo il file homepage.ejs nella cartella views e scriviamo il codice seguente:

touch views/homepage.ejs (in Windows PowerShell usa il comando "New-Item" al posto di "touch")

Ed ora andiamo ad aggiungerci il seguente codice:

<!DOCTYPE html>
<html lang="it">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="style.css" />
  <title>E-commerce App Homepage</title>
</head>
<body>
  <div className="container">
    <h1 className="title">E-commerce App</h1>
    <p>Scopri il nostro fantastico negozio online e trova i migliori prodotti per le tue esigenze!</p>

    <a href="/products" className="button">Scopri i prodotti del nostro E-commerce</a>
  </div>
  <footer>
    &copy; 2023 Ecommerce App. Tutti i diritti riservati.
  </footer>
</body>
</html>

Noterai che abbiamo aggiunto un link alla pagina /products che in realtà non abbiamo ancora creato. Infatti se provassi a cliccarci sopra ti darebbe un errore. In un attimo andremo a crearla.

Andiamo anche a creare una cartella public e all'interno di essa un file style.css per inserire il nostro CSS.

mkdir public
touch public/style.css (in Windows PowerShell usa il comando "New-Item" al posto di "touch")

Ed aggiungiamo il seguente codice:

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html,
body {
  height: 100%;
}

body {
  font-family: Arial, sans-serif;
  line-height: 1.6;
  background-color: #f4f4f4;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.container {
  text-align: center;
  flex-grow: 1;
}

h1 {
  font-size: 2rem;
  margin-bottom: 1rem;
}

p {
  font-size: 1rem;
  margin-bottom: 2rem;
}

.button {
  display: inline-block;
  background-color: #333;
  color: #fff;
  padding: 0.5rem 1rem;
  border-radius: 5px;
  text-decoration: none;
  margin-top: 1rem;
}

footer {
  background-color: #333;
  color: #fff;
  padding: 1rem;
  text-align: center;
}

Avvio del database

Prima di poter avviare il server dobbiamo assicurarci di avere il database correttamente in esecuzione.

In Windows possiamo utilizzare il comando mongod per avviare il database in una Windows PowerShell, mentre in macOS possiamo utilizzare brew services start mongodb-community nel terminale.

Avvio del server

Ora che abbiamo creato la nostra homepage possiamo avviare il server e testare che tutto funzioni correttamente. Per farlo apriamo il terminale e scriviamo il seguente comando:

nodemon index.js

Se il tutto è stato configurato correttamente dovresti ritrovarti con dei logs nel terminale più o meno come questi:

Ed una volta aperto il browser e navigato all'indirizzo http://localhost:3000 dovresti ritrovarti con la seguente pagina:

Creare il modello del Prodotto

Adesso che abbiamo creato la base del nostro progetto siamo pronti per iniziare a creare le parti più specifiche dell'applicazione. Iniziamo creando il modello del prodotto.

touch models/product.js (in Windows PowerShell usa il comando "New-Item" al posto di "touch")

Ed aggiungiamo il seguente codice:

const mongoose = require("mongoose");

const ProductSchema = new mongoose.Schema({
  nome: {
    type: String,
    required: true,
  },
  prezzo: {
    type: Number,
    required: true,
  },
  descrizione: {
    type: String,
    required: true,
  },
});

module.exports = mongoose.model("Product", ProductSchema);

Affinchè possiamo utilizzare il modello del prodotto all'interno del nostro progetto dobbiamo importarlo all'interno del file index.js. Aggiungiamo il require del modello del prodotto dopo il require di Express:

const Product = require("./models/product");

Dovremmo avere un qualcosa del genere a questo punto:

Creare le CRUD routes e le corrispondenti views

Lista dei prodotti

Nella Homepage abbiamo aggiunto un link alla pagina /products che non abbiamo ancora creato. Creiamo quindi questa route all'interno del file index.js subito dopo la route della homepage e prima della logica di avvio del server:

// INDEX - Lista di tutti i prodotti
app.get("/products", async (req, res) => {
  const products = await Product.find({});
  res.render("products/index", { products });
});

Come puoi vedere stiamo utilizzando il metodo find del modello del prodotto per recuperare tutti i prodotti dal database. Una volta recuperati i prodotti li passiamo alla view products/index che andremo a creare in un attimo.

La cartella views l'abbiamo creata in precedenza, adesso andiamo a creare al suo interno la cartella products che conterrà tutte le views relative ai products e quindi creiamo il file index.ejs:

mkdir views/products
touch views/products/index.ejs (in Windows PowerShell usa il comando "New-Item" al posto di "touch")

Ed aggiungiamo il seguente codice al file index.ejs:

<!DOCTYPE html>
<html lang="it">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="style.css" />
  <title>E-commerce App</title>
</head>
<body>
  <div className="container">
    <h1>Lista dei prodotti</h1>

    <ul>
    <% products.forEach((product) => { %>
      <li>
        <%= product.nome %> - <%= product.prezzo %>€
        <a href="/products/edit/<%= product._id %>">Modifica</a>

        <form action="products/<%= product._id %>?_method=DELETE" method="POST" style="display:inline;">
          <button type="submit">Elimina</button>
        </form>
      </li>
    <% }) %>
  </ul>

    <a href="/products/new" className="button">Aggiungi un nuovo prodotto</a>
    <a href="/" className="button">Torna alla Homepage</a>
  </div>

  <footer>
    &copy; 2023 Ecommerce App. Tutti i diritti riservati.
  </footer>
</body>
</html>

Come puoi vedere stiamo utilizzando la sintassi di EJS per fare un loop su tutti i prodotti e stamparli a schermo. Inoltre abbiamo aggiunto due link che ci permettono di tornare alla homepage e di aggiungere un nuovo prodotto. Quello per aggiungere il nuovo prodotto se provi a cliccarlo ti darà un errore e questo è perchè ancora non abbiamo aggiunto la logica per la crazione di un prodotto.

La pagina che dovresti avere a questo punto dovrebbe essere simile a questa:

Perfetto! 😃 Ora siamo pronti per creare la logica che ci permette di creare un prodotto.

Aggiungere un nuovo prodotto

Per creare un nuovo prodotto abbiamo bisogno di due routes, una che ci permette di visualizzare il form per creare il prodotto e una che ci permette di creare effettivamente il prodotto.

Visualizzare il form per creare un prodotto

Creiamo la route che ci permette di visualizzare il form per creare un nuovo prodotto all'interno del file index.js subito dopo la route INDEX che abbiamo appena creato:

// NEW - Mostra il form per creare un nuovo prodotto
app.get("/products/new", (req, res) => {
  res.render("products/new");
});

Come puoi vedere stiamo utilizzando il metodo render di Express per renderizzare la view products/new che andremo a creare in un attimo.

Andiamo quindi a creare la view products/new all'interno della cartella views/products:

touch views/products/new.ejs (in Windows PowerShell usa il comando "New-Item" al posto di "touch")

Ed aggiungiamo il seguente codice al file new.ejs:

<!DOCTYPE html>
<html lang="it">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="../style.css" />
  <title>E-commerce App</title>
</head>
<body>
  <div className="container">
    <h1>Aggiungi un nuovo prodotto</h1>

    <form action="/products/new" method="POST">
      <label for="nome">Nome:</label>
      <input type="text" id="nome" name="nome" required>

      <label for="prezzo">Prezzo:</label>
      <input type="number" id="prezzo" name="prezzo" required>

      <label for="descrizione">Descrizione:</label>
      <textarea id="descrizione" name="descrizione" required></textarea>

      <button type="submit">Crea prodotto</button>
    </form>

    <a href="/products" className="button">Lista di tutti i prodotti</a>
    <a href="/" className="button">Torna alla Homepage</a>
  </div>

  <footer>
    &copy; 2023 Ecommerce App. Tutti i diritti riservati.
  </footer>
</body>
</html>

Come puoi vedere abbiamo creato un semplice form che ci permette di inserire il nome, il prezzo e la descrizione del prodotto. Inoltre abbiamo aggiunto due link che ci permettono di tornare alla homepage e alla lista di tutti i prodotti.

Andiamo a dare uno stile più carino al form che abbiamo appena creato andando ad aggiungere il seguente codice al file style.css sotto il codice che abbiamo precedentemente aggiunto:

form {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-bottom: 2rem;
}

label {
  font-weight: bold;
  margin-top: 1rem;
  margin-bottom: 0.5rem;
}

input[type="text"],
input[type="number"],
textarea {
  width: 100%;
  max-width: 400px;
  padding: 0.5rem;
  border: 1px solid #ccc;
  border-radius: 5px;
  font-size: 1rem;
  font-family: inherit;
}

input[type="text"]:focus,
input[type="number"]:focus,
textarea:focus {
  outline: none;
  border-color: #333;
}

textarea {
  height: 150px;
  resize: vertical;
}

button[type="submit"] {
  background-color: #333;
  color: #fff;
  padding: 0.5rem 1rem;
  border-radius: 5px;
  font-size: 1rem;
  cursor: pointer;
  margin-top: 1rem;
}

button[type="submit"]:hover {
  background-color: #555;
}

A questo punto se sei sulla pagina dove listiamo tutti i prodotti prova a cliccare sul link "Aggiungi un nuovo prodotto" e dovresti essere reindirizzato alla pagina che abbiamo appena creato /products/new e avere una pagina simile alla seguente:

Perfetto! 😃 Ora che abbiamo creato la route che ci permette di visualizzare il form per creare un nuovo prodotto possiamo passare alla creazione della route che ci permette di creare effettivamente il prodotto nel database.

Creare un nuovo prodotto

Per creare un nuovo prodotto abbiamo bisogno di utilizzare il metodo POST e quindi dobbiamo creare una nuova route all'interno del file index.js subito dopo la route che abbiamo creato precedentemente:

// CREATE - Crea un nuovo prodotto
app.post("/products/new", async (req, res) => {
  const { name, price, description } = req.body;

  const product = new Product({
    name,
    price,
    description,
  });

  await product.save();

  res.redirect("/products");
});

Fermiamoci un attimo per analizzare quello che stiamo facendo passo dopo passo all'interno di questa route:

  1. Stiamo utilizzando il metodo post di Express per creare la route che processa la richiesta di creazione di un prodotto;
  2. Dall'oggetto req.body stiamo estraendo il nome, il prezzo e la descrizione del prodotto che l'utente ha inserito nel form;
  3. Utilizziamo il metodo new di Mongoose sul modello Product per creare un nuovo prodotto passando come argomento un oggetto con le proprietà nome, prezzo e descrizione;
  4. A questo punto utilizziamo il metodo save di Mongoose per salvare il prodotto nel database;
  5. Infine utilizziamo il metodo redirect di Express per reindirizzare l'utente alla lista di tutti i prodotti.

Vediamo se il tutto funziona! Prova a compilare il form e a cliccare sul pulsante "Crea prodotto" e dovresti essere reindirizzato alla lista di tutti i prodotti e dovresti vedere il prodotto che hai appena creato.

Io per esempio ho provato a creare un annuncio per vendere le mie AirPods 😂 Questo è il risultato:

Sei appena riuscito a creare il tuo primo prodotto e salvarlo nel database! Figo no?! 🎉

Se proverai ad utilizzare la funzione Modifica o Elimina riceverai un errore perchè ancora non abbiamo aggiunto le funzionalità. Andiamo a farlo subito!

Modificare un prodotto

Per modificare un prodotto abbiamo bisogno di creare due routes come per la creazione di un nuovo prodotto. La prima route ci permette di visualizzare il form per modificare un prodotto mentre la seconda route ci permette di processare la richiesta di modifica di un prodotto.

Visualizzare il form per modificare un prodotto

Andiamo a creare la prima route all'interno del file index.js subito dopo la route che abbiamo appena creato per la creazione di un nuovo prodotto:

// UPDATE - Visualizza il form per modificare un prodotto
app.get("/products/edit/:id", async (req, res) => {
  const product = await Product.findById(req.params.id);
  res.render("products/edit", { product: product });
});

Ora andiamo a creare la view edit.ejs all'interno della cartella views/products:

touch views/products/edit.ejs (in Windows PowerShell usa il comando "New-Item" al posto di "touch")

All'interno del file edit.ejs andiamo ad inserire il seguente codice:

<!DOCTYPE html>
<html lang="it">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="../../style.css" />
  <title>E-commerce App</title>
</head>
<body>
  <div className="container">
    <h1>Modifica Prodotto: <%= product.name %></h1>

    <form action="/products/<%= product._id %>?_method=PUT" method="POST">
      <label for="name">Nome:</label>
      <input type="text" id="name" name="name" value="<%= product.name %>" required>

      <label for="price">Prezzo:</label>
      <input type="number" id="price" name="price" value="<%= product.price %>" required>

      <label for="description">Descrizione:</label>
      <textarea id="description" name="description" required><%= product.description %></textarea>

      <button type="submit">Salva le modifiche</button>
    </form>

    <a href="/products" className="button">Lista di tutti i prodotti</a>
    <a href="/" className="button">Torna alla Homepage</a>
  </div>

  <footer>
    &copy; 2023 Ecommerce App. Tutti i diritti riservati.
  </footer>
</body>
</html>

Come potrai notare il form è molto simile a quello che abbiamo creato per la creazione di un nuovo prodotto. Una delle principali differenze è l'action del form che ora punta alla route per processare la modifica di un prodotto.

Processare la richiesta di modifica di un prodotto

Andiamo a creare la seconda route all'interno del file index.js subito dopo la route che abbiamo creato per visualizzare il form per modificare un prodotto:

// UPDATE - Processa la richiesta di modifica di un prodotto
app.put("/products/:id", async (req, res) => {
  await Product.findByIdAndUpdate(req.params.id, req.body);
  res.redirect("/products");
});

Ora se provi a cliccare sul pulsante "Modifica" di un prodotto dovresti essere reindirizzato alla pagina per modificare il prodotto. Prova a modificare il prodotto e a cliccare sul pulsante "Salva le modifiche" e dovresti essere reindirizzato alla lista di tutti i prodotti e dovresti vedere il prodotto che hai appena modificato.

Yay! 🎉 Ora possiamo anche modificare i prodotti nel nostro E-commerce!

Manca l'ultima operazione CRUD: l'eliminazione di un prodotto.

Eliminare un prodotto

Per eliminare un prodotto è molto semplice e avremo bisogno di una sola route. Andiamo a creare la route all'interno del file index.js subito dopo la route che abbiamo creato per processare la modifica di un prodotto:

// DELETE - Elimina un prodotto
app.delete("/products/:id", async (req, res) => {
  await Product.findByIdAndDelete(req.params.id);
  res.redirect("/products");
});

Ora se provi a cliccare sul pulsante "Elimina" di un prodotto dovresti essere reindirizzato alla lista di tutti i prodotti e dovresti vedere che il prodotto che hai appena liminato non è più presente nella lista.

Io ho appena eliminato dalla lista le mie Airpods e sono ritornato ad una pagina di prodotti vuota:

E con questo abbiamo finito (o quasi) di creare il nostro E-commerce! 🎉

Perchè dico quasi? C'è una piccola challenge per te ma ti prometto che è piuttosto semplice e dovresti essere capace di risolverla poichè si basa sui concetti visti insieme finora. Te la spiego nella prossima sezione.

Piccola Challenge

La challenge è quellla di creare una route che gestisce la richiesta di visualizzare i dettagli di un singolo prodotto.

Non voglio darti altri aiuti perchè voglio che tu provi a risolvere questa challenge da solo/a ma se proprio non riesci puoi guardare la soluzione che ti ho preparato qui sotto.

Soluzione

Per prima cosa andiamo a creare la route all'interno del file index.js subito dopo la route CREATE che abbiamo aggiunto per processare la richiesta di creazione di un nuovo prodotto:

  // SHOW - Visualizza i dettagli di un singolo prodotto
  app.get("/products/:id", async (req, res) => {
    const product = await Product.findById(req.params.id);
    res.render("products/show", { product });
  });

Ora andiamo a creare il file show.ejs all'interno della cartella views/products:

  touch views/products/show.ejs (in Windows PowerShell usa il comando "New-Item" al posto di "touch")

Adesso che abbiamo tutto pronto per visualizzare i dettagli di un singolo prodotto dobbiamo andare a modificare il codice HTML all'interno del file index.ejs per aggiungere un link che ci permette di visualizzare i dettagli di un singolo prodotto facilmente.

Andiamo a modificare il codice nel file views/products/index.ejs.

Dove abbiamo il loop dei prodotti, nell'elemento HTML li abbiamo aggiunto il seguente codice:

  <%= product.name %> - <%= product.price %>€

Sosituisci questa parte con il seguente codice:

  <a href="/products/<%= product._id %>"><%= product.name %></a> - <%= product.price %>€

Conclusione

E con questo abbiamo finito di creare il nostro E-commerce! 🎉

Se hai seguito tutti i passaggi dovresti avere un E-commerce funzionante che ti permette di creare, leggere, aggiornare ed eliminare prodotti.

Con questa lezione concludiamo anche qui il Corso completo Web Developer.

Gli Inglesi dicono "You don't know what you don't know" ed è per questo che ho voluto creare questo corso per farti esplorare tutti i principali concetti della programmazione e darti un'infarinatura generale su ognuno di essi.

Siamo partiti dall'installazione dei primi programmi utili per la programmazione nel tuo computer fino alla creazione di un E-commerce basico che include tutta la conoscenza base vista durante il corso.

Con un'infarinatura generale di base adesso hai la conoscenza necessaria per poter approfondire ogni singolo concetto e avvicinarti alle aree della programmazione che più t'interessano.

Ovviamente continuando a seguire il blog troverai sempre nuovi articoli che andranno appunto ad approfondire i singoli concetti oltre a vari articoli per tenerti aggiornato sulle novità dell'affascinante mondo Tech.

Voglio ringraziarti per aver seguito questo corso e augurarti un buon proseguimento del tuo percorso di apprendimento!

Per ora è tutto da parte mia, ci vediamo al prossimo articolo! 👋