1ère utilisation de Node

Programmer en Node revient à programmer en Javascript. Voici par exemple un programme node :

n1.js
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

Node et l'objet global

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 :

myvars.js
// 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 :

  1. les fonctions ne sont pas partie de l’objet global;

  2. 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.

Les modules

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 :

module_view.js
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'
  ]
}

Les exportations

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 :

exportation.js
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'
  ]
}

Les importations

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.

importation.js
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 « ======================== ».

Modules et asynchronie

Node comprend de nombreux modules (cf. la documentation de Node). La plupart de leurs fonctions sont fournies en deux modes : synchones et asynchrones.

Modules usuels en mode synchrone et asynchrone

Utilisations de fonctions synchrones

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.

readDirSync.js
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 :

readDirAsync.js
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.

Créer un serveur web en Node

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 :

http.js
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
Le mini serveur web en pratique

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).

Le serveur avec plusieurs pages

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.

http2.js
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

Passage à l'échelle du serveur web

Il y a deux inconvénients majeurs à utiliser le serveur ci-dessus :

  1. Son code est pénible à écrire s’il y a beaucoup de routes différentes ;

  2. C’est peu pratique si l’on a des routes paramétrées.

La solution : utiliser Express

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

Exemple Angular de GET :

http_client1.ts
export class CoursesService {
  constructor( private http : HttpClient ) { }

  getCourses () : Observable<Course[]> {
    return this.http.get<Course[]>(
      'http://127.0.0.1/forum/getCourses.php'
    );
  }
}
 
© C.G. 2007 - 2024