Aller au contenu principal

Découverte de NodeJS

Introduction

NodeJS est une plateforme libre en JavaScript qui permet de faire fonctionner du code JavaScript côté serveur (à l’instar de nombreux langages de programmation comme Ruby, Python, Java, etc). Ainsi, il était possible d’utiliser JavaScript à la fois du côté client et du côté serveur. Sa première version date de 2009.

Environnement de développement

La première étape consiste à vérifier que NodeJS est installé sur le système. Pour cela, il faut taper la commande suivante dans un terminal :

node --version

Si vous voyez un numéro de version, c’est que NodeJS est installé. Sinon, il faut passer par le site officiel pour télécharger NodeJS: https://nodejs.org/fr.

danger

Etant donné qu’il s’agit d’un environnement de DEVELOPEMENT, nous pouvons utiliser la version qui ne comporte pas "LTS" (dans un environnement de PRODUCTION il FAUT utiliser la version LTS !).

Initialisation du projet

Créez le répertoire laboratoire_1 et utilisez les commandes suivantes à l'intérieur:

npm init -y
npm pkg set type=module
astuce

N'utilisez pas de répertoire avec des espaces ou caractères spéciaux dans le chemin pour accéder au dossier. De plus, évitez également les dossiers synchronisés avec un système Cloud (OneDrive, Dropbox, etc). NodeJS a des difficultés avec ces dossiers.

Cette commande permet d’initialiser le fichier package.json qui contient les informations sur votre projet. Cela permet d’indiquer les dépendances de votre projet et de pouvoir les installer en une ligne de commande. Le fichier package-lock.json indique seulement la version précise de chaque dépendance pour éviter des problèmes de mise à jour qui amèneraient des "breaking changes". Maintenant, nous allons installer une dépendance :

npm i express

Express est considéré comme un "mini" framework. En effet, il ne s’occupe que de la partie routage des requêtes dans votre application, contrairement aux frameworks plus classiques qui ont tendance à avoir beaucoup de fonctionnalités.

Dans un premier temps, nous allons montrer comment faire une rapide démonstration de son fonctionnement. Créez un fichier server.js au même niveau que votre package.json. Dans le fichier copier/coller ces lignes de code :

server.js
import express from "express";
const app = express();
const port = 3001;

app.get('/', (req, res) => {
res.send('Hello World!');
});

app.listen(port, () => {
console.log(`http://localhost:${port}`);
});

