TP2 Building websites using Javascript

Deuxième TP

By Mariem ZAOUALI

TP 2 : Créer une application avec Javascript, Node.JS et Express

À la fin de ce TP, vous aurez une meilleure compréhension de la création d’une application avec Javascript, Express et Node.JS en interagissant avec une base de données, ainsi que de l’architecture front-end et back-end des applications Web. Vous saurez également comment créer une application Web avec JavaScript qui effectue des opérations CRUD. Avant de commencer ce cours, vous devez avoir des connaissances générales sur le HTML, le CSS, le développement Web, les technologies Web et une compréhension de base de JavaScript.

Outils à utiliser

  • Un éditeur : Visual Studio Code. Après son installation, choisissez “Auto Save” dans le menu File pour assurer l’enregistrement automatique des changements au niveau du code.
  • Live Server : Installer le serveur à partir de visual code
  • Installer Node.JS du site officiel, si votre VS code est ouvert, veuillez le redémarrer.
  • Un SGBD MySQL (vous utiliser live server ou installer wampserver)
  • Un navigateur

Architecture de l’application

Nous allons dans ce TP, créer une application qui génère des citations d’une manière aléatoire. Nous pouvons également ajouter de nouvelles citations ou encore lister celles déjà existantes dans une base de données. L’architecture de cette application sera donc composée de trois parties.

  • Une partie Front pour communiquer ou lire les citations.
  • Une partie API qui servira le trait d’union entre le front et la base de données. On peut qualifier cette partie comme étant la logique métier de notre application
  • Une partie dédiée à la persistance des données. (( Suivre la présentation du cours )) ((Arborescence de l’application))

Créer votre projet sur VS code

Ouvrez votre VS code et sélectionnez un nouveau dossier où vous allez créer votre application. Ouvrez le terminal et choisissez le prompt Git bash pour taper la commande suivante. Cette commande va vous créer l’arborescence ciblée pour notre projet ainsi que la création des fichiers vides. Copiez ce code dans un fichier texte sur VS code, sélectionnez cette commande et allez au menu View>Word Wrap pour organiser toute la commande sur une seule ligne.

mkdir -p app/{config,features/{addquote,quotelist},views} && touch "app/index.js" "app/apiserver.js"
"app/config/dbinfo.js" "app/views/index.html" "app/features/addquote/addquote.html" "app/features/addquote/addquote.js"
"app/features/quotelist/quotelist.html" "app/features/quotelist/quotelist.js"

Tapez par la suite dans le même terminal, la commande suivante pour initier le runtime Node.JS pour notre application JS. C’est l’environnement qui nous permettra d’exécuter le code écrit en JS. Suite à cette exécution, on va vous demander d’entrer des informations sur votre application. Vous pourvez ne pas tout remplir, mais au moins vous mettez le nom du projet comme Quotes et vous spécifier la page index.js comme votre entry-point.

npm init

La première ossature des pages HTML

Nous créons, dans cette section, la structure des pages html de notre application. Veuillez copier le code de chaque cellule dans la page adéquate: index.html

<!DOCTYPE html>
<html>

<head>
    <title>Quotes for life</title>
</head>

<body> <!-- Call the function on page load -->
    <h1>Quotes</h1>
    <div></div>




    <footer>
        <Center>
        <p>&copy; Ecole Nationale d'Ingénieurs de Tunis - TP2 Dev web avancé</p>
        </Center>
      </footer>
</body>

</html>

addquote.html

<!DOCTYPE html>
<html>
<head>
  <title>Add Quotes</title>
</head>
<body>
  <div class="container">
    <h1>Add a quote</h1>
    
  </div>
  <br>
  <br>
  <div class="container"></div>
    <center><a href="/app/views/index.html" class="btn btn-primary">Home</a></center>
  </div>
  <footer>
    <Center>
        <p>&copy; Ecole Nationale d'Ingénieurs de Tunis - TP2 Dev web avancé</p>
    </Center>
  </footer>

</body>
</html>

quotelist.html

<!DOCTYPE html>
<html>
<head>
  <title>Quotes List</title>
</head>
<body>
  <div>
    <h1>Quotes List</h1>
  </div>

  <br>
  <br>
  <div class="container"></div>
    <center><a href="/app/views/index.html" class="btn btn-primary">Home</a></center>
  </div>
  <script>
    //Function to list the quotes
  </script>

  <br>
  <br>
  <footer>
    <Center>
        <p>&copy; Ecole Nationale d'Ingénieurs de Tunis - TP2 Dev web avancé</p>
    </Center>
  </footer>

</body>
</html>

