Programmer en Node revient à programmer en Javascript. Voici par exemple un programme node :
const x = 3;
console.log('valeur de x:', x);
Pour exécuter le programme ci-dessus, tapez dans une console :
node n1.js
ce qui donne le résultat suivant :
valeur de x: 3
Le problème auquel vous allez être confronté est le suivant : vous allez devoir séparer votre programme (votre serveur) en plusieurs fichiers et des variables définies dans certains fichiers devront être accessibles d’autres fichiers. Cela nous amène à définir des variables globales.
Pour cela, Node possède un objet global et on peut imaginer que toute variable définie hors d’une fonction ou d’une classe en fait partie. Pour voir si c’est bien le cas, considérons le programme suivant :
// création de 3 variables.
// Rappel : il est préférable d'utiliser let ou const plutôt que var
let xxLet = 3;
var xxVar = 4;
xxRien = 5;
function xxFunc() { return True; }
// affichage de l'objet global de node
console.log(global);
Exécutons le programme et filtrons sur ce qui contient la chaîne xx :
node myvars.js | grep xx
On obtient alors :
xxRien: 5
On constate que :
les fonctions ne sont pas partie de l’objet global;
les variables créées avec let, var ou const n’en font pas partie non plus.
La solution, pallier cela, consiste à exploiter la notion de module.
Grosso modo, on peut considérer qu’en Node un module correspond à un fichier.
La portée des variables et fonctions que vous définissez dans un fichier fic.js est le fichier lui-même. Donc, par défaut, ces variables et fonctions ne seront pas connues des autres fichiers, comme indiqué dans l’exemple ci-dessus.
On peut observer le contenu d’un module, notamment son champ exports qui indique ce qu’il permet aux autres fichiers de connaître de lui :
console.log(module);
{
id: '.',
path: '/home/gonzales/enseignement/mobile-23-24/prog/src_FR/node',
exports: {},
filename: '/home/gonzales/enseignement/mobile-23-24/prog/src_FR/node/module_view.js',
loaded: false,
children: [],
paths: [
'/home/gonzales/enseignement/mobile-23-24/prog/src_FR/node/node_modules',
'/home/gonzales/enseignement/mobile-23-24/prog/src_FR/node_modules',
'/home/gonzales/enseignement/mobile-23-24/prog/node_modules',
'/home/gonzales/enseignement/mobile-23-24/node_modules',
'/home/gonzales/enseignement/node_modules',
'/home/gonzales/node_modules',
'/home/node_modules',
'/node_modules'
]
}
Pour rendre visible une variable ou une fonction, il suffit de l’exporter, en la rajoutant à la propriété exports du module, comme indiqué ci-dessous. On peut modifier le nom de ce qui est exporté, même si cela n’est pas forcément souhaitable :
function maFonction(x) { return x+1; }
module.exports.maFonction = maFonction; // même nom
module.exports.maFonctionExportee = maFonction; // autre nom
let maVar1 = 1; module.exports.maVar1 = maVar1;
var maVar2 = 1; module.exports.maVar2 = maVar2;
const maVar3 = 1; module.exports.maVar3 = maVar3;
console.log(module);
{
id: '.',
path: '/home/gonzales/enseignement/mobile-23-24/prog/src_FR/node',
exports: {
maFonction: [Function: maFonction],
maFonctionExportee: [Function: maFonction],
maVar1: 1,
maVar2: 1,
maVar3: 1
},
filename: '/home/gonzales/enseignement/mobile-23-24/prog/src_FR/node/exportation.js',
loaded: false,
children: [],
paths: [
'/home/gonzales/enseignement/mobile-23-24/prog/src_FR/node/node_modules',
'/home/gonzales/enseignement/mobile-23-24/prog/src_FR/node_modules',
'/home/gonzales/enseignement/mobile-23-24/prog/node_modules',
'/home/gonzales/enseignement/mobile-23-24/node_modules',
'/home/gonzales/enseignement/node_modules',
'/home/gonzales/node_modules',
'/home/node_modules',
'/node_modules'
]
}
Comme en PHP, le mot clef pour importer du code d’un autre fichier est require. Dans l’exemple ci-dessous, on récupère ainsi un objet qui contient précisément ce qui a été exporté. En principe, il est d’usage de donner à cet objet le même nom que le module importé. Ci-dessous, on crée l’objet en const afin d’éviter qu’on puisse le modifier par inadvertence.
const exportation = require('./exportation');
console.log("========================");
console.log(exportation);
console.log('appel fonction:', exportation.maFonction(exportation.maVar1));
{
id: '/home/gonzales/enseignement/mobile-23-24/prog/src_FR/node/exportation.js',
path: '/home/gonzales/enseignement/mobile-23-24/prog/src_FR/node',
exports: {
maFonction: [Function: maFonction],
maFonctionExportee: [Function: maFonction],
maVar1: 1,
maVar2: 1,
maVar3: 1
},
filename: '/home/gonzales/enseignement/mobile-23-24/prog/src_FR/node/exportation.js',
loaded: false,
children: [],
paths: [
'/home/gonzales/enseignement/mobile-23-24/prog/src_FR/node/node_modules',
'/home/gonzales/enseignement/mobile-23-24/prog/src_FR/node_modules',
'/home/gonzales/enseignement/mobile-23-24/prog/node_modules',
'/home/gonzales/enseignement/mobile-23-24/node_modules',
'/home/gonzales/enseignement/node_modules',
'/home/gonzales/node_modules',
'/home/node_modules',
'/node_modules'
]
}
========================
{
maFonction: [Function: maFonction],
maFonctionExportee: [Function: maFonction],
maVar1: 1,
maVar2: 1,
maVar3: 1
}
appel fonction: 2
Notez que l’on n’indique pas l’extension .js du fichier importé et que, pour l’importation des fichiers « locaux », c’est-à-dire ceux qui ne sont pas des packages node installés, on préfixe le nom du fichier par le répertoire courant « ./ ». L’affichage de la console ci-dessus montre que lorsque l’on require un fichier, on exécute toutes ses instructions (cf. les lignes avant la démarcation « ======================== ».
Node comprend de nombreux modules (cf. la documentation de Node). La plupart de leurs fonctions sont fournies en deux modes : synchones et asynchrones.
Le programme suivant permet d’afficher la liste des fichiers du répertoire courant. Dans la variable files, on récupère cette liste, puis on l’affiche.
const fs = require('fs');
const files = fs.readdirSync('.');
console.log(files);
Notez qu’ici, fs est le nom d’un package de node. Quand on l’inclut, on ne préfixe pas avec « ./ ».
La version asynchrone de ce programme est la suivante :
const fs = require('fs');
fs.readdir('.', function (err, files) {
// arrivé dans cette fonction, soit le readdir s'est bien passé et on a la liste
// des fichiers dans files et err n'est pas défini (null), soit il y a eu une
// erreur et files est null
// => on traite systématiquement les 2 cas :
if (err)
console.log ('Erreur :', err);
else
console.log ('fichiers :', files);
});
Dans les versions asynchrones, on a systématiquement un 2ème argument qui est une callback. Celle-ci a toujours un 1er paramètre qui permet de gérer les erreurs produites par la fonction asynchrone.
En exploitant les fonctions asynchrones et 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. Evidemment, 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'
);
}
}