En exploitant le package http, il est facile de créer un mini serveur web. Le serveur est en fait un emitter. On lui passe en paramètre la callback à appliquer quand un client se connecte :
request est l’url qu’a demandée le client,
result est ce que l’on renvoie au client
const http = require('http');
const serveur = http.createServer((request, result) => {
// on peut récupérer l'url demandée par l'utilisateur
let url = request.url;
// pour envoyer des données (HTML?) vers le client :
result.write(`hello ${url}`);
result.end();
}).listen(8080); // on indique au serveur d'écouter le port 8080
Notez que, dans le code du fichier http.js
, on peut faire autant de result.write()
que l’on souhaite. C’est le result.end() qui terminera la requête (autrement dit, elle
correspond au close() que l’on peut retrouver dans d’autres langages comme Python).
L’idée pour servir de multiples pages consiste simplement à faire des if/else sur la valeur de request.url, comme indiqué ci-dessous. Évidemment, c’est moyennement utilisable pour un gros site.
const http = require('http');
const serveur = http.createServer((request, result) => {
if (request.url === '/toto') {
result.write('<body>acces a toto</body>');
result.end();
}
else if (request.url === '/toto/titi') {
result.write('<body>acces a toto/titi<br/>');
result.write('fin de l\'acces</body>');
result.end();
}
else {
result.writeHead(404, 'page non trouvee');
result.end();
}
}).listen(8080); // on indique au serveur d'écouter le port 8080
Il y a deux inconvénients majeurs à utiliser le serveur ci-dessus :
Son code est pénible à écrire s’il y a beaucoup de routes différentes ;
C’est peu pratique si l’on a des routes paramétrées.
La solution : utiliser Express
C’est un framework puissant et pratique pour gérer les routes.
Il permet de réaliser des services (API) RESTfull (REpresentational State Transfer).
Il permet de mettre en œuvre les opérations CRUD (Create, Read, Update, Delete).
Les opérations CRUD et leurs méthodes HTTP associées sont les suivantes :
Op. CRUD | Signification | Méthode HTTP |
---|---|---|
Create | créer de nouvelles données | POST |
Read | récupérer des données | GET |
Update | mise à jour de données | PUT |
Delete | supprimer des données | DELETE |
export class CoursesService {
constructor(private http : HttpClient) { }
getCourses() : Observable<Course[]> {
return this.http.get<Course[]>(
'http://127.0.0.1/forum/getCourses.php'
);
}
}
Express est disponible via le package npm express
. Installez-le.
Vous verrez par la suite qu’Express aura également besoin d’autres packages npm
afin de récupérer les données des posts, de gérer les problèmes de cors, etc.
Aussi, installez les packages suivants : cors
et body-parser
.
Pour créer un serveur Express, il y a essentiellement 4 étapes :
importer Express ;
appeler la fonction express() afin de créer une application Express ;
écrire le code permettant de répondre aux requêtes (ci-dessous app.get) ;
faire un listen pour que le serveur écoute l’arrivée de nouveaux clients.
Voici un exemple de serveur web en Express :
// on importe express (cela renvoie une fonction, que l'on nomme express)
const express = require('express');
// on appelle la fonction pour créer notre appli Express. Cela permettra d'appeler
// les méthodes app.get(), app.post(), etc., pour réaliser les opérations CRUD
const app = express();
const port = 8080;
// ici, on indique ce qu'il faut faire si le client demande à accéder à l'URL
// "/toto" via un GET :
app.get('/toto', (request,result) => {
// result étend les méthodes du module Http. Send ne peut être appelé qu'1 fois
// et il inclut le `end()` que l'on exécute après les `write`.
result.send('<body>acces a toto</body>');
});
app.get('/toto/titi', (request,result) => {
result.write('<body>acces a toto/titi<br/>');
result.write('fin de l\'acces</body>');
result.end();
});
// on écoute les clients. Si url non trouvée ci-dessus, Express renvoie une erreur 404
app.listen(port, () => { console.log ('listening'); });
Pour l’exécuter :
node express1.js
Voici le résultat :
Dans le code ci-dessus, on imposait dans le code de l’application que le serveur attende ses clients sur le port 8080. On peut faire en sorte que le serveur récupère cette valeur directement de l’environnement dans lequel il est exécuté :
const express = require('express');
const app = express();
// en principe, pour spécifier le port d'écoute, on récupère la valeur d'une variable
// d'environnement PORT, si elle existe, sinon on utilise le port que l'on veut
const port = process.env.PORT || 8080;
app.get('/toto/titi', (request,result) => {
result.write ('<body>acces a toto/titi<br/>');
result.write ('fin de l\'acces</body>');
result.end ();
});
app.listen(port, () => { console.log (`listening du port ${port}`); });
Pour exécuter ce code en spécifiant dans l’environnement le numéro de port :
export PORT=4242
node express2.js
Voici le résultat dans la console :
listening du port 4242
Il est possible de spécifier des valeurs de paramètres dans les routes servies par le serveur Express. L’exemple ci-dessous montre comment faire. Il suffit d’ajouter un : puis le nom du paramètre dans la route. Le tableau request.params contient alors les valeurs de tous les paramètres de la route.
const express = require('express');
const app = express();
const port = process.env.PORT || 8080;
// comme dans les routes d'Angular, on peut spécifier des paramètres via ':'
app.get('/toto/:course_id/topics/:topic_id', (request,result) => {
// tous les paramètres sont récupérables dans l'objet request.params
result.send(`
cours numero ${request.params.course_id}<br/>
topic numero ${request.params.topic_id}
`);
});
app.listen(port, () => { console.log (`listening du port ${port}`); });
Voici le résultat obtenu :
Il est possible de spécifier des paramètres dans la route qui sont optionnels. Voir, dans l’exemple ci-dessous, le paramètre sortBy.
const express = require('express');
const app = express();
const port = process.env.PORT || 8080;
// Les paramètres spécifiés par ':' sont des paramètres "obligatoires".
// si l'on veut rajouter des paramètres optionnels, on utilise des "query strings" :
// on les spécifie avec ?nom=valeur dans l'URL du navigateur
app.get('/toto/:course_id', (request,result) => { // route = paramètres obligatoires
// les paramètres obligatoires sont récupérables dans l'objet request.params
// les paramètres optionnels sont dans l'objet request.query
result.send (`cours numero ${request.params.course_id}<br/>
tri par ${request.query.sortBy}`);
});
app.listen(port, () => { console.log (`listening du port ${port}`); });
Voici le résultat obtenu :
Comme vous l’avez vu en TP, il existe plusieurs manières de transférer des informations
du navigateur vers le serveur web. En autres, on peut le faire dans le body de la
requête. C’est ce que vous avez utilisé avec Postman et Angular. C’est également la
méthode qu’utilise le code ci-dessous, en exploitant le package bodyParser
.
const express = require('express');
const app = express();
const port = process.env.PORT || 8080;
// bodyParser permet de mettre en forme le body du post pour qu'on puisse facilement
// l'utiliser par la suite (c'est lui qui contient les données).
const bodyParser = require('body-parser');
// pour pouvoir parser des data transmises en application/x-www-form-urlencoded (comme
// le font les <form></form>) ainsi que des données transmises en JSON (Ajax) :
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
// ici, on récupère une demande du client transmise par post
app.post('/toto', (request,result) => {
// les données des post sont accessibles via request.body
result.write('Reponse de notre serveur : ');
result.write(JSON.stringify(request.body));
result.end ();
});
app.listen (port, () => { console.log (`listening du port ${port}`); });
Supposons que l’utilisateur ait chargé la page html ci-dessous :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Formulaire Express5</title>
</head>
<body>
<form method="post" action="http://127.0.0.1:8080/toto">
champ 1 : <input type="text" name="mon_champ1" /><br/>
champ 2 : <input type="text" name="mon_champ2" /><br/>
<button name="mon_bouton" value="bouton">soumettre à Express</button>
</form>
</body>
</html>
et qu’il ait rempli les champs de la manière suivante :
alors, il obtient la réponse suivante du serveur express5.js
:
Une autre manière de requêter le serveur Express consiste à utiliser Postman :
Évidemment, si le serveur Express gère de nombreuses routes, il peut être fastidieux de placer tout le code correspondant à celles-ci dans le même fichier. De plus, une telle approche rend le serveur difficilement maintenable.
Une méthode plus viable consiste à créer un fichier par route, comme vous l’avez fait
en PHP. Dans ce cas, l’idée consiste à placer dans chacun de ces fichiers une fonction
qui prend en paramètres le request et le result et qui exécute précisément le code
pour répondre à la requête adressée à la route. Par exemple, si l’on regarde le serveur
express5.js
ci-dessus, on peut créer le fichier toto.js
ci-dessous, qui
va répondre à la requête sur la route /toto :
function toto(request,result) {
// les données des post sont accessibles via request.body
result.write('Reponse de notre serveur : ');
result.write(JSON.stringify(request.body));
result.end();
}
module.exports.toto = toto;
Il suffit maintenant d’importer ce fichier dans le serveur Express et d’indiquer au app.post qu’il faut exécuter la fonction toto() :
const express = require('express');
const app = express();
const port = process.env.PORT || 8080;
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
// on importe ici les scripts des pages. Le mieux : 1 fichier par route
const toto = require('./toto');
app.post('/toto',(request, result) => { toto.toto(request,result); });
app.listen(port, () => { console.log (`listening du port ${port}`); });
On peut intégrer facilement des requêtes SQL dans les serveurs Express. Pour cela, il suffit de créer la connexion via la fonction createConnection() puis de réaliser les requêtes dans le code exécuté pour chaque route, comme indiqué ci-dessous. Notez que vous n’avez pas intérêt à utiliser db.end() en fin de fichier car vous souhaitez que la connexion à MySQL perdure jusqu’à ce que vous arrêtiez votre serveur Express.
const express = require('express');
const mysql = require('mysql2');
const db = mysql.createConnection({
host: 'localhost',
user: 'polytech',
password: 'polytech',
database: 'polytech' });
db.connect();
const app = express ();
app.get('/toto', (req,res) => {
db.query('SELECT * FROM courses WHERE id <= ?',
[2],
(error, rows) => {
if (error)
console.log(error);
else
res.send ('Reponse du serveur: ' + JSON.stringify(rows));
});
});
app.listen (3000, () => { console.log (`listening du port 3000`); });
Comme on l’a vu plus haut, il est intéressant d’organiser le code avec un fichier par route. Il est également utile de séparer complètement les requêtes SQL de la logique de l’application. Cela permet une maintenance plus facile et, si on décide de changer la base de données, on n’a pas à réécrire tout le serveur mais juste la partie relative à la base de données.
On peut donc réorganiser le code du serveur ci-dessus de la manière suivante :
un répertoire contient le code de toutes les requêtes SQL. Ici, il s’agit du
fichier sql_connect.js
qui réalise la connexion à la base de données et
du fichier sql_courses.js
qui réalise des requêtes SQL sur la table Courses.
Pour chaque route, on a un fichier qui contient les instructions pour répondre
à la requête web de cette route. Ici, il s’agit du fichier my_route.js
.
Un fichier contient notre serveur Express. Ici, il s’agit du fichier
express_mysql2.js
.
.
├── config.js # fichier de configuration du backend
├── database # répertoire contenant toutes les queries SQL
│ ├── sql_connect.js # script de connexion au serveur SQL
│ └── sql_courses.js # des queries SQL
├── express_mysql2.js # le serveur backend
├── my_route.js # le fichier à exécuter pour la route /myroute
├── package.json
└── package-lock.json
// étant donné qu'il y aura plein de variables à exporter, autant les
// placer dans un objet Javascript
const config = {
// la configuration pour se connecter à la base de données
sqlHost: 'localhost',
sqlLogin: 'polytech',
sqlPassword: 'polytech',
sqlDatabase: 'polytech',
charset: 'utf8',
// les noms des tables de la base de données
sqlCourses: 'forumCourses',
};
// ici, j'indique directement que exports est égal à config. Cela va
// permettre d'écrire directement : const config = require(./config);
module.exports = config;
// script de connexion à la base de données
const mysql = require('mysql2');
const config = require('../config');
const db = mysql.createConnection({
host: config.sqlHost, // ici, on peut utiliser notre fichier
user: config.sqlLogin, // de configuration pour entrer les
password: config.sqlPassword, // paramètres de connexion
database: config.sqlDatabase
});
db.connect();
module.exports = db;
// on peut écrire autant de fois que l'on veut les "require" ci-dessous,
// tout au long de l'application, ils ne seront exécutés et importés
// qu'une seule fois
const config = require('../config');
const db = require('./sql_connect');
// ici, on retourne une Promise
function myQuery() {
return new Promise((resolve, reject) => {
db.query(
`SELECT * FROM ${config.sqlCourses} WHERE id <= ?`,
[2],
(error, results) => { // quand on a la réponse du serveur Mysql,
if (error) reject(error); // on rejette la promesse en cas d'erreur ou
else resolve(results); // on la résout si tout est OK
});
});
}
// Si le fichier sql_courses.js contient toutes les queries se rapportant
// à la table courses de la base de données, module.exports doit être un
// objet contenant plusieurs queries, d'où l'export suivant :
module.exports.myQuery = myQuery;
// j'utilise Courses avec un C majuscule car c'est un objet, qui contiendra
// plusieurs queries. Notez qu'en important ce fichier, on assure que l'on
// se connecte à la base de données
const Courses = require('./database/sql_courses');
// la fonction exécutée pour la route /myRoute : elle appelle une fonction
// qui requête la base de données
async function myRoute(request, result) {
const rows = await Courses.myQuery();
result.send ('Reponse du serveur: ' + JSON.stringify(rows));
}
module.exports = myRoute;
const express = require ('express');
const app = express ();
// pour gérer les posts
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
// organisation : 1 fichier par route
const myRoute = require('./myRoute');
app.get('/myroute', (req,res) => {myRoute(req, res);});
app.listen(3000, () => { console.log (`listening du port 3000`); });
En vous inspirant de ce qui précède, écrivez le fichier qui démarre votre serveur
Express sur le port 3000 (appelons-le serveur.js
) et créez la route du
checkLogin qui, pour l’instant, ne transmettra que la chaîne de caractères « toto ».
Testez avec Postman que cela fonctionne bien.
Rajoutez le fichier message.js
suivant qui émule les fonctions sendMessage
et sendError de PHP :
// renvoie un message au format JSON. On a besoin de passer en paramètre
// res, la réponse que l'on envoie au client (Angular). Le paramètre
// data est un objet JavaScript. Globalement, cette fonction est
// équivalente au "echo json_encode(data);" que vous utilisiez en PHP
function sendMessage(res, data) {
res.json({ status: 'ok', data: data });
}
function sendError(res, reason) {
res.json({ status: 'error', data: {reason: reason }});
}
// avec les accolades, il est possible d'exporter plusieurs fonctions sans que
// celui qui importe le fichier ait besoin de spécifier nom_module.fonction.
// par exemple, si j'importe message.js, je peux appeler directement sendMessage()
module.exports = { sendMessage, sendError };
dans ces fonctions, il y a deux différences principales avec celles de
PHP auxquelles il faudra faire attention :
Elles nécessitent qu’on leur passe l’objet res. Il n’y a pas moyen d’éviter cela.
En PHP, la dernière instruction de ces fonctions était die, qui permettait de terminer le script dès qu’on les appelait. Ici, on ne peut pas faire la même chose car cela arrêterait le serveur. Ce que je vous conseille donc pour parvenir au même résultat, c’est de ne pas appeler directement sendMessage ou sendError mais plutôt de faire :
return sendMessage(res, mes_données);
return sendError(res, "aucune raison valable");
En préfixant avec return, on arrête la fonction qui correspond à la route requêtée par l’utilisateur mais pas le serveur.
Pour importer ces fonctions, il suffira d’écrire :
const {sendError, sendMessage} = require ("./message");
Modifiez votre fichier checkLogin.js
de manière à ce qu’il utilise sendMessage
pour toujours renvoyer un message indiquant une authentification réussie.
Redémarrez votre serveur et testez avec Postman.
C’est pénible de devoir redémarrer le serveur à chaque fois que l’on réalise une
modification dans le code. Pour éviter cela, installez le package npm nodemon
.
À partir de maintenant, plutôt que d’exécuter node serveur.js, exécutez :
nodemon serveur.js
Chaque fois que vous modifierez des fichiers, nodemon redémarrera automatiquement votre serveur.
Modifiez maintenant votre fichier checkLogin.js
de manière à ce que vous testiez
bien que le client a transmis via post un login et un password. Si, selon votre base
de données, le couple login/password est invalide, checkLogin.js
renvoie un
message d’erreur, sinon un message indiquant que l’utilisateur a bien été authentifié.
Votre frontend Angular doit être mis à jour afin d’utiliser votre nouveau backend en Node/Express. Ainsi, les URL du backend doivent être modifiées. Par exemple, en PHP, le script assurant l’authentification des utilisateurs peut être :
http://127.0.0.1/forum-angular-php/backend/checkLogin.php
alors que celui en Node/Express pourra être :
http://127.0.0.1:3000/checkLogin
De la manière dont vous avez créé votre frontend Angular, seules 2 modifications sont nécessaires pour que celui-ci utilise votre backend en Node/Express. Elles sont situées toutes les deux dans le code de la classe MessageService :
remplacez le préfixe de l’URL du backend PHP par http://127.0.0.1:3000 (en supposant que votre serveur Express écoute le port 3000). Supprimez également l’ajout de l’extension .php que vous rajoutiez aux URLs.
Traditionnellement, dans les transferts d’informations vers Node/Express, on n’utilise pas les FormData, on transmet directement des objets Javascript. Donc, mettez entre commentaires le code que vous aviez écrit pour remplir le FormData et passez directement en argument de this.http.post les données passées en argument à votre méthode sendMessage.
Voilà, la mise à jour de votre frontend est terminée. Vous n’aurez plus aucune modification à y apporter. Notez bien que cela résulte du fait que l’on a utilisé un service de messagerie qui abstrait les envois de messages, plutôt que d’utiliser directement dans toutes les classes des instances de HttpClient.
Testez si, lorsque vous cliquez sur le bouton de connexion de votre frontend, vous êtes bien redirigé(e) vers la page des cours et, seulement ensuite, lisez le paragraphe suivant.
Vous obtenez un message d’erreur dû au CORS. Eh oui, votre backend n’est plus sur le
port 80, il est sur le port 3000, mais votre frontend est toujours sur le port 4200,
qui est différent de 3000, d’où le CORS. Pour pallier cela, rajoutez dans le fichier
serveur.js
les instructions suivantes qui vont régler le problème, comme le faisait
le début du fichier helper.php
:
........
const app = express ();
// permet d'éviter le problème de CORS que l'on avait déjà vu
const cors = require ('cors');
app.use(cors({origin: 'http://127.0.0.1:4200', credentials: true}));
........
Retestez une connexion à partir de votre frontend. Cela devrait fonctionner un peu
mieux même si ce n’est pas encore « top » puisque vous n’avez pas encore écrit le fichier
getCourses.js
.
Rajoutez à votre backend node la route getCourses ainsi que le code permettant de répondre à cette route (vous traduirez simplement le code PHP que vous aviez écrit pour cette route). Pour l’instant, on suppose que l’utilisateur a bien été authentifié et vous rajouterez au début de votre code une variable qui indique « en dur » qui est l’utilisateur.
Testez avec Postman que votre code est correct puis testez avec votre frontend.
Voici quelques références qui pourraient vous être utiles concernant le CORS :