Ajout des formulaires

La logique à suivre dans cette section, est l’implémentation des fonctionnalités CRUD. Nous allons écrire du code qui va permettre de consulter la base de données pour ajouter et lister les citations (aka. quotes).

De prime abord, nous ajouterons à la page addquote.html, un petit formulaire qui va nous permettre de saisir une nouvelle citation selon le domaine (motivation, inspiration, amitié etc). A l’intérieur de body, dans la première div, ajoutons ce contenu:

<div class="container"></div>
  <h1>Add a Quote</h1>
  <form id="add-quote-form" method="POST">
    <div class="form-group">
      <label for="domain">Domain</label>
      <input type="text" class="form-control" id="domain" name="domain" required>
    </div>
    <div class="form-group">
      <label for="content">Content</label>
      <input type="text" class="form-control" id="content" name="content" required>
    </div>
    <div class="form-group">
      <label for="rating">Rating</label>
      <input type="number" class="form-control" id="rating" name="rating" min="1" max="5" required>
    </div>
    <button type="submit" class="btn btn-primary">Add Quote</button>
  </form>
</div>
 <script src="./addquote.js"></script>

L’ajout des formulaires nécessite l’ajout des Event-handler. Ceci permet de détecter la soumission du formulaire suite au click de l’utilisateur sur le bouton submit. Grâce au DOM, nous allons pouvoir récupérer tous les objets de la page, extraire ses valeurs et les passer en paramètres à la fonction aynchrone addQuote.

Qu’est ce que le DOM?

Le DOM (Document Object Model) est une interface de programmation qui représente la structure d’un document HTML ou XML sous forme d’arbre. Chaque élément d’un document (balise, texte, attributs) devient un nœud dans cet arbre. Grâce au DOM, les scripts JavaScript peuvent accéder et manipuler dynamiquement la structure, le contenu et le style d’un document Web.

Que veut dire asynchrone?

Lorsqu’une tâche est qualifiée d’asynchrone, cela signifie que son exécution ne se fait pas de manière séquentielle (où chaque étape doit attendre la fin de l’étape précédente). Au lieu de cela, la tâche est exécutée de manière non bloquante, permettant au programme de continuer à fonctionner pendant que la tâche attend une réponse, telle qu’une requête réseau ou un accès à une base de données.

Prenons une requête HTTP asynchrone comme exemple :

-1- Le client (navigateur ou application) envoie une requête pour demander une ressource (comme une page web, des données JSON, etc.). -2- Au lieu d’attendre que le serveur réponde avant de continuer à exécuter d’autres parties du code, le client continue à fonctionner normalement (par exemple, l’utilisateur peut encore interagir avec l’interface). -3- Une fois que le serveur a terminé de traiter la requête, il envoie une réponse (que ce soit des données ou une confirmation). -4- Le client, une fois la réponse reçue, peut alors la traiter (par exemple, afficher les données sur l’interface utilisateur).

En JS, ceci est implémenté à l’aide des mots-clés async/await.

Dans le fichier addquote.js, insérez le code suivant:

// Function to add a new quote
async function addQuote(domain, content, rating) {
    try {
      const response = await  fetch('http://localhost:5500/qapi/quotes', { 
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ domain, content, rating }), 
      });
  
      if (response.ok) {
        alert('Quote added successfully!');
      } else {
        alert('Failed to add quote.');
      }
    } catch (error) {
      console.log(error);
    }
  }
  
  // Attach event listener to the form outside the addQuote function
  document.getElementById('add-quote-form').addEventListener('submit', async (event) => { 
    event.preventDefault(); // Prevent default form submission behavior
  
    const domainElement = document.getElementById('domain'); 
    const contentElement = document.getElementById('content'); 
    const ratingElement = document.getElementById('rating');
  
    const domain = domainElement.value; 
    const content = contentElement.value; 
    const rating = ratingElement.value;
  
    await addQuote(domain, content, rating);
  });
  

Passons maintenant à la fonctionnalité Read du CRUD. Mettez à jour le squelette de la page quotelist.html ainsi:

<body>

    <div class="container">
      <h1>Quote List</h1>
      <ul id="quote-list" class="list-group"></ul>
    </div>

  <br>
  <br>
  <div class="container">
    <center><a href="/app/views/index.html" class="btn btn-primary">Home</a></center>
  </div>
  <script src="./quotelist.js"></script>

Dans le fichier quotelist.js, insérez le code suivant. Remarquez que nous avons prévu l’ajout de deux boutons qui vont permettre d’accéder aux autres fonctionnalités du CRUD. A ce moment, nous n’allons pas les implémenter.