Analysons ces lignes avant toute chose :

  • import express from "express;" permet d’importer le paquet express depuis les modules installés.

  • const app = express(); permet de créer une "application" qui permettra d’indiquer les comportements pour le routage.

  • const port = 3001; est une constante qui indique sur quel port tournera le serveur.

  •   app.get('/', (req, res) => {
    res.send('Hello World!');
    });

    permet d’avoir une première route ! Lorsque que vous faites une requête GET à la racine de votre domaine (http://mondomaine.be), express utilisera la fonction callback donnée en argument. Cette fonction prend 2 arguments : la requête reçue par le serveur et la future réponse. Dans le callback, on envoie le texte "Hello World !".

  •   app.listen(port, () => {
    console.log(`Example app listening at http://localhost:${port}`);
    });

    permet de lancer l’application et de la faire écouter sur le port indiqué dans la variable du même nom. Quand le serveur écoute, il effectue le callback passé en paramètre.

Pour lancer le serveur, il faut ouvrir un terminal au niveau du fichier server.js et taper la commande suivante :

node server.js

Et vous obtiendrez ceci:

Texte de la console Nodemon indiquant le démarrage d'une application Node.js et son écoute à l'adresse http://localhost:3001.

Maintenant, ouvrez votre navigateur et allez à l’adresse suivante : http://localhost:3001 et vous verrez le texte "Hello World !".

Les 2 types de dépendances

Il existe 2 types de dépendances : celles de production et celles de développement. Les premières sont vitales au fonctionnement de votre application tandis que les secondes ne sont là que pour vous aider à développer.

Pour illustrer cela, modifiez le fichier server.js en changeant le texte "Hello World !" en "Bonjour tout le monde !". Rechargez la page http://localhost:3001 et vous verrez que le texte affiché est toujours "Hello World !". En effet, NodeJS ignore les modifications. Il faudra donc arrêter le processus (dans le terminal, faites "ctrl – c") et relancer le avec la commande plus haut. Ce processus peut très vite devenir contraignant et il existe un utilitaire qui permet de relancer automatiquement le serveur pour vous. Pour cela, tapez la ligne de commande suivante :

npm i -D nodemon

Si vous regardez dans le fichier package.json, vous verrez que nodemon se trouve dans les dépendances de développement et non les dépendances classiques (contrairement à express). Maintenant, il ne reste plus qu’à préparer le script. Exécutez la commande suivante:

npm pkg set scripts.dev="nodemon server.js"

Vous devriez avoir ceci dans votre fichier package.json:

Code JSON montrant un script npm avec "dev" exécutant "nodemon server.js".

Maintenant, dans le terminal, vous pouvez taper cette nouvelle commande :

npm run dev

Quand vous changerez quelque chose dans un fichier JavaScript ou JSON, nodemon s’occupera de redémarrer l’application pour vous :

Texte affiché dans un terminal montrant des messages de nodemon pour une application Node.js, incluant des instructions et des statuts.

Premier pas vers une API REST

Nous allons commencer à faire une API REST très simple ! Pour cela, il faut créer le dossier model dans le dossier de votre projet. Dedans, créez le fichier product.js et mettez-y le code suivant :

./model/product.js
//permet de simuler une base de données
const products = [
{id: 1, name: "Playstation 4", price:400},
{id: 2, name: "Xbox One", price:399.9},
{id: 3, name: "Nintendo Switch", price:349.99}
]

export const readProduct = (id) => {
const results = products.filter(p => p.id === id);
if(results.length > 0){
return results[0];
} else {
throw new Error("Aucun produit trouvé");
}
}

Ensuite, dans le fichier server.js il faut importer le nouveau fichier JS et l’utiliser pour pouvoir afficher les produits. Normalement, il devrait ressembler à ceci :

./server.js
import express from "express";
import * as productModel from "./model/product.js";
const app = express();
const port = 3001;

app.get('/', (req, res) => {
res.send('Hello World');
});

app.get('/product/:id', (req, res) => {
const idTexte = req.params.id; // ATTENTION ! Il s'agit d'un texte !
const id = parseInt(idTexte);
const produit = productModel.readProduct(id);
res.json(produit);
});

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});

Maintenant, si vous allez à l’adresse suivante : http://localhost:3001/product/1, vous devriez voir le produit avec l’ID 1.

Gestion des erreurs

Très vite, vous serez confrontés à la gestion des erreurs. En effet, dans Node JS, si une erreur n’est pas capturée et gérée, cette dernière peut provoquer un arrêt de votre processus (et donc de votre API). Il faudra ainsi apprendre à les traiter ! Pour cela, vous avez les mots clés try et catch.

Dans l’exemple précédent, il y a 2 façons de faire pour lancer une exception. La première consiste à donner un ID qui n’existe pas (4, 5, 6, …) et la seconde consiste à passer comme ID quelque chose qui n’est pas un ID (une chaîne de caractères par exemple). Pour les gérer, vous pouvez utiliser le code suivant dans le fichier server.js :

./server.js
app.get('/product/:id', (req, res) => {
const idTexte = req.params.id; // ATTENTION ! Il s'agit d'un texte !
const id = parseInt(idTexte);
if(isNaN(id)){
res.sendStatus(400);
} else {
try {
const produit = productModel.readProduct(id);
res.json(produit);
} catch (error) {
res.sendStatus(404);
}
}
});

Et normalement, si vous essayez de trouver un ID qui ne fonctionne pas ou un mauvais format d’ID, vous devriez avoir ceci comme réponse dans votre navigateur :

État HTTP 404 Not Found, Version HTTP/1.1, Transfert 220 octets (taille 9 octets).