// Quote List feature JavaScript code
// quotelist.js

// Function to display all quotes
function displayQuotes() {
    fetch('http://localhost:5500/qapi/quotes')
      .then(response => response.json())
      .then(data => {
        const quoteList = document.getElementById('quote-list');
        quoteList.innerHTML = '';
  
        data.forEach(quote => {
          const listItem = document.createElement('li');
          listItem.className = 'list-group-item';
          listItem.innerHTML = `
            <strong>Domain:</strong> ${quote.domain}<br>
            <strong>Content:</strong> ${quote.content}<br>
            <strong>Rating:</strong> ${quote.rating}<br>
            <button class="btn btn-sm btn-primary" onclick="editQuote(${quote.id})">Edit</button>
            <button class="btn btn-sm btn-danger" onclick="deleteQuote(${quote.id})">Delete</button>
          `;
          quoteList.appendChild(listItem);
        });
      })
      .catch(error => console.log(error));

  }
document.addEventListener('DOMContentLoaded', displayQuotes);

Ajoutons aussi un Event Handler dans la page index.html, le code commence par ajouter un appeal à ce handler dans la balise body (ce code doit être copié avant la balise footer). Attention! supprimez le tag body initialement existant

<body onload="getRandomQuote()"> <!-- Call the function on page load -->
    <div class="jumbotron text-center">
      <h1 class="display-4">Quotes for life</h1>
      <p class="lead">Get ready to be inspired!</p>
      <hr class="my-4">
      <h3 id="quote-domain"></h3>
      <p id="quote-content"></p>
      <button class="btn btn-primary" onclick="getRandomQuote()">Get Another Quote</button>
      <a href="/app/features/addquote/addquote.html" class="btn btn-success">Add a Quote</a>
      <a href="/app/features/quotelist/quotelist.html" class="btn btn-info">List all Quotes</a>
    </div>
  
    <script>
    // Function to get a random quote
    function getRandomQuote() {
       fetch('http://localhost:5500/qapi/randomquotes')
          .then(response => response.json())
          .then(data => {
            const quoteDomain = document.getElementById('quote-domain');
            const quoteContent = document.getElementById('quote-content');
            quoteDomain.textContent = data[0].domain;
            quoteContent.textContent = data[0].content;
          })
          .catch(error => console.log(error));
      }
    </script>

Pour que tout cela ait un sens, nous devons implémenter deux fichiers : apiserver.js et index.js. Dans apiserver.js, nous allons implémenter le code qui communique avec la base de données à l’aide des requêtes REST.

const express = require('express');
const db = require('./config/dbinfo');
const router = express.Router();

// Create a quote
router.post('/quotes', (req, res) => {
  let quote = req.body;
  let sql = 'INSERT INTO quotes (domain, content, rating) VALUES (?, ?, ?)';
  let values = [quote.domain, quote.content, quote.rating];
  db.query(sql, values, (err, result) => {
    if (err) throw err;
    res.send('Quote has been added...');
  });
});

// All quotes
router.get('/quotes', (req, res) => {
  let sql = 'SELECT * FROM quotes';
  db.query(sql, (err, results) => {
    if (err) throw err;
    res.send(results);
  });
});

// A single quote
router.get('/quotes/:id', (req, res) => {
  let sql = `SELECT * FROM quotes WHERE id = ${req.params.id}`;
  db.query(sql, (err, result) => {
    if (err) throw err;
    res.send(result);
  });
});

// A random quote
router.get('/randomquotes', (req, res) => {
  let sql = 'SELECT * FROM quotes ORDER BY RAND() LIMIT 1';
  db.query(sql, (err, result) => {
    if (err){throw err;
      console.error('Error executing SQL:', err); // Affiche l'erreur si la requête échoue
      return res.status(500).send('Error executing SQL');
    }
    res.send(result);
    console.log('Result from database:', result); // Affiche le résultat
  });
});

// Update a quote
router.put('/quotes/:id', (req, res) => {
  let quote = req.body;
  let sql = `UPDATE quotes SET domain='${quote.domain}', content='${quote.content}', rating=${quote.rating} WHERE id=${req.params.id}`;
  db.query(sql, (err, result) => {
    if (err) throw err;
    res.send('Quote updated...');
  });
});

// Delete a quote
router.delete('/quotes/:id', (req, res) => {
  let sql = `DELETE FROM quotes WHERE id=${req.params.id}`;
  db.query(sql, (err, result) => {
    if (err) throw err;
    res.send('Quote deleted...');
  });
});

module.exports = router;