Erreur 400 Bad Request affichant des détails sur la version HTTP et le transfert.

API REST : CRUD

Maintenant que nous avons la possibilité d’afficher un produit, on aimerait pouvoir ajouter, mettre à jour et supprimer un produit. Pour cela, on peut directement utiliser les méthodes d’Express qui correspondent aux méthodes HTTP.

Pour le POST, ajoutez cette ligne après les imports de modules dans le fichier server.js:

./server.js
app.use(express.json());

Ceci est appelé un middleware et permet de faire un traitement pour chaque requête. Ici, express va parser le body de la requête pour le transformer en object JavaScript utilisable facilement dans votre code. Il existe d’autres types de middleware, comme Helmet qui permet d’ajouter quelques sécurités au niveau des requêtes.

Ensuite ajoutez cette fonction dans le même fichier, ainsi que l'import associé :

./server.js
app.post('/product', (req, res) => {
const body = req.body;
const {id, name, price} = body;
const response = productModel.createProduct(id, name, price);
if(response){
res.sendStatus(201);
} else {
res.sendStatus(500);
}
});

Et dans le fichier ./model/product.js, ajoutez cette fonction :

./model/product.js
export const createProduct = (id, name, price) => {
products.push({
id,
name,
price
});
return true;
}

Si vous testez avec un logiciel qui permet de construire facilement des requêtes HTTP (dans les prochains exemples, il s’agira de Bruno. Cependant, vous pouvez en utiliser d’autres et il existe même des plugins pour navigateur). Vous verrez que tout le produit est bien rajouté (dans l’exemple, on suppose que l’insertion du produit ne posera aucun problème. Ce qui n’est pas toujours le cas !) :

Code JSON pour créer un produit, avec le nom "Steam deck", le prix de "249" et l'identifiant "4".

Réponse d'une API pour un produit : ID 4, nom "Steam deck", prix "249".

remarque

Pourquoi avoir choisi Bruno et pas Postman ?

En effet, Postman est plus connu dans l'industrie et propose plus de fonctionnalités. La raison est simple: Postman impose une limite dans les tests automatique d'une collection. Il s'agit du sujet principal du laboratoire 7 et vous serez évalués dessus. Pour plus de confort, il a été décidé d'utiliser Bruno pour ne pas subir cette limitation

Maintenant, passons à la mise à jour d’un produit. Pour cela, il existe 2 méthodes : PUT et PATCH. PUT remplace entièrement la ressource, tandis que PATCH remplace une partie de la ressource. Dans l’exemple, nous utiliserons uniquement PATCH pour modifier un prix.

Ajoutez cette fonction dans le fichier server.js:

./server.js
app.patch('/product', (req, res) => {
const {id, price} = req.body;
const response = productModel.updateProduct(id, price);
if(response){
res.sendStatus(204);
} else {
res.sendStatus(404);
}
});

Et dans le fichier ./model/product.js, ajoutez cette fonction :

./model/product.js
export const updateProduct = (id, price) => {
const product = products.find(p => p.id === id);
if(product){
product.price = price;
return true;
} else {
return false;
}
}

Et voici le résultat :

Code JSON montrant une requête PATCH avec un identifiant de produit et un prix.

Réponse d'une API montrant un produit avec l'ID 4, le nom "Steam deck" et le prix de 10.

Enfin pour la méthode DELETE, il s’agit du même procédé. On ajoute cette fonction dans le fichier server.js :

./server.js
app.delete('/product/:id', (req, res) => {
const idTexte = req.params.id; // ATTENTION ! Il s'agit d'un texte !
const id = parseInt(idTexte);
if(isNaN(id)){
res.sendStatus(400);
} else {
const response = productModel.deleteProduct(id);
if(response){
res.sendStatus(204);
} else {
res.sendStatus(500);
}
}
});

Dans le fichier ./model/product.js, ajoutons la méthode suivante :