Dans index.js, nous allons implémenter le code qui lance notre application.

// Add middleware / dependencies
const express = require('express');
const db = require('./config/dbinfo');
const app = express();
const port = 5500;
const quoteRouter = require('./apiserver'); 
const cors = require('cors');

app.use(express.json());
app.use(express.static(__dirname));
app.use(cors()); 
// Use API routes
app.use('/qapi', quoteRouter); 

// Entry point for the web app
app.get('/', (req, res) => {
    res.sendFile(__dirname + '/views/index.html');
});

// 404 route
app.use((req, res) => {
  res.status(404).send(`
    <html>
      <head>
        <title>Page Not Found</title>
      </head>
      <body>
        <h1>The page you are trying to access does not exist.</h1>
        <p>Enjoy some quotes by going to the:</p>
        <p>Quotes for life<a href="/">HOME</a>.</p> 
      </body>
    </html>
  `);
});

// Start the Server
app.listen(port, () => {
    console.log('Server started on port 5500');
});

// npm install should be run for the first time to ensure all dependencies are installed for node.js before starting the app.
// To start the app using the standard method from the start script in the project's package.json file run:
// npm start
// Starts the nodeman tool from the dev script defined in the project’s package.json file run:
// npm run dev

Améliorant nos pages avec du CSS. Pour ce faire, nous aurons recours au Bootstrap qui est un framework de développement front-end. Au niveau de la bailse head de la page index.html, ajoutons ce contenu :

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
  <style>
    body {
      background-color: purple;
    }

    .jumbotron {
      background-color: gold;
    }
    h1,p{
        color:beige;
    }
  </style>

Ajoutons dans la balise head des autres pages, le contenu suivant:

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">

Création de la base de données

Créez votre base de données sous mysql. Donnez le nom quotesdb à votre base et puis créez la table quotes avec les champs suivants:

CREATE TABLE quotes (
  id INT AUTO_INCREMENT PRIMARY KEY,
  domain VARCHAR(255),
  content VARCHAR(255),
  rating INT
);

Insérez ces données dans votre table :

INSERT INTO quotes (domain, content, rating) VALUES
('Motivation', 'The only way to do great work is to love what you do. - Steve Jobs', 5),
('Motivation', 'Don’t watch the clock; do what it does. Keep going. - Sam Levenson', 4),
('Motivation', 'Success is not the key to happiness. Happiness is the key to success. - Albert Schweitzer', 5),
('Amitié', 'Friendship is the only cement that will ever hold the world together. - Woodrow Wilson', 5),
('Amitié', 'A real friend is one who walks in when the rest of the world walks out. - Walter Winchell', 4),
('Amitié', 'True friendship comes when the silence between two people is comfortable. - David Tyson', 5),
('Patience', 'Patience is not simply the ability to wait – it’s how we behave while we’re waiting. - Joyce Meyer', 5),
('Patience', 'The two most powerful warriors are patience and time. - Leo Tolstoy', 4),
('Patience', 'Patience is bitter, but its fruit is sweet. - Aristotle', 5);

Nous passons maintenant à la configuration du fichier qui contient les données sur notre base, dans notre cas, app/config/dbinfo.js. Remarquez qu’à la fin du code, nous exportant db pour pouvoir l’utiliser dans d’autres fichiers.

// Database configuration
const mysql = require('mysql');

const db = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: '',
  database: 'quotesdb'
});

db.connect((error) => {
  if (error) {
    console.error('Database connection error:', error);
  } else {
    console.log('Connected to the database');
  }
});

module.exports = db;

Testez la page index.html

Avant de voir le résultat final, il faut lancer des commandes pour ajouter des dépendences nécessaires au fonctionnement du site.

npm install --save express
npm install --save body-parser
npm install --save cors
npm install --save mysql
npm install --save nodemon

Avant de passer au lancement de notre site, vérifiez le contenu de la section script et la section devDependencies du fichier package.json comme suit.

 "devDependencies": {
    "body-parser": "^1.20.1",
    "cors": "^2.8.5",
    "express": "^4.21.0",
    "mysql": "^2.18.1",
    "nodemon": "^2.0.20"
  }
  "scripts": {
    "dev": "nodemon ./app/index.js",
    "start": "node ./app/index.js"
  },

Attention ! si vous trouvez dependencies, changez-là en devDependencies. Passons maintenant au lancement de notre application :

npm run dev

A vous de jouer
En exercice et en se référant à des ressources sur internet, proposez une implémentation des fonctionnalités Edit et Delete des citations (Quotes). Vous allez appeler les fonctions écrites dans apisever.js.