./model/product.js
export const deleteProduct = (id) => {
for (let i = 0; i < products.length; i++){
if(products[i].id === id){
products.splice(i, 1);
return true;
}
}
return true;
}

Et voici ce que cela donne :

Interface utilisateur d&#39;une application de test d&#39;API, affichant une requête DELETE.

Une interface de requête API montrant un message &quot;Not Found&quot;.

Structurer son application

Vous l’avez surement constaté, mais le fichier server.js commence à avoir beaucoup de lignes. Il est donc conseillé de diviser le code en plusieurs fichiers. Nous allons créer un système de routage pour chaque ressource (ici, juste produit) et séparer le code en controler et model.

Créez trois dossiers : controler, model et route. Dans le premier dossier, créez le fichier product.js et mettez ce code :

./controler/product.js
import * as productModel from '../model/product.js';

export const getProduct = (req, res) => {
const idTexte = req.params.id; // ATTENTION ! Il s'agit d'un texte !
const id = parseInt(idTexte);
if(isNaN(id)){
res.sendStatus(400);
} else {
try {
const produit = productModel.readProduct(id);
res.json(produit);
} catch (error) {
res.sendStatus(404);
}
}
};

export const addProduct = (req, res) => {
const body = req.body;
const {id, name, price} = body;
const response = productModel.createProduct(id, name, price);
if(response){
res.sendStatus(201);
} else {
res.sendStatus(500);
}
};

export const updateProduct = (req, res) => {
const {id, price} = req.body;
const response = productModel.updateProduct(id, price);
if(response){
res.sendStatus(204);
} else {
res.sendStatus(404);
}
};

export const deleteProduct = (req, res) => {
const idTexte = req.params.id; // ATTENTION ! Il s'agit d'un texte !
const id = parseInt(idTexte);
if(isNaN(id)){
res.sendStatus(400);
} else {
const response = productModel.deleteProduct(id);
if(response){
res.sendStatus(204);
} else {
res.sendStatus(500);
}
}
};

Dans le deuxième dossier, créez le fichier product.js et insérez ce code :

./model/product.js
//permet de simuler une base de données
const products = [
{id: 1, name: "Playstation 4", price:400},
{id: 2, name: "Xbox One", price:399.9},
{id: 3, name: "Nintendo Switch", price:349.99}
]

export const readProduct = (id) => {
const results = products.filter(p => p.id === id);
if(results.length > 0){
return results[0];
} else {
throw new Error("Aucun produit trouvé");
}
}

export const createProduct = (id, name, price) => {
products.push({
id,
name,
price
});
return true;
}

export const updateProduct = (id, price) => {
for(let i = 0; i < products.length; i++){
if(products[i].id === id){
products[i].price = price;
return true;
}
}
return false;
}

export const deleteProduct = (id) => {
for (let i = 0; i < products.length; i++){
if(products[i].id === id){
products.splice(i, 1);
return true;
}
}
return true;
}

Dans le troisième dossier, créez un premier fichier product.js et collez ce code :

./route/product.js
import {Router} from 'express';
import {
addProduct,
updateProduct,
getProduct, deleteProduct
} from "../controler/product.js";
const router = Router();

router.get('/:id', getProduct);
router.post('/', addProduct);
router.patch('/', updateProduct);
router.delete('/:id', deleteProduct);

export default router;

Puis créez un second fichier index.js avec le code suivant à l’intérieur :

./route/index.js
import {Router} from 'express';
import {default as productRouter} from "./product.js";
const router = Router();

router.use("/product", productRouter);

export default router;

Enfin, copiez/collez le code suivant dans le fichier server.js :

./server.js
import express from "express";
import {default as Router} from "./route/index.js";
const app = express();
const port = 3001;

app.use(express.json());
app.use(Router);

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});

Voilà, maintenant, vous avez une application bien mieux structurée. L’architecture qui vous est donnée peut être utilisée comme base pour un projet. Faites une copie de ce laboratoire, car nous l’utiliserons comme base dans le prochain.

Deux hommes assis sur un canapé dans un salon, l'un portant un pull rouge et l'autre un manteau brun.