<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Grégory CHEVALLIER</title>
    <description>The latest articles on Forem by Grégory CHEVALLIER (@messagegit).</description>
    <link>https://forem.com/messagegit</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1011313%2F3a450583-a400-4a10-936c-8cc1570eab84.png</url>
      <title>Forem: Grégory CHEVALLIER</title>
      <link>https://forem.com/messagegit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/messagegit"/>
    <language>en</language>
    <item>
      <title>💾 Automatiser le « backup » d'une base de donnée MySQL avec NestJS et TypeORM</title>
      <dc:creator>Grégory CHEVALLIER</dc:creator>
      <pubDate>Tue, 17 Oct 2023 07:06:32 +0000</pubDate>
      <link>https://forem.com/messagegit/automatiser-le-backup-dune-base-de-donnee-mysql-avec-nestjs-et-typeorm-i7k</link>
      <guid>https://forem.com/messagegit/automatiser-le-backup-dune-base-de-donnee-mysql-avec-nestjs-et-typeorm-i7k</guid>
      <description>&lt;p&gt;Parmi les différentes causes à l'origine d'une journée cauchemardesque 😫 dans la vie d'un(e) développeu.r.se, on retrouve le célèbre « drop » inopiné (à l'effet tant redouté) d'une base de données en environnement de production qui permet à celles et ceux qui la subissent de voir passer le temps plus lentement qu'il ne l'a jamais été auparavant. &lt;/p&gt;

&lt;p&gt;🗑️ En bref, la base de données s'est vidée de son contenu spontanément en raison d'un incident technique.&lt;/p&gt;

&lt;p&gt;Les applications/serveurs sollicitant l'utilisation de votre base de données sont dès lors impacté(e)s de manière critique ‼️. Celles-ci sont momentanément interrompues et votre environnement de production devient « down » (inaccessible).&lt;/p&gt;

&lt;p&gt;De nombreux incidents peuvent être à l'origine d'un « drop » de la base de données :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Une maladresse causée durant la réalisation d'une tâche « DevOps » sur serveur&lt;/li&gt;
&lt;li&gt;Le datacenter hébergeant votre serveur est victime d'un incident (dernier évènement majeur en date : &lt;a href="https://fr.wikipedia.org/wiki/Incendie_du_centre_de_donn%C3%A9es_d%27OVHcloud_%C3%A0_Strasbourg"&gt;incendit 🔥 d'un datacenter OVHcloud à Strasbourg le 10 mars 2021&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Le déploiement d'une mise à jour impliquant la modification structurelle des entitées internes à la base de données&lt;/li&gt;
&lt;li&gt;La résiliation à date échéante de votre formule d'hébergement et donc la réinitialisation du serveur intégrant la base de données&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;[...] Et tant d'autres.&lt;/p&gt;

&lt;p&gt;🤷‍♂️ Comment l'appréhender ?&lt;/p&gt;

&lt;p&gt;L'implémentation d'un système de backup 💾 est l'une des solutions parmi les plus recommandées lorsqu'il s'agit de protéger l'intégrité des données liées à votre application&lt;/p&gt;

&lt;p&gt;Cette solution génère, selon un intervalle temporel défini, un export (partiel ou intégral) de la base de données. Un « snapshot » de la base de données est alors daté et exporté localement ou sur un serveur tiers.&lt;/p&gt;

&lt;p&gt;Le cumul de ces snapshots va permettre de restaurer, en cas d'incident, la majeure partie, voir l'intégralité des données perdues depuis une date spécifique.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ Pré-requis
&lt;/h2&gt;

&lt;p&gt;La réalisation de cette technique nécessite l'utilisation d'une API &lt;a href="https://nestjs.com/"&gt;NestJS&lt;/a&gt; (framework « server-side » basé sur Node.js) ainsi que l'utilisation de &lt;a href="https://typeorm.io/"&gt;TypeORM&lt;/a&gt; (ORM JavaScript et TypeScript majoritairement utilisé sur Node.js). &lt;/p&gt;

&lt;p&gt;Elle a comme objectif la création d'une archive (.zip) incluant les différentes tables de votre base de données (.sql), dans un répertoire spécifique sur serveur, à date de son exécution.&lt;/p&gt;

&lt;p&gt;Une base de données MySQL doit être disponible sur le même environnement que celui où est exécutée l'API NestJS. (Celle-ci peut parfaitement être intégrée dans un « Docker Container »)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ Si la base de données est démarrée depuis Docker, les fonctionnalités Docker doivent être exploitables via bash sans avoir à justifier de permissions « super utilisateur ».&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📦 Création du service dédié aux tâches
&lt;/h2&gt;

&lt;p&gt;La première étape de notre implémentation requiert la création d'un nouveau service dédié aux tâches.&lt;/p&gt;

&lt;p&gt;Les dossiers &lt;code&gt;tasks&lt;/code&gt; et &lt;code&gt;services&lt;/code&gt; ainsi que les fichiers &lt;code&gt;tasks.module.ts&lt;/code&gt; et &lt;code&gt;tasks.service.ts&lt;/code&gt; doivent être créés selon l'arborescence suivante (à ajuster selon l'architecture de votre API) :&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Arborescence des fichiers 👇&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   - 🗂️ src
        - 🗂️ tasks
            - 🗂️ services
                - 🔵 tasks.service.ts
            - 🟢 tasks.module.ts
        - 🟢 app.module.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Procédons à l'export du nouveau service fraîchement créé :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    import { Injectable } from '@nestjs/common';

    @Injectable()
    export class TasksService {

    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Puis ajoutons la configuration minimale requise à notre nouveau module &lt;code&gt;tasks.module.ts&lt;/code&gt; en important le fichier &lt;code&gt;tasks.service.ts&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    import { Module } from '@nestjs/common';
    import { ConfigModule } from '@nestjs/config';

    import { TasksService } from './services/tasks.service';

    @Module({
        imports: [ ConfigModule ],
        providers: [ TasksService ],
        controllers: [ ],
    })
    export class TasksModule {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dans notre fichier &lt;code&gt;app.module.ts&lt;/code&gt;, le module récemment créé doit être importé de la manière suivante :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    import { TasksModule } from './tasks/tasks.module';
    /* ... */

    @Module({
      imports: [ 
        TasksModule,
        /* ... */
      ],
      controllers: [],
      providers: [],
    })

    export class AppModule {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Il ne nous reste plus qu'à implémenter de nouvelles fonctionnalités dans le nouveau service créé 🔥 &lt;/p&gt;




&lt;h2&gt;
  
  
  ⏰ Implémentation de la tâche CRON dédiée au « backup »
&lt;/h2&gt;

&lt;p&gt;Pour planifier l'export de notre base de données, &lt;strong&gt;NestJS&lt;/strong&gt; rend disponible le package &lt;code&gt;@nestjs/schedule&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Ce dernier permet de programmer l'exécution d'une fonction à une date/heure fixe, à des intervalles récurrents ou une fois après un intervalle donné.&lt;/p&gt;

&lt;p&gt;Les packages suivants doivent alors être installés :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    $ npm install --save @nestjs/schedule
    $ npm install --save-dev @types/cron
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;@nestjs/schedule&lt;/code&gt; doit, une fois installé, être importé dans le fichier &lt;code&gt;app.module.ts&lt;/code&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;L'import &lt;code&gt;import { ScheduleModule } from '@nestjs/schedule';&lt;/code&gt; doit être ajouté.&lt;/li&gt;
&lt;li&gt;Notre &lt;code&gt;AppModule&lt;/code&gt; doit également importer : &lt;code&gt;ScheduleModule.forRoot()&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    import { ScheduleModule } from '@nestjs/schedule'; // import from package here
    /* ... */

    @Module({
      imports: [ 
        ScheduleModule.forRoot(), // module import here
        /* ... */
      ],
      controllers: [],
      providers: [],
    })

    export class AppModule {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nous pouvons dès lors implémenter une ⏰ &lt;strong&gt;tâche programmée&lt;/strong&gt; depuis le service dédié aux tâches &lt;code&gt;TasksService&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    import { Injectable } from '@nestjs/common';
    import { Cron, CronExpression } from '@nestjs/schedule';

    @Injectable()
    export class TasksService {

        @Cron(CronExpression.EVERY_10_SECONDS)
        async exportDB() {
            // do something
        }

    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;L'enum &lt;code&gt;CronExpression&lt;/code&gt; fourni une correspondance explicite des différentes expressions qui permettent de définir l'intervalle d'exécution. Il est conseillé de l'importer pour améliorer la clarté du code.&lt;/p&gt;

&lt;p&gt;Nous utiliserons &lt;code&gt;EVERY_10_SECONDS&lt;/code&gt; pour les phases de test.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️  Selon le comportement attendu, l'exécution du backup pourrait très bien être exécuté une fois chaque semaine de sorte à créer un point de sauvegarde sans accumuler excessivement de données de stockage.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;La fonction &lt;code&gt;exportDB()&lt;/code&gt; est à présent sollicitée depuis un intervalle donné, voyons comment exporter la base de données.&lt;/p&gt;




&lt;h2&gt;
  
  
  📤 Export des tables depuis le serveur MySQL
&lt;/h2&gt;

&lt;p&gt;A la racine de notre projet, le dossier &lt;code&gt;backups&lt;/code&gt; doit être créé, il va permettre de répertorier les archives (.zip) qui seront générées par notre système de sauvegarde.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚙️ Déclarations principales
&lt;/h3&gt;

&lt;p&gt;Commençons par déclarer les constantes essentielles à notre tâche :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    /* Settings */
    const DEBUG_MODE = false; // Pass to 'true' to print the script exec output and display errors
    const BACKUPS_DIRECTORY = 'backups'; // Directory where will be exported the snapshot
    const DOCKER_CONTAINER_NAME = false; // Add a Docker container name if your mysql server depends of Docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;ℹ️ La constante DOCKER_CONTAINER_NAME permet d'accéder au serveur MySQL d'un éventuel processus Docker.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Puis prélever les informations relatives à notre base de données courante.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    ...

    /* Database */
    const databaseCredentials: ConnectionCredentials&amp;lt;ConnectionOptions&amp;gt; = await getConnectionOptions('default');
    const tablesList = await getManager().query(`SHOW tables;`);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La commande MySQL &lt;code&gt;SHOW tables;&lt;/code&gt; permet d'obtenir les différentes tables de notre base de données.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️  Libre à vous de paramètrer l'export d'une ou plusieurs base(s) de données selon l'architecture de votre API.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Les informations de connexion au serveur MySQL &lt;code&gt;databaseCredentials&lt;/code&gt; nous permettront de nous authentifier à la base de données pour y ordonner l'export des tables cibles.&lt;/p&gt;

&lt;p&gt;Les dernières constantes à déclarer sont des informations utiles au référencement de notre snapshot :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    ...

    /* Main declarations */
    const dateOfSnapshot = DateTime.now().toFormat('yyyy-LL-dd'); // Today as "2023-04-29" format
    const exportedFiles = []; // Temporarily created (.sql) files are pushed in this const
    const archiveToCreate = `${BACKUPS_DIRECTORY}/${dateOfSnapshot}_snapshot.zip`; // Relative path for snapshot from root of project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;ℹ️  La librairie Luxon est utilisée dans le code ci-dessus et fournit DateTime, elle n'est cependant pas indispensable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;La déclaration de &lt;code&gt;dateOfSnapshot&lt;/code&gt; va permettre d'ajouter la date à laquelle a été effectué notre snapshot.&lt;/p&gt;

&lt;p&gt;Les déclarations essentielles à notre fonction sont faites, il ne reste plus qu'à importer les fonctionnalités utilisées en tête de notre fichier &lt;code&gt;tasks.service.ts&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
    import { DateTime } from 'luxon';
    import { getManager, getConnectionOptions, ConnectionOptions } from 'typeorm';

    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  📤 Export des tables SQL
&lt;/h2&gt;

&lt;p&gt;Un script programmé par l'intermédiaire de l'API et exécuté sur le même serveur que l'API, va permettre la connexion au serveur MySQL et ainsi permettre l'export des différentes tables inclues dans votre base de données.&lt;/p&gt;

&lt;p&gt;Pour poursuivre, le package &lt;code&gt;shelljs&lt;/code&gt; (basé sur l'utilisation du module &lt;strong&gt;Node&lt;/strong&gt; &lt;a href="https://nodejs.org/api/child_process.html#child-process"&gt;child_process&lt;/a&gt;) permettant l'exécution de processus externes va devoir être installé.&lt;/p&gt;

&lt;p&gt;Utilisez &lt;code&gt;npm install shelljs&lt;/code&gt;, puis, en dessous des imports de votre service &lt;code&gt;tasks.service.ts&lt;/code&gt;, ajoutez :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    ...
    const shell = require('shelljs');
    const fs = require('fs');

    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Actuellement, si l'on part du principe que le nom de notre base de données correspond à : &lt;code&gt;test_database&lt;/code&gt;, la constante &lt;code&gt;tablesList&lt;/code&gt; préalablement définit contiendra un tableau dont le format sera le suivant :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
    { Tables_in_test_database: 'first_table' },
    { Tables_in_test_database: 'second_table' },
    { Tables_in_test_database: 'third_table' },
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nous savons dès lors quelles tables exporter, nous avons également des identifiants d'authentification pour accéder à notre serveur MySQL, l'export peut commencer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    ...

    try {
        console.log(`💾 Trying to export the database for the ${DateTime.now().toFormat('dd LLL yyyy')}..`);
        tablesList.map(table =&amp;gt; { // Export of differents tables to .sql format
            const nameOfTable = Object.values(table)[0];
            const fileOfSnapshot = `${dateOfSnapshot}_${nameOfTable}.sql`;
            const resultOfExport = shell.exec(`${DOCKER_CONTAINER_NAME ? `docker exec ${DOCKER_CONTAINER_NAME} ` : ''}mysqldump -u ${databaseCredentials.username} --password=${databaseCredentials.password} ${databaseCredentials.database} ${nameOfTable} &amp;gt; ${fileOfSnapshot}`, { silent: !DEBUG_MODE });
            if (!String(fs.readFileSync(fileOfSnapshot, 'utf8')).includes('INSERT INTO')) // Check of SQL export
                throw `Unable to export '${nameOfTable}' table from the database!\n&amp;gt; ${resultOfExport.stderr}`;
            else console.log(`📥 SQL Table '${fileOfSnapshot}' is successfully retrieved.`);
        });
        console.log('✅ Database saved!');
    } catch (err) {
        console.log('❌ Snapshot of the database has failed!', DEBUG_MODE ? '\n' + err : '');
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On ajoute ici les instructions : &lt;code&gt;try&lt;/code&gt; et &lt;code&gt;catch&lt;/code&gt;.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ Si la constante &lt;code&gt;DOCKER_CONTAINER_NAME&lt;/code&gt; a été configurée, le premier ordre d'exécution concerne &lt;strong&gt;Docker&lt;/strong&gt; et permet la connexion au serveur &lt;strong&gt;MySQL&lt;/strong&gt; à travers le conteneur qui l'exécute.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;La requête &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html"&gt;mysqldump&lt;/a&gt; est exécutée pour chaque table, alimentée par les informations d'authentification fournis par &lt;code&gt;databaseCredentials&lt;/code&gt; (connexion courante retournée par &lt;strong&gt;TypeORM&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;Chaque table est prétendument exportée &lt;em&gt;(à la racine du projet pour le moment)&lt;/em&gt; au format &lt;code&gt;.sql&lt;/code&gt; et selon &lt;code&gt;fileOfSnapshot&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Pour le vérifier, nous utilisons la fonction &lt;code&gt;readFileSync&lt;/code&gt; du module &lt;strong&gt;File System&lt;/strong&gt; &lt;em&gt;(fs)&lt;/em&gt;, nous vérifions ainsi que le fichier cible existe et inclut bien la chaine alphanumérique &lt;code&gt;INSERT INTO&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Si c'est le cas, la table suivante est exportée jusqu'à ce que toutes le soient. Le cas échéant, une occurrence de l'instruction &lt;code&gt;catch&lt;/code&gt; survient et l'export échoue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    💾 Trying to export the database for the 29 Apr 2023..
    📥 SQL Table '2023-04-29_first_table.sql' is successfully retrieved.
    📥 SQL Table '2023-04-29_second_table.sql' is successfully retrieved.
    📥 SQL Table '2023-04-29_third_table.sql' is successfully retrieved.
    ✅ Database saved!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trois fichiers &lt;code&gt;.sql&lt;/code&gt; ont été créés à la racine du projet.&lt;/p&gt;

&lt;p&gt;Voyons à présent comment créer un snapshot en générant &lt;strong&gt;une archive&lt;/strong&gt; &lt;em&gt;(.zip)&lt;/em&gt; dans notre dossier &lt;code&gt;backups&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🗂️ Génération d'une nouvelle archive (.zip) en guise de snapshot
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Générer une archive&lt;/strong&gt; est idéal pour &lt;strong&gt;garantir la sauvegarde de notre base de données&lt;/strong&gt;, nos données sont dès lors compressées puis enregistrées et datées depuis un fichier &lt;code&gt;.zip&lt;/code&gt; individuel, dans le répertoire préalablement créé : &lt;code&gt;backups&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ Des fichiers/répertoires autres que notre base de données peuvent parfois intégrer notre archive : pour certaines applications, il peut être nécessaire d'enregistrer le(s) répertoire(s) dans le(s)quel(s) sont hébergées les différentes ressources exploitées (= images de profil, uploads, etc..) pour &lt;strong&gt;garantir la disponibilité des ressources internes référencées en base de données&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A l'heure actuelle, seul l'export des tables SQL de notre base de données est effectué, à la racine de notre projet.&lt;/p&gt;

&lt;p&gt;Voyons comment transformer ces exports en fichiers temporaires pour procéder à la création d'une archive :&lt;/p&gt;

&lt;p&gt;Lorsque chaque table est exportée, le nom du fichier temporaire créé (.sql) doit être référencé dans le tableau &lt;code&gt;exportedFiles&lt;/code&gt; &lt;em&gt;(préalablement défini)&lt;/em&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    tablesList.map(table =&amp;gt; { // Export of differents tables to .sql format
        const nameOfTable = Object.values(table)[0];
        const fileOfSnapshot = `${dateOfSnapshot}_${nameOfTable}.sql`;
        exportedFiles.push(fileOfSnapshot); // Push the temporarily file name here
        const resultOfExport = shell.exec(`${DOCKER_CONTAINER_NAME ? `docker exec ${DOCKER_CONTAINER_NAME} ` : ''}mysqldump -u ${databaseCredentials.username} --password=${databaseCredentials.password} ${databaseCredentials.database} ${nameOfTable} &amp;gt; ${fileOfSnapshot}`, { silent: !DEBUG_MODE });
        if (!String(fs.readFileSync(fileOfSnapshot, 'utf8')).includes('INSERT INTO')) // Check of SQL export
            throw `Unable to export '${nameOfTable}' table from the database!\n&amp;gt; ${resultOfExport.stderr}`;
        else console.log(`📥 SQL Table '${fileOfSnapshot}' is successfully retrieved.`);
    });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nos deux instructions &lt;code&gt;try&lt;/code&gt; et &lt;code&gt;catch&lt;/code&gt; doivent ensuite être complétées par l'instruction &lt;code&gt;finally&lt;/code&gt;. Elle permettra d'exécuter une opération à terme des opérations initiales, quelque soit l'issue des opérations.&lt;/p&gt;

&lt;p&gt;Grâce à l'instruction &lt;code&gt;finally&lt;/code&gt;, les fichiers temporairement créés seront supprimés à terme des opérations, que l'archive soit créée ou non :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    try {
        ...

    } catch (err) {
        console.log('❌ Snapshot of the database has failed!', DEBUG_MODE ? '\n' + err : '');
    } finally { // Finally, delete the temporarily .sql files
        exportedFiles?.map(exportedFile =&amp;gt; 
            (fs.existsSync(exportedFile)) &amp;amp;&amp;amp; shell.exec(`rm -rf ${exportedFile}`, { silent: !DEBUG_MODE }));
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Il ne nous reste plus qu'à implémenter la génération d'une archive &lt;em&gt;(.zip)&lt;/em&gt; après que soient générés les exports temporaires &lt;em&gt;(.sql)&lt;/em&gt; dans notre instruction &lt;code&gt;try&lt;/code&gt; puis de s'assurer que l'archive soit correctement générée :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    console.log(`📦 Trying to generate an archive (.zip) from retrieved data..`);
    const resultOfArchive = shell.exec(`zip -r ${archiveToCreate} ${exportedFiles.join(' ')}`, { silent: !DEBUG_MODE }); // Create .zip archive from .sql exports
    if (fs.existsSync(archiveToCreate)) {
        console.log(`📎 (+) ${exportedFiles.length} table(s) added to archive.`);
        console.log(`✅ Snapshot '${archiveToCreate}' is successfully created.`);
    } else throw `Unable to generate a zip snapshot of the database!\n&amp;gt; ${resultOfArchive.stderr}`; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Les différentes tables exportées sont ici enregistrées dans un fichier &lt;code&gt;.zip&lt;/code&gt; daté, dans notre répertoire &lt;code&gt;backups&lt;/code&gt;. Si l'archive n'existe pas après l'opération : une erreur survient et un rapport d'erreurs est envoyé.&lt;/p&gt;

&lt;p&gt;Quoi qu'il advienne de la création du snapshot au format &lt;code&gt;.zip&lt;/code&gt;, les exports temporaires sont inévitablement supprimés en fin d'opération. &lt;/p&gt;

&lt;h3&gt;
  
  
  🤲 Aperçu global de notre fonction
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    @Cron(CronExpression.EVERY_WEEK)
    async exportDB() {
        /* Settings */
        const DEBUG_MODE = false; // Pass to 'true' to print the script exec output and display errors
        const BACKUPS_DIRECTORY = 'backups'; // Directory where will be exported the snapshot
        const DOCKER_CONTAINER_NAME = false; // Add a Docker container name if your mysql server depends of Docker
        /* Database */
        const databaseCredentials: ConnectionCredentials&amp;lt;ConnectionOptions&amp;gt; = await getConnectionOptions('default');
        const tablesList = await getManager().query(`SHOW tables;`);
        /* Main declarations */
        const dateOfSnapshot = DateTime.now().toFormat('yyyy-LL-dd');
        const exportedFiles = [];
        const archiveToCreate = `${BACKUPS_DIRECTORY}/${dateOfSnapshot}_snapshot.zip`;
        try {
            console.log(`💾 Trying to export the database for the ${DateTime.now().toFormat('dd LLL yyyy')}..`);
            tablesList.map(table =&amp;gt; { // Export of differents tables to .sql format
                const nameOfTable = Object.values(table)[0];
                const fileOfSnapshot = `${dateOfSnapshot}_${nameOfTable}.sql`;
                exportedFiles.push(fileOfSnapshot);
                const resultOfExport = shell.exec(`${DOCKER_CONTAINER_NAME ? `docker exec ${DOCKER_CONTAINER_NAME} ` : ''}mysqldump -u ${databaseCredentials.username} --password=${databaseCredentials.password} ${databaseCredentials.database} ${nameOfTable} &amp;gt; ${fileOfSnapshot}`, { silent: !DEBUG_MODE });
                if (!String(fs.readFileSync(fileOfSnapshot, 'utf8')).includes('INSERT INTO')) // Check of SQL export
                    throw `Unable to export '${nameOfTable}' table from the database!\n&amp;gt; ${resultOfExport.stderr}`;
                else console.log(`📥 SQL Table '${fileOfSnapshot}' is successfully retrieved.`);
            });
            console.log(`📦 Trying to generate an archive (.zip) from retrieved data..`);
            const resultOfArchive = shell.exec(`zip -r ${archiveToCreate} ${exportedFiles.join(' ')}`, { silent: !DEBUG_MODE }); // Create .zip archive from .sql exports
            if (fs.existsSync(archiveToCreate)) {
                console.log(`📎 (+) ${exportedFiles.length} table(s) added to archive.`);
                console.log(`✅ Snapshot '${archiveToCreate}' is successfully created.`);
            } else throw `Unable to generate a zip snapshot of the database!\n&amp;gt; ${resultOfArchive.stderr}`;
        } catch (err) {
            console.log('❌ Snapshot of the database has failed!', DEBUG_MODE ? '\n' + err : '');
        } finally { // Finally, delete the temporarily .sql files
            exportedFiles?.map(exportedFile =&amp;gt; 
                (fs.existsSync(exportedFile)) &amp;amp;&amp;amp; shell.exec(`rm -rf ${exportedFile}`, { silent: !DEBUG_MODE }));
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;L'export de notre base de données est dans cet exemple programmé chaque semaine.&lt;/p&gt;

&lt;p&gt;A dates échéantes, le backup sera automatiquement effectué :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    💾 Trying to export the database for the 29 Apr 2023..
    📥 SQL Table '2023-04-29_first_table.sql' is successfully retrieved.
    📥 SQL Table '2023-04-29_second_table.sql' is successfully retrieved.
    📥 SQL Table '2023-04-29_third_table.sql' is successfully retrieved.
    📦 Trying to generate an archive (.zip) from retrieved data..
    📎 (+) 3 table(s) added to archive.
    📎 (+) 'uploads' directory added to archive. // can be added in the zip script execution
    ✅ Snapshot 'backups/2023-04-29_snapshot.zip' is successfully created.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dans l'exemple ci-dessus, l'archive &lt;code&gt;2023-04-29_snapshot.zip&lt;/code&gt; a été créée dans le répertoire &lt;code&gt;backups&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Elle inclut &lt;strong&gt;les trois différentes tables&lt;/strong&gt; incluses dans notre &lt;strong&gt;base de données&lt;/strong&gt; ainsi que le répertoire &lt;code&gt;uploads&lt;/code&gt; du projet.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ Ce sytème de backup &lt;strong&gt;ne permet pas la création de plusieurs snapshots dans la même journée&lt;/strong&gt;, si l'intervale de la tâche CRON était par exemple définit à 6 heures : une archive serait créée la première fois puis mise à jour pour chaque prochaine occurence du même jour. Inclure le &lt;code&gt;timestamp&lt;/code&gt; du snapshot deviendrait alors nécessaire.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🆘 Erreurs éventuelles
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️  La constante &lt;code&gt;DEBUG_MODE&lt;/code&gt; passée à &lt;code&gt;true&lt;/code&gt; permet l'affichage des logs liées aux différentes opérations exécutées au cours de la sauvegarde de la base de données.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Une erreur liée à une insuffisance de permissions est fréquemment rencontrée sur les systèmes Linux lors de l'utilisation de Docker :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;gt; docker container ps -a

    &amp;gt; `Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: 
    Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json?all=1": dial 
    unix /var/run/docker.sock: connect: permission denied`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pour changer rapidement les permissions de l'utilisateur en cours d'utilisation, utilisez : &lt;code&gt;sudo chown $USER /var/run/docker.sock&lt;/code&gt;. L'exécution de Docker devient désormais possible sans recourir à une élévation de privilèges _(fix temporaire) _&lt;/p&gt;

&lt;p&gt;Pour que les permissions de l'utilisateur persistent après le redémarrage de votre machine, il sera nécessaire d'ajouter l'utilisateur en cours d'utilisation au groupe docker en procédant de la sorte : &lt;/p&gt;

&lt;p&gt;Créez le groupe "docker" s'il n'existe pas : &lt;code&gt;sudo groupadd docker&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Ensuite, ajoutez l'utilisateur en cours d'utilisation au groupe fraîchement créé : &lt;code&gt;sudo usermod -aG docker $USER&lt;/code&gt;. Finalement, intégrez-le au groupe en utilisant: &lt;code&gt;newgrp docker&lt;/code&gt; &lt;em&gt;(fix permanent)&lt;/em&gt;  &lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>mysql</category>
      <category>database</category>
      <category>javascript</category>
    </item>
    <item>
      <title>⛓ Automatiser rapidement le deploy ou « pipeline CI/CD » avec Github Actions via protocole SSH</title>
      <dc:creator>Grégory CHEVALLIER</dc:creator>
      <pubDate>Tue, 17 Oct 2023 07:06:23 +0000</pubDate>
      <link>https://forem.com/messagegit/automatiser-rapidement-le-deploy-ou-pipeline-cicd-avec-github-actions-via-protocole-ssh-58e4</link>
      <guid>https://forem.com/messagegit/automatiser-rapidement-le-deploy-ou-pipeline-cicd-avec-github-actions-via-protocole-ssh-58e4</guid>
      <description>&lt;p&gt;L'implémentation d'un « &lt;strong&gt;pipeline CI/CD&lt;/strong&gt; » &lt;em&gt;(= Continous Integration / Continuous distribution)&lt;/em&gt; est une &lt;strong&gt;pratique DevOps&lt;/strong&gt; moderne. Elle permet d'automatiser le déploiement du code, créé par les équipes de développement, dans l'environnement de production, selon une série d'étapes.&lt;/p&gt;

&lt;p&gt;Elle comble généralement une série d'opérations qui échappe à la mission des développeu.rs.ses, investi(e)s notament dans le développement d'un logiciel applicatif.&lt;/p&gt;

&lt;p&gt;Tout au long du cycle de vie d'une application, le CI/CD :&lt;/p&gt;

&lt;p&gt;&lt;em&gt;1️⃣ Déploie intégralement et automatiquement l'application depuis un répertoire (partagé ou privé) à destination d'un environnement de production&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;2️⃣ Garantit l'intégrité du ou des services internes au(x) serveur(s) de production en procédant à une série de tests automatisés&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;3️⃣ Assure l'envoi de logs/rapports réguliers aux équipes de développement en ce qui concerne l'analyse du code, les tests ou les éventuels processus en cours d'exécution&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;4️⃣ Permet l'augmentation de la fréquence des mises à jour&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;5️⃣ Facilite, pour les équipes de développement : la mise à niveau de certains fichiers de configuration en production (variables d'environnement, etc..)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;6️⃣ Prémunit l'environnement de production contre les éventuelles erreurs humaines&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;[...] Comment implémenter un « &lt;strong&gt;pipeline CI/CD&lt;/strong&gt; » via protocole SSH depuis un répertoire avec Github Actions ?&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ Pré-requis
&lt;/h2&gt;

&lt;p&gt;Notre implémentation nécessite la disposition d'&lt;strong&gt;un serveur distant&lt;/strong&gt; &lt;em&gt;(environnement de production)&lt;/em&gt; incluant &lt;strong&gt;une application active&lt;/strong&gt; &lt;em&gt;(liée à un répertoire Github accessible)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;L'authentification de notre serveur auprès de &lt;strong&gt;Github&lt;/strong&gt; doit être configurée de sorte à ce qu'elle ne requiert &lt;strong&gt;aucun mot de passe&lt;/strong&gt;. Une authentification par paire de clés SSH &lt;strong&gt;sans phrase de sécurité&lt;/strong&gt; est nécessaire.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ Le protocole &lt;strong&gt;SSH&lt;/strong&gt; &lt;em&gt;(Port 22)&lt;/em&gt; de votre serveur distant doit être activé.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🔐 Création d'une paire de clés SSH côté serveur
&lt;/h2&gt;

&lt;p&gt;Le plugin dépendant de l'action que nous souhaitons utiliser pour la réalisation de notre CI/CD &lt;strong&gt;requiert un accès SSH&lt;/strong&gt;. Dès lorsque le plugin est authentifié par notre serveur via le &lt;strong&gt;protocole SSH&lt;/strong&gt;, il peut alors intéragir avec notre serveur et &lt;strong&gt;exécuter notre futur workflow&lt;/strong&gt; &lt;em&gt;(permettant le déploiement de notre application)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Commencons par &lt;strong&gt;créer une paire de clés SSH&lt;/strong&gt;, côté serveur, pour permettre à l'action que nous utilisons de se connecter à notre serveur distant &lt;strong&gt;via le protocole SSH&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Une fois la connexion SSH avec notre serveur établie&lt;/strong&gt;, localisons-nous dans le répertoire &lt;code&gt;.ssh&lt;/code&gt; en utilisant &lt;code&gt;cd ~/.ssh&lt;/code&gt; puis générons notre &lt;strong&gt;nouvelle paire de clés ed25519&lt;/strong&gt; grâce à la commande :&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ssh-keygen -t ed25519 -a 200 -C "Key Comment"&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ Le schéma de signature ssh-rsa utilisant l'algorithme de hachage SHA-1 a été dépréciée depuis OpenSSH 8.8 (2021). Nous utilisons alors une paire de clés ed25519.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;gt; ssh-keygen -t ed25519 -a 200 -C "Key Comment"

`   (...)

    Generating public/private ed25519 key pair.
    Enter file in which to save the key (/home/yourusername/.ssh/id_ed25519):
    Created directory '/home/yourusername/.ssh'.
    Enter passphrase (empty for no passphrase):
    Enter same passphrase again:
    Your identification has been saved in /home/yourusername/.ssh/id_ed25519.
    Your public key has been saved in /home/yourusername/.ssh/id_ed25519.pub.
    The key fingerprint is:
    SHA256:2m0FfefEAFeJ31mFw87DAfef3FA113ge Key Comment
    The key's randomart image is:
    +---[ED25519 256]----+
    |      .             |
    |                    |
    | .                  |
    |. . . .             |
    |. .=.o .S.          |
    | =o.o. ..   .       |
    |o +   .  .    o ..  |
    |.. .      oE   oo . |
    |o.        .o+o   o  |
    +------[SHA256]------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notre paire de clés est à présent générée.&lt;/p&gt;

&lt;p&gt;Deux fichiers intègrent dorénavant notre répertoire &lt;code&gt;.ssh&lt;/code&gt; : &lt;code&gt;id_ed25519&lt;/code&gt; et &lt;code&gt;id_ed25519.pub&lt;/code&gt;. Une clé privée et une clé publique.&lt;/p&gt;

&lt;p&gt;Imprimons dès lors le contenu de notre clé publique &lt;code&gt;id_ed25519.pub&lt;/code&gt; en utilisant &lt;code&gt;cat id_ed25519.pub&lt;/code&gt; puis complétons notre fichier &lt;code&gt;authorized_keys&lt;/code&gt; par cette nouvelle clé en l'ajoutant à la fin du fichier. (= Utilisez &lt;code&gt;sudo nano authorized_keys&lt;/code&gt;) &lt;/p&gt;

&lt;p&gt;&lt;code&gt;ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCb4oUsXZ51L9DmH3UqSnwOAUr9w6AOZa8bYH8qsJAhypAjH9YvQteVXXQEY0ybaVE1cmpIKEyC2jmC/jOJ4b7bivlo2hgnLOrj3FPkDWO2yNNio9RnPXREBx7 Your Comment&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Notre clé privée &lt;code&gt;~/.ssh/id_ed25519&lt;/code&gt; peut dorénavant permettre à notre action de s'authentifier via le protocole SSH. Copiez-là en utilisant &lt;code&gt;cat id_ed25519&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    -----BEGIN OPENSSH PRIVATE KEY-----
    mO2P1IFEA93md2eZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
    2109fTUxOQAAACDytzhEI+btA5eW/JPqdBVZEYB4cBVrWdStrZrZzwXqsAAAAKD43x6J+N8e
    iFEA9AAAtzc2gtZWQyFAEFAxAACDygzhEEAL1+13IB5eW/JPqdBVZEYB4cBVrWdSrZzmPfeFA
    AAAECxDmZbgzG/rChoHYC0zux9C05Rvm2U6qCYTHpD982lJPdDOEQj4EgHl5b8k+p0FVkR
    gHhwFWtZ1KGEAOFKAmtnPBeqwAAAAFm1lc3NhZ2UtYm94QG91FEAF9uZnIBAgMEBQYH
    -----END OPENSSH PRIVATE KEY-----
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🌐 Mise à niveau du répertoire Github
&lt;/h2&gt;

&lt;p&gt;Accédons à la page Github de notre &lt;strong&gt;project &amp;gt; Paramètres &amp;gt; Secrets et variables &amp;gt; Actions&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jyzodjm2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nu5ex7gwzye5tdawsgla.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jyzodjm2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nu5ex7gwzye5tdawsgla.png" alt="Image description" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Quatre (ou cinq*) nouvelles variables doivent être ajoutées :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;HOST&lt;/code&gt; &lt;em&gt;(adresse distante de votre serveur)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;USERNAME&lt;/code&gt; &lt;em&gt;(nom d'utilisateur principal)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;KEY&lt;/code&gt; &lt;em&gt;(clé privée id_ed25519)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PORT&lt;/code&gt; &lt;em&gt;(22)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;(&lt;em&gt;) Si vous avez configuré **une phrase de sécurité&lt;/em&gt;* lors de la génération de &lt;strong&gt;votre paire de clés SSH&lt;/strong&gt;, une cinquième variable &lt;code&gt;PASSPHRASE&lt;/code&gt; doit alors être créée et assignée.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Le contenu de notre fichier &lt;code&gt;~/.ssh/id_ed25519&lt;/code&gt; doit être intégralement copié/collé dans le champ de valeur de notre variable &lt;code&gt;KEY&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Une fois ces variables enregistrées, le projet &lt;strong&gt;Github&lt;/strong&gt; détient dès lors secrètement &lt;strong&gt;l'accès à notre serveur distant&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ Configuration du workflow Github Actions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Github&lt;/strong&gt; inclut une rubrique "&lt;strong&gt;Actions&lt;/strong&gt;" &lt;em&gt;(Github Actions)&lt;/em&gt; spécifique à chaque projet, elle est dédiée à l'implémentation d'un  « &lt;strong&gt;pipeline CI/CD&lt;/strong&gt; ».&lt;/p&gt;

&lt;p&gt;Cette plateforme va permettre une mise à l'&lt;strong&gt;écoute des évènements&lt;/strong&gt; &lt;em&gt;(= Push, Pull Request, Merge..)&lt;/em&gt; liés aux différentes branches de notre &lt;strong&gt;projet Github&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Lorsqu'un &lt;strong&gt;évènement&lt;/strong&gt; survient, un &lt;strong&gt;runner&lt;/strong&gt; &lt;em&gt;(= serveur)&lt;/em&gt; exécute une &lt;strong&gt;action&lt;/strong&gt; &lt;em&gt;(code d'exécution)&lt;/em&gt; en tenant compte de la &lt;strong&gt;configuration d'un workflow&lt;/strong&gt; &lt;em&gt;(étapes du processus)&lt;/em&gt; et permet notament : le déploiement de notre code dans un environnement de production.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ Un &lt;strong&gt;marketplace&lt;/strong&gt;, rendu disponible par &lt;strong&gt;Github Actions&lt;/strong&gt;, propose l'utilisation d'actions créées par des dévellopeu.rs.ses de la communauté &lt;em&gt;(Ex: Deploy with Node.js, Deploy Node.js with Azure App, etc..)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Procédons à présent à la configuration de notre workflow.&lt;/p&gt;

&lt;p&gt;Ouvrons la page de notre &lt;strong&gt;projet Github&lt;/strong&gt; dans notre navigateur et rendons-nous dans l'onglet "&lt;strong&gt;Actions&lt;/strong&gt;".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TfGXmgfZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qnadye7f29u44qx8la8i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TfGXmgfZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qnadye7f29u44qx8la8i.png" alt="Image description" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Le bouton "&lt;strong&gt;set up a workflow yourself&lt;/strong&gt;" permet d'initialiser le fichier &lt;code&gt;.github/workflows/main.yml&lt;/code&gt; à la racine de notre projet Github et autorise ainsi la configuration d'un workflow personnalisé.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ Ce fichier est capable d'accéder aux &lt;strong&gt;variables secrètes&lt;/strong&gt; enregistrées (accessibles depuis Github.com &amp;gt; Paramètres &amp;gt; Secrets et variables &amp;gt; Actions)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Inaugurons la création de notre fichier en ajoutant les lignes suivantes :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    name: CI/CD Deploy (using SSH)

    on:
      push:
        branches:
          - master      
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le processus "&lt;strong&gt;CI/CD Deploy (using SSH)&lt;/strong&gt;" est alors créé.&lt;/p&gt;

&lt;p&gt;Il a pour rôle d'écouter les éventuels « &lt;strong&gt;Push&lt;/strong&gt; » depuis la branche « &lt;strong&gt;master&lt;/strong&gt; ».&lt;/p&gt;

&lt;p&gt;Il nous faut à présent &lt;strong&gt;configurer une série d'étapes et d'actions à effectuer&lt;/strong&gt; pour permettre à &lt;strong&gt;Github Actions&lt;/strong&gt; de savoir quoi faire lorsqu'un(e) développeu.r.se « &lt;strong&gt;Push&lt;/strong&gt; » du code sur la branche « &lt;strong&gt;master&lt;/strong&gt; »&lt;/p&gt;

&lt;p&gt;Pour cela, nous utiliserons l'action &lt;strong&gt;Github&lt;/strong&gt; suivante : &lt;a href="https://github.com/appleboy/ssh-action"&gt;appleboy/ssh-action@v0.1.8&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cette action va permettre d'&lt;strong&gt;exécuter les différentes étapes&lt;/strong&gt; de notre &lt;strong&gt;workflow de déploiement&lt;/strong&gt; via l'utilisation du protocole SSH de notre serveur distant.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    name: CI/CD Deploy (using SSH)

    on:
      push:
        branches:
          - master

    jobs:
          build:
            name: Build
            runs-on: ubuntu-latest
            steps:
                - name: Deploy to production server
                  uses: appleboy/ssh-action@v0.1.8
                  with:
                    host: ${{ secrets.HOST }}
                    username: ${{ secrets.USERNAME }}
                    key: ${{ secrets.KEY }}
                    port: ${{ secrets.PORT }}
                    script: printf "Hello World!"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Une nouvelle étape "&lt;strong&gt;Deploy to production server&lt;/strong&gt;" est ajoutée, elle requiert l'utilisation de l'action  &lt;code&gt;appleboy/ssh-action@v0.1.8&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Les variables secrètes &lt;code&gt;host&lt;/code&gt;, &lt;code&gt;username&lt;/code&gt;, &lt;code&gt;key&lt;/code&gt; et &lt;code&gt;port&lt;/code&gt; de notre répertoire sont alors récupérées et transmises à l'action pour &lt;strong&gt;permettre l'ouverture d'une session SSH&lt;/strong&gt; depuis une authentification par paire de clés SSH.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ Si vous avez configuré une &lt;strong&gt;phrase de sécurité&lt;/strong&gt; lors de la création de votre paire de clés SSH, &lt;strong&gt;ajoutez la ligne&lt;/strong&gt; : &lt;code&gt;passphrase: ${{ secrets.PASSPHRASE }}&lt;/code&gt; en dessous de la ligne &lt;code&gt;key: ${{ secrets.KEY }}&lt;/code&gt;. Elle permettra la &lt;strong&gt;prise en charge de votre phrase de sécurité&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;La propriété &lt;code&gt;script&lt;/code&gt; permet enfin d'ordonner une série d'opérations internes au serveur.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ Des logs instantanées sont accessibles pour chaque déploiement et &lt;strong&gt;permettent le suivi du processus&lt;/strong&gt; au fil de son exécution.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Il ne nous reste plus qu'à « commit » notre nouveau fichier &lt;code&gt;.github/workflows/main.yml&lt;/code&gt; pour assister au premier déploiement automatisé 🔥&lt;/p&gt;

&lt;p&gt;Une fois les modifications effectuées, &lt;strong&gt;Github Actions&lt;/strong&gt; détecte un nouveau « &lt;strong&gt;Push&lt;/strong&gt; » sur la branche « &lt;strong&gt;master&lt;/strong&gt; ».&lt;/p&gt;

&lt;p&gt;Une connexion SSH est dès lors initiée par &lt;strong&gt;ssh-actions&lt;/strong&gt; depuis les accès secrets fournis. La commande &lt;code&gt;printf "Hello World!"&lt;/code&gt; est alors exécutée au sein de notre serveur.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5_EwDi6s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oow8re08i167u6tl814o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5_EwDi6s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oow8re08i167u6tl814o.png" alt="Image description" width="800" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Si l'authentification est approuvée par notre serveur&lt;/strong&gt;, l'action retourne la synthèse des commandes à exécuter. Les réponses capturées, pour chaque commande, lors de l'exécution de l'étape "&lt;strong&gt;Deploy to production server&lt;/strong&gt;", sont alors retransmises en temps réel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;======CMD======
printf "Hello World!"

======END======
Hello World!

==============================================
✅ Successfully executed commands to all host.
==============================================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;❌ A contrario, le build sera un échec et cessera.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ Configuration du pipeline CI/CD
&lt;/h2&gt;

&lt;p&gt;Github est dorénavant capable de &lt;strong&gt;détecter l'occurence d'un évènement et d'établir une connexion SSH&lt;/strong&gt; avec notre serveur pour y &lt;strong&gt;exécuter une commande&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Une fois la connexion établie, il ne nous reste plus qu'à configurer la série d'opérations à exécuter via &lt;code&gt;bash&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Dans le cas d'une simple application &lt;strong&gt;React&lt;/strong&gt;, les tâches à entreprendre seraient les suivantes :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Se localiser dans le répertoire de notre application (nous utiliserons le répertoire &lt;code&gt;~/app-to-deploy&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Effectuer un pull du répertoire et ainsi bénéficier d'une version de l'application up-to-date.&lt;/li&gt;
&lt;li&gt;Installer les dépendances du projet&lt;/li&gt;
&lt;li&gt;Build l'application&lt;/li&gt;
&lt;li&gt;Relancer l'application (via l'utilisation d'un process manager comme &lt;strong&gt;PM2&lt;/strong&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Modifions dès lors notre fichier &lt;code&gt;.github/workflows/main.yml&lt;/code&gt; pour y inclure le pipeline de notre &lt;strong&gt;CI/CD&lt;/strong&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    name: CI/CD Deploy (using SSH)

    on:
      push:
        branches:
          - master

    jobs:
          build:
            name: Build
            runs-on: ubuntu-latest
            steps:
                - name: Deploy to production server
                  uses: appleboy/ssh-action@v0.1.8
                  with:
                    host: ${{ secrets.HOST }}
                    username: ${{ secrets.USERNAME }}
                    key: ${{ secrets.KEY }}
                    port: ${{ secrets.PORT }}
                    script: |
                      cd "app-to-deploy"
                      printf "[1/5] 📥 Pull of repository.."
                      git pull
                      printf "[2/5] 📦 Installation of packages.."
                      npm i
                      printf "[3/5] 🖨 Build of application.."
                      npm run build
                      printf "[4/5] 🔄 Restart the application.."
                      pm2 restart 0
                      printf "[5/5] ✅ Deploy is finished!"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notre CI/CD est à présent fonctionnel 🎉&lt;/p&gt;

&lt;p&gt;Les différentes opérations de notre script sont exécutées unes à unes. Elles seront ensuite retransmises via les logs du déploiement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;======CMD======
cd "app-to-deploy"
printf "[1/5] 📥 Pull of repository.."
git pull
printf "[2/5] 📦 Installation of packages.."
npm i
printf "[3/5] 🖨 Build of application.."
npm run build
printf "[4/5] 🔄 Restart the application.."
pm2 restart 0
printf "[5/5] ✅ Deploy is finished!"

======END======
out: [1/5] 📥 Pull of repository..
out: Updating ecb327c..934abbc
out: Fast-forward
out:  src/App.js | 2 +-
out:  1 file changed, 1 insertion(+), 1 deletion(-)
out: [2/5] 📦 Installation of packages..
out: up to date, audited 1611 packages in 10s
out: 235 packages are looking for funding
out:   run `npm fund` for details
out: 6 high severity vulnerabilities
out: To address all issues (including breaking changes), run:
out:   npm audit fix --force
out: Run `npm audit` for details.
out: [3/5] 🖨 Build of application..
out: &amp;gt; app-to-deploy@0.1.0 build
out: &amp;gt; react-scripts build
out: Creating an optimized production build...
out: Compiled successfully.
out: File sizes after gzip:
out:   46.63 kB  build/static/js/main.80a26327.js
out:   1.78 kB   build/static/js/787.d08cf7c1.chunk.js
out:   541 B     build/static/css/main.073c9b0a.css
out: The project was built assuming it is hosted at /.
out: You can control this with the homepage field in your package.json.
out: The build folder is ready to be deployed.
out: You may serve it with a static server:
out:   npm install -g serve
out:   serve -s build
out: Find out more about deployment here:
out:   https://cra.link/deployment
out: [4/5] 🔄 Restart the application..Use --update-env to update environment variables
out: [PM2] Applying action restartProcessId on app [0](ids: [ '0' ])
out: [PM2] [my-app](0) ✓
out: ┌────┬───────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
out: │ id │ name      │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
out: ├────┼───────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
out: │ 0  │ my-app    │ default     │ N/A     │ fork    │ 34966    │ 0s     │ 15   │ online    │ 0%       │ 3.4mb    │ ***   │ disabled │
out: └────┴───────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
out: [5/5] ✅ Deploy is finished!
==============================================
✅ Successfully executed commands to all host.
==============================================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Il ne tient plus qu'à vous de personnaliser le script d'exécution avant de procéder au déploiement de votre ou vos application(s)/serveur(s). &lt;/p&gt;

</description>
      <category>github</category>
      <category>automation</category>
      <category>ssh</category>
      <category>cicd</category>
    </item>
    <item>
      <title>🚀 Deploy d'une App Node.js avec NGINX et PM2 + Config Linux Server + Certificat SSL</title>
      <dc:creator>Grégory CHEVALLIER</dc:creator>
      <pubDate>Tue, 17 Oct 2023 07:05:51 +0000</pubDate>
      <link>https://forem.com/messagegit/deploy-dune-app-nodejs-avec-nginx-et-pm2-config-linux-server-certificat-ssl-3hil</link>
      <guid>https://forem.com/messagegit/deploy-dune-app-nodejs-avec-nginx-et-pm2-config-linux-server-certificat-ssl-3hil</guid>
      <description>&lt;p&gt;« Mise en prod' » , « Deploy », « Push ».. Tant d'expressions qui permettent de signifier l'aboutissement d'un long chemin de développement : ça y est, votre code est fin prêt à être déployé dans un environnement de production et votre application/serveur exposé(e) au monde entier.&lt;/p&gt;

&lt;p&gt;Si vous n'avez jamais mis d'applications en ligne auparavant, les appréhensions peuvent être nombreuses.&lt;/p&gt;

&lt;p&gt;Comment configurer un &lt;strong&gt;serveur Linux&lt;/strong&gt; pour exécuter des applications &lt;strong&gt;Node.js&lt;/strong&gt; ? Pourquoi utiliser un gestionnaire de processus ? Comment mettre en place un &lt;strong&gt;certificat SSL&lt;/strong&gt; et profiter du protocole &lt;strong&gt;HTTPS&lt;/strong&gt; ? Comment configurer la &lt;strong&gt;zone DNS&lt;/strong&gt; d'un nom de domaine ?&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ Pré-configuration de votre serveur
&lt;/h2&gt;

&lt;p&gt;L'utilisation d'un &lt;strong&gt;VPS&lt;/strong&gt; &lt;em&gt;(= Virtual Private Server)&lt;/em&gt; est l'une des solutions les plus adaptées lorsque l'on doit déployer par exemple une ou plusieurs applications &lt;strong&gt;Node.js&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Vulgairement parlant, un &lt;strong&gt;VPS&lt;/strong&gt; : c'est une machine comme la votre exposée via une adresse IPv4 dédiée, qui a pour vocation d'&lt;strong&gt;être allumée 24h/24 - 7j/7&lt;/strong&gt;. Elle a généralement la particularité d'intégrer un système d'exploitation minimaliste, léger et nécessitant peu de ressources : &lt;strong&gt;Linux&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;📶 La connexion à ce serveur distant est effectuée via le protocole SSH (Port 22), c'est par l'intermédiaire d'une invite de commande que notre serveur va être configurable.&lt;/p&gt;

&lt;p&gt;Lorsque vous commandez un &lt;strong&gt;VPS&lt;/strong&gt;, les informations suivantes vous sont communiquées :  &lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Adresse IP (Host)&lt;/li&gt;
&lt;li&gt;Username&lt;/li&gt;
&lt;li&gt;Password&lt;/li&gt;
&lt;li&gt;Port (22)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ces informations permettent d'établir une connexion SSH. La plupart des systèmes d'exploitations intègrent aujourd'hui un client SSH mais certains nécessitent l'utilisation d'un client SSH tierce, c'est le cas avec Windows par exemple : l'installation du logiciel &lt;a href="https://www.putty.org/" rel="noopener noreferrer"&gt;PuTTY&lt;/a&gt; est alors requise.&lt;/p&gt;

&lt;p&gt;Si votre VPS est livré avec un système d'authentification : hôte / nom d'utilisateur / mot de passe, il n'a cependant pas pour vocation à le rester. Ce type d'authentification est exposé à de nombreux types d'attaques, le &lt;strong&gt;bruteforce&lt;/strong&gt; notament.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pour pallier à cette problématique&lt;/strong&gt; : l'autorisation d'un client par le serveur depuis une pair de clé SSH préalablement générée&lt;/p&gt;

&lt;p&gt;La première mise à niveau du serveur implique alors &lt;strong&gt;les actions suivantes&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Autoriser une/des clé(s) pour s'authentifier au protocole SSH&lt;/li&gt;
&lt;li&gt;Supprimer l'authentification par mot de passe du protocole SSH&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔑 Générer une nouvelle pair de clé SSH, localement
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🌕 Si vous utilisez Linux
&lt;/h3&gt;

&lt;p&gt;Accédez localement à votre dossier &lt;code&gt;~/.ssh&lt;/code&gt; puis générez une nouvelle clé si vous n'en n'avez pas déjà une :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    cd ~/.ssh
    ssh-keygen -t rsa -b 4096 -C "your_label"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vous n'êtes pas obligé(e) d'inclure une passphrase à votre nouvelle clé, appuyez sur ENTRER sans même saisir de phrase de sécurité, celle-ci est souvent négligée par les développeurs, notament lorsqu'une clé est sollicitée en guise d'authentification via un processus automatisé.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Generating public/private rsa key pair.
    Enter file in which to save the key (/home/yourusername/.ssh/id_rsa):
    Created directory '/home/yourusername/.ssh'.
    Enter passphrase (empty for no passphrase):
    Enter same passphrase again:
    Your identification has been saved in /home/yourusername/.ssh/id_rsa.
    Your public key has been saved in /home/yourusername/.ssh/id_rsa.pub.
    The key fingerprint is:
    SHA256:2m0FfefEAFeJ31mFw87DAfef3FA113ge your_label
    The key's randomart image is:
    +---[RSA 4096]----+
    |      .          |
    |                 |
    | .               |
    |. . . .          |
    |. .=.o .S.       |
    | =o.o. ..   .    |
    |o +   .  . o ..  |
    |.. .      oEoo . |
    |o.        .o+oo  |
    +-----------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Votre pair de clé est à présent créée.&lt;/p&gt;

&lt;p&gt;Récupérez votre clé publique grâce à la commande suivante : &lt;code&gt;cat ~/.ssh/id_rsa.pub&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;C'est cette clé que nous autoriserons côté serveur, nous pourrons ainsi nous authentifier à notre serveur distant sans même avoir à justifier d'un mot de passe*.&lt;br&gt;
&lt;em&gt;(*) sous réserve qu'aucune phrase de sécurité n'ai été définie&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  🌑 Si vous utilisez Windows
&lt;/h3&gt;

&lt;p&gt;Le logiciel &lt;a href="https://www.puttygen.com/download-putty" rel="noopener noreferrer"&gt;PuTTYGen&lt;/a&gt; va vous permettre de générer une nouvelle pair de clé.&lt;/p&gt;

&lt;p&gt;Une fois le logiciel ouvert, cliquez sur "Generate", agitez la souris aux 4 coins de la fenêtre (stimulation cryptographique) et patientez un instant durant la génération de votre clé.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8htf4qwi19atcmbex5l6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8htf4qwi19atcmbex5l6.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cette clé publique générée a vocation d'être autorisée auprès de notre serveur. Notre clé privée permettra de nous authentifier avec &lt;a href="https://www.putty.org/" rel="noopener noreferrer"&gt;PuTTY&lt;/a&gt; via le protocole SSH sans utiliser de mot de passe*.&lt;br&gt;
&lt;em&gt;(*) sous réserve qu'aucune phrase de sécurité n'ai été définie&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Cliquez ensuite sur le bouton "&lt;strong&gt;Save private key&lt;/strong&gt;" pour enregistrer votre clé au format &lt;code&gt;.ppk&lt;/code&gt; (&lt;em&gt;Putty Private Key&lt;/em&gt; - format dédié à PuTTY). &lt;/p&gt;


&lt;h2&gt;
  
  
  🚦 Connexion au serveur et autorisation de notre nouvelle clé
&lt;/h2&gt;

&lt;p&gt;Il est temps de &lt;strong&gt;se connecter pour la première fois à notre serveur&lt;/strong&gt;, nous utiliserons logiquement l'adresse distante de notre machine, l'identifiant et le mot de passe fournis par notre hébergeur.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    ssh ubuntu@102.49.82.203
    ...

    login as: ubuntu
    ubuntu@102.49.82.203's password: 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Une fois connecté(e),&lt;/p&gt;

&lt;p&gt;Ouvrez le fichier &lt;code&gt;authorized_keys&lt;/code&gt; via la commande : &lt;code&gt;sudo nano ~/.ssh/authorized_keys&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;[...] Et ajoutez votre clé publique, précédemment générée, en la collant.&lt;/p&gt;

&lt;p&gt;ℹ️ Voici un exemple de ce à quoi pourrait ressembler votre fichier &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; si vous aviez autorisé plusieurs clés; toutes les clés autorisées peuvent permettre de s'authentifier au serveur sans mot de passe :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQCo9+BpMRYQ/dL3DS2CyJxRF+j6ctbT3/Qp84+KeFhnii7NT7fELilKUSnxS30WAvQCCo2yU1orfgqr41mM70MB your_label
    ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCb4oUsXZ51L9DmH3UqSnwOAUr9w6AOZa8bYH8qsJAhypAjH9YvQteVXXQEY0ybaVE1cmpIKEyC2jmC/jOJ4b7bivlo2hgnLOrj3FPkDWO2yNNio9RnPXREBx7 special_key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sauvegardez à présent votre fichier, félicitations 🎉 &lt;/p&gt;

&lt;p&gt;Vous pouvez dorénavant vous authentifier auprès de votre serveur en utilisant la clé précédemment générée.&lt;/p&gt;

&lt;p&gt;Voyons à présent comment s'authentifier à notre serveur.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔒 S'authentifier à notre serveur via SSH grâce à notre nouvelle clé
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🌕 Si vous utilisez Linux
&lt;/h3&gt;

&lt;p&gt;Connectez-vous à votre serveur via la commande traditionnelle : &lt;code&gt;ssh user@host&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;✅ Aucun mot de passe n'est à présent requis, l'authentification est directement effectuée.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Si une phrase de sécurité (= passphrase) a été configurée lors de la génération de votre clé, cette dernière sera requise lors de la tentative de connexion :&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    login as: ubuntu
    Authenticating with public key "id_rsa"
    Passphrase for key "id_rsa":
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🌑 Si vous utilisez Windows
&lt;/h3&gt;

&lt;p&gt;Ouvrez le logiciel &lt;a href="https://www.putty.org/" rel="noopener noreferrer"&gt;PuTTY&lt;/a&gt; &lt;em&gt;(à ne pas confondre avec PuTTYGen permettant de générer des clés)&lt;/em&gt; et renseignez l'&lt;strong&gt;adresse de votre serveur&lt;/strong&gt; distant, définissez le &lt;strong&gt;port&lt;/strong&gt; SSH (22).&lt;/p&gt;

&lt;p&gt;Rendez-vous dans la catégorie : &lt;strong&gt;Connection &amp;gt; SSH &amp;gt; Auth &amp;gt; Credentials&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Localisez votre clé privée (au format &lt;code&gt;.ppk&lt;/code&gt;) préalablement enregistrée lors de l'étape de la génération de clé.&lt;/p&gt;

&lt;p&gt;Retournez dans la catégorie "&lt;strong&gt;Sessions&lt;/strong&gt;" puis cliquez sur "&lt;strong&gt;Save&lt;/strong&gt;". Vos informations de connexion (y compris votre clé privée) seront alors gardés en mémoire. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyuhy3pfgipgal54aprpd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyuhy3pfgipgal54aprpd.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Séléctionnez à présent la session que vous venez de sauvegarder et lancez-là.&lt;/p&gt;

&lt;p&gt;Vous êtes à présent authentifié(e) à votre serveur*.&lt;br&gt;
&lt;em&gt;(*) sous réserve qu'aucune phrase de sécurité n'ai été définie&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  ⛔ Désactiver l'authentification par identifiant / mot de passe
&lt;/h2&gt;

&lt;p&gt;Notre authentification via clé SSH est à présent fonctionnelle ✅&lt;/p&gt;

&lt;p&gt;L'authentification par l'intermédiaire d'une combinaison identifiant / mot de passe peut alors être supprimée, elle est la potentielle porte d'entrée à d'éventuelles intrustions malveillantes.&lt;/p&gt;

&lt;p&gt;Commencons par modifier le fichier &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; en utilisant la commande :&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo nano /etc/ssh/sshd_config&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Notre mission : repérer les variables suivantes :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PasswordAuthentication&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ChallengeResponseAuthentication&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UsePAM&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;[...] et leur attribuer la valeur suivante : &lt;code&gt;no&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ℹ️ Si l'une de ces variables est absente, rajoutez-là manuellement et définissez-là à &lt;code&gt;no&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;La variable &lt;code&gt;PubkeyAuthentication&lt;/code&gt; quant à elle doit être définie à &lt;code&gt;yes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Dès &lt;strong&gt;lorsque les modifications sont terminées&lt;/strong&gt;, utilisez généralement &lt;strong&gt;CTRL + X&lt;/strong&gt; pour fermer et saisissez "&lt;strong&gt;Y&lt;/strong&gt;" pour confirmer les changements.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ℹ️  Vérifiez parallèlement qu'aucun fichier &lt;code&gt;.conf&lt;/code&gt; ne soit inclus dans les répertoires suivants : &lt;code&gt;/etc/ssh/ssh_config.d&lt;/code&gt; et &lt;code&gt;/etc/ssh/sshd_config.d&lt;/code&gt;. Certains hébergeurs définissent parfois des variables dans des fichiers de configuration à ces endroits pour, entre autre, autoriser la connexion via identifiant / mot de passe.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Le serveur SSH aura besoin d'être redémarré pour que les changements soient pris en compte, saisissez la commande suivante : &lt;code&gt;sudo systemctl restart sshd ssh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notre serveur n'accepte dorénavant plus les connexions avec mot de passe&lt;/strong&gt;. Seules les clés autorisées via notre registre &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; pourront s'authentifier au protocole SSH de notre serveur.&lt;/p&gt;


&lt;h2&gt;
  
  
  📥 Télécharger et installer la dernière version de Node.js
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Node.js&lt;/strong&gt; est souvent téléchargé depuis les dépôts officiels / PPA &lt;em&gt;(= Personal Package Archives)&lt;/em&gt; Ubuntu.&lt;/p&gt;

&lt;p&gt;Nous délaisserons cette méthode en téléchargeant directement &lt;strong&gt;Node.js&lt;/strong&gt; depuis la &lt;a href="https://nodejs.org/en/download" rel="noopener noreferrer"&gt;page de téléchargement du site officiel&lt;/a&gt;, selon la version souhaitée. Ainsi, nous sommes certains de pouvoir profiter d'une version up-to-date.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;ℹ️ Dans un environnement de production, il est recommandé d'utiliser l'une des dernières versions de Node.js&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Dans ce tutoriel, nous utiliserons la &lt;strong&gt;version 18.15.0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;1️⃣ Localisez-vous au chemin d'accès par défaut &lt;code&gt;~&lt;/code&gt; de votre serveur grâce à &lt;code&gt;cd ~/&lt;/code&gt; puis téléchargez la version Node.js cible depuis le site officiel grâce à la commande :&lt;/p&gt;

&lt;p&gt;&lt;code&gt;wget https://nodejs.org/dist/v18.15.0/node-v18.15.0-linux-x64.tar.xz&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    --2023-04-01 12:49:28
    --`https://nodejs.org/dist/v18.15.0/node-v18.15.0-linux-x64.tar.xz`
    Resolving nodejs.org (nodejs.org)... 2606:4700:10::6814:162e, 2606:4700:10::6814:172e, 104.20.23.46, ...
    Connecting to nodejs.org (nodejs.org)|2606:4700:10::6814:162e|:443... connected.
    HTTP request sent, awaiting response... 200 OK
    Length: 23637576 (23M) [application/x-xz]
    Saving to: ‘node-v18.15.0-linux-x64.tar.xz’

    `node-v18.15.0-linux` 100%[===================&amp;gt;]  22.54M  28.9MB/s    in 0.8s    

    2023-04-01 12:49:28 (28.9 MB/s) - ‘node-v18.15.0-linux-x64.tar.xz’ saved [23637576/23637576]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2️⃣ Décompressez le fichier &lt;code&gt;.tar.xz&lt;/code&gt; téléchargé en utilisant :&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo tar -xvf node-v18.15.0-linux-x64.tar.xz&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;3️⃣ Puis transférez les fichiers source de &lt;strong&gt;Node.js&lt;/strong&gt; dans le répertoire &lt;code&gt;/usr/&lt;/code&gt; en utilisant la commande :&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo cp -r node-v18.15.0-linux-x64/{bin,include,lib,share} /usr/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;4️⃣ Supprimons sans tarder notre fichier &lt;code&gt;node-v18.15.0-linux-x64.tar.xz&lt;/code&gt; ainsi que le répertoire décompressé &lt;code&gt;node-v18.15.0-linux-x64&lt;/code&gt;, ils ne nous seront plus d'aucune utilité.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo rm -rf node-v18.15.0-linux-x64 node-v18.15.0-linux-x64.tar.xz&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;5️⃣ Ouvrez à présent le script de votre shell via &lt;code&gt;sudo nano ~/.bashrc&lt;/code&gt; et exposez &lt;strong&gt;Node.js&lt;/strong&gt; en ajoutant la ligne suivante, à la fin :&lt;/p&gt;

&lt;p&gt;&lt;code&gt;export PATH=/usr/node-v18.15.0-linux-x64/bin:$PATH&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;6️⃣ Rechargez à présent le script shell &lt;code&gt;~/.bashrc&lt;/code&gt; en utilisant &lt;code&gt;source ~/.bashrc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Node.js&lt;/strong&gt; est normalement installé ✅&lt;/p&gt;

&lt;p&gt;Vérifiez que l'installation soit conforme en saisissant &lt;code&gt;node -v&lt;/code&gt; dans votre terminal.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔐 Authentification auprès de Github
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Git&lt;/strong&gt; est et restera sans doûte le meilleur ami de celles &amp;amp; ceux qui pratiquent la programmation. Il est un outil indispensable du quotidien et sert notament d'intermédiaire entre votre environnement de développement et votre environnement de production.&lt;/p&gt;

&lt;p&gt;La configuration de l'&lt;strong&gt;authentification avec Github&lt;/strong&gt; est particulièrement importante.&lt;/p&gt;

&lt;p&gt;L'authentification via pair de clé SSH peut par exemple outrepasser la nécessité de justifier d'un mot de passe. Elle peut alors permettre la manipulation automatisée d'un 🌐 &lt;strong&gt;repository&lt;/strong&gt; grâce à un processus de &lt;strong&gt;CI/CD&lt;/strong&gt; &lt;em&gt;(= Continuous Integration/Continuous Delivery)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;🔒 Intéressons-nous à l'&lt;strong&gt;authentification via pair de clé SSH&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Lors de la configuration de notre serveur Linux : nous avions généré, localement, une pair de clé SSH et nous avions autorisé notre clé publique depuis le serveur.&lt;/p&gt;

&lt;p&gt;Cette fois, c'est &lt;strong&gt;sur le serveur&lt;/strong&gt; que la pair de clé SSH va devoir être générée. La clé publique sera transmise à &lt;strong&gt;Github&lt;/strong&gt; via les paramètres de notre compte et les échanges avec &lt;strong&gt;Github&lt;/strong&gt; seront dès lors authentifiés.&lt;/p&gt;

&lt;p&gt;Localisez-vous dans le dossier &lt;code&gt;~/.ssh&lt;/code&gt; de votre machine distante puis générez votre nouvelle pair de clé SSH :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    cd ~/.ssh
    ssh-keygen -t rsa -b 4096 -C "your_email@domain.com"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suivez la procédure de génération, ajoutez ou non une phrase de sécurité puis patientez un instant.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Generating public/private rsa key pair.
    Enter file in which to save the key (/home/ubuntu/.ssh/id_rsa): 
    Enter passphrase (empty for no passphrase): 
    Enter same passphrase again: 
    Your identification has been saved in /home/ubuntu/.ssh/id_rsa
    Your public key has been saved in /home/ubuntu/.ssh/id_rsa.pub
    The key fingerprint is:
    SHA256:c7bEyoefeR09TZ9vnEmddK2LwD4XRo4ZCLfmE3kKKTWoM your_email@domain.com
    The key's randomart image is:
    +---[RSA 4096]----+
    |    o . oo  .    |
    |  .  o     . o   |
    | o .  o   . + .  |
    |o      . + + .  .|
    |E s.    S X .   +|
    |.o     . B T..+.+|
    |.       = *.+o.* |
    |  ..  .  *.  .o o|
    |        ..o.     |
    +----[SHA256]-----+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Il ne vous reste plus qu'à copier votre clé publique en utilisant : &lt;code&gt;cat ~/.ssh/id_rsa.pub&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Rendez-vous sur &lt;a href="https://github.com/settings/keys" rel="noopener noreferrer"&gt;https://github.com/settings/keys&lt;/a&gt;, cliquez sur "&lt;strong&gt;Nouvelle clé SSH&lt;/strong&gt;" puis ajoutez votre clé publique en l'identifiant par un nom de référence.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9g1l4tl3c0lgzxyfoibg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9g1l4tl3c0lgzxyfoibg.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vérifiez, dans le terminal de votre serveur, que vous êtes bien authentifié en utilisant la commande : &lt;code&gt;ssh -T git@github.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Si vous êtes authentifié, vous reçevrez un message similaire à celui-ci 👇&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Hi {username}! You've successfully authenticated, but GitHub does not provide shell access.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;[...] Vous pouvez à présent cloner votre projet sur votre serveur.&lt;/p&gt;




&lt;h2&gt;
  
  
  🟰 Cloner une application depuis un repository Git
&lt;/h2&gt;

&lt;p&gt;A la racine &lt;code&gt;~&lt;/code&gt; de votre serveur distant, clônez votre application grâce à la commande :&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git clone git@github.com:{username}/{project}.git&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;ℹ️  Prenez soin d'actualiser les variables &lt;code&gt;{ }&lt;/code&gt; par vos données (= nom d'utilisateur Git et nom de projet)&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Une fois votre application clônée et accessible en local, localisez-vous à la racine de votre projet à l'aide du nom de votre projet précédé par la commande &lt;code&gt;cd&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;ℹ️ Supposons que notre application soit une simple application React&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Créez votre fichier d'environnement de production (dotenv) à la racine (= &lt;code&gt;.env&lt;/code&gt; ou &lt;code&gt;.env.local&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Installez vos dépendances et lancez le build de votre projet à l'aide de &lt;code&gt;npm i&lt;/code&gt; et &lt;code&gt;npm run build&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;⚙️ Vérifiez que votre application puisse se lancer via &lt;code&gt;npm run start&lt;/code&gt; et tentez de joindre votre application via l'adresse de votre machine depuis le port utilisé par &lt;strong&gt;Node.js&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Mettez fin au processus &lt;strong&gt;(CTRL + C)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;⚠️ Soyez sûre que votre application soit fonctionnelle en installant les éventuels services dépendants &lt;em&gt;(= (No)SQL database, Redis, Docker, API..)&lt;/em&gt; &lt;/p&gt;




&lt;h2&gt;
  
  
  📂 Utiliser le protocole SFTP dédié aux transferts de fichiers
&lt;/h2&gt;

&lt;p&gt;Que vous utilisiez &lt;strong&gt;Mac OS, Windows&lt;/strong&gt; ou &lt;strong&gt;Linux&lt;/strong&gt;, le logiciel de transfert de fichiers &lt;a href="https://filezilla-project.org/download.php" rel="noopener noreferrer"&gt;FileZilla&lt;/a&gt; vous permettra via une interface graphique de gérer les ressources de votre serveur (répertoires/fichiers).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;ℹ️ Le transfert de fichiers se révèle particulièrement indispensable pour les sites web basiques incluant un système d'upload de fichiers. Ces derniers n'utilisant pas de solutions liées au cloud comme S3 AWS / Azure / Google Storage etc.. Les fichiers sont alors stockés sur le serveur&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Le transfert de fichiers requiert l'utilisation d'un protocole &lt;strong&gt;FTP&lt;/strong&gt; &lt;em&gt;(= File Transfert Protocol)&lt;/em&gt;. Un serveur &lt;strong&gt;FTP&lt;/strong&gt; &lt;em&gt;(Port 21)&lt;/em&gt; doit alors être installé sur le serveur.&lt;/p&gt;

&lt;p&gt;Pour pallier à cette épreuve, une alternative s'offre à nous : le &lt;strong&gt;SFTP&lt;/strong&gt; &lt;em&gt;(= SSH File Transfert Protocol)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Le &lt;strong&gt;SFTP&lt;/strong&gt; accompli les missions du &lt;strong&gt;FTP&lt;/strong&gt; mais en utilisant un autre protocole : le &lt;strong&gt;SSH&lt;/strong&gt; &lt;em&gt;(Port 22)&lt;/em&gt;, le même que nous utilisons quotidiennement pour intéragir via shell avec notre serveur distant. Nous bénéficions dès lors des atouts qu'offre le &lt;strong&gt;SSH&lt;/strong&gt; : à savoir une connexion sécurisée via pair de clé &lt;strong&gt;SSH&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;En principe et selon notre mode opératoire, &lt;strong&gt;FileZilla&lt;/strong&gt; charge notre clé &lt;strong&gt;SSH&lt;/strong&gt; &lt;em&gt;(préalablement générée)&lt;/em&gt; et la transmet à notre serveur qui nous autorise à établir la connexion.&lt;/p&gt;

&lt;p&gt;Renseignez dès-lors l'adresse IPv4 de votre serveur, votre identifiant, le port : 22 puis procédez à la connexion.&lt;/p&gt;

&lt;p&gt;✅ Votre clé SSH est détectée, transmise et approuvée par le serveur, vous êtes authentifié(e).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La clé n'est pas détectée ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Si la clé n'est pas détectée, vous utilisez probablement &lt;strong&gt;Windows&lt;/strong&gt;. Dans &lt;strong&gt;FileZilla&lt;/strong&gt;, localisez la clé &lt;code&gt;.ppk&lt;/code&gt; que vous aviez générée avec &lt;strong&gt;PuTTYGen&lt;/strong&gt; depuis :&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Filezilla &amp;gt; Fichier &amp;gt; Gestionnaire de sites &amp;gt; Type d'authentification &amp;gt; Fichier de clef&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fexh5snfpve0itavkgl08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fexh5snfpve0itavkgl08.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[...] Et chargez votre clé, la connexion devrait à présent aboutir.&lt;/p&gt;




&lt;h2&gt;
  
  
  📥 Installer et configurer le gestionnaire de processus PM2
&lt;/h2&gt;

&lt;p&gt;Le gestionnaire de processus va permettre d'&lt;strong&gt;initialiser chacune de vos applications Node.js&lt;/strong&gt; en les répertoriant dans des &lt;strong&gt;conteneurs centralisés&lt;/strong&gt;. Il va occuper un rôle majeur en ce qui concerne la maintenance et le processus de déploiement de vos applications.&lt;/p&gt;

&lt;p&gt;Chacun de ces conteneurs peut alors être (re)démarré/arrêté et exploité individuellement.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;🔁 En cas de redémarrage impromptu du serveur, le gestionnaire de processus permet de redémarrage de l'intégralité de vos applications afin d'éviter une indisponibilité prolongée de vos services. Il donne également accès aux logs et à un système de monitoring efficace concernant vos processus.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nous utiliserons &lt;a href="https://github.com/Unitech/pm2" rel="noopener noreferrer"&gt;PM2&lt;/a&gt;, une librairie disponible sur &lt;a href="https://yarnpkg.com/package/pm2" rel="noopener noreferrer"&gt;yarn&lt;/a&gt; et &lt;a href="https://www.npmjs.com/package/pm2" rel="noopener noreferrer"&gt;npm&lt;/a&gt;, simple, complète et efficace d'utilisation.&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 Et si nous n'utilisions pas de gestionnaire de processus ?
&lt;/h3&gt;

&lt;p&gt;Si par exemple vous démarriez votre application via une session SSH sans utiliser de gestionnaire de processus (via &lt;code&gt;npm run start&lt;/code&gt; par exemple) : un processus serait alors lancé.&lt;/p&gt;

&lt;p&gt;Dans l'hypothèse où vous abandonneriez ce(tte) terminal/session, vous perderiez la main sur le processus mais celui-ci continuerai alors d'être exécuté en arrière plan &lt;em&gt;(vous pourriez évidement le reprendre dans une session ultérieure)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Si hasardeusement, vous oublieriez qu'une application Node.js était déjà en cours d'exécution, vous la lanceriez une nouvelle fois et il n'existerait alors pas moins de deux occurences d'une même application Node.js &lt;em&gt;(x2 processus en arrière plan)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Autrement, si vous aviez par exemple plusieurs applications Node.js sur le même serveur, vous ne pourriez même pas les distinguer dans la liste des processus en cours d'exécution.&lt;/p&gt;

&lt;p&gt;C'est à ces problématiques que le gestionnaire de processus PM2 apporte des solutions 🔥&lt;/p&gt;

&lt;h3&gt;
  
  
  💹 Comment ça fonctionne ?
&lt;/h3&gt;

&lt;p&gt;Installons maintenant notre gestionnaire de processus, sur notre serveur distant, via la commande :&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo npm i pm2 -g&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Vérifiez que &lt;strong&gt;PM2&lt;/strong&gt; soit installé sur votre machine en exécutant : &lt;code&gt;pm2 -v&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ Le drapeau &lt;code&gt;-g&lt;/code&gt; permet d'installer la librairie globalement et d'enregistrer la nouvelle variable d'environnement &lt;code&gt;pm2&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Le mode de fonctionnement de &lt;strong&gt;PM2&lt;/strong&gt; permet d'initialiser un nouveau processus depuis la racine d'une application &lt;strong&gt;Node.js&lt;/strong&gt;, au même niveau que notre traditionnel &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Il intègre d'ailleurs la reconnaissance d'un fichier de configuration intitulé &lt;code&gt;ecosystem.config.js&lt;/code&gt;. Dès lorsque ce fichier est présent à la racine de votre projet, il va permettre au gestionnaire de processus de démarrer votre application avec les différentes propriétés spécifiées.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;ℹ️ La documentation des différentes propriétés disponibles pour la configuration du fichier &lt;code&gt;ecosystem.config.js&lt;/code&gt; est &lt;a href="https://pm2.keymetrics.io/docs/usage/application-declaration/" rel="noopener noreferrer"&gt;disponible en ligne&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Avant d'initialiser votre application dans &lt;strong&gt;PM2&lt;/strong&gt;, prenez-soin de créer un fichier &lt;code&gt;ecosystem.config.js&lt;/code&gt; à la racine de votre projet puis d'y apporter la configuration requise minimale :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    module.exports = {
        apps : [{
            name: 'my-process-name',
            script : "npm run start",
        }]
    };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exécutez à présent la commande &lt;code&gt;pm2 start&lt;/code&gt; à la racine de votre projet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    [PM2][WARN] Applications my-process-name not running, starting...
    [PM2] App [my-process-name] launched (1 instances)
    ┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
    │ id │ name               │ mode     │  ↺   │ status    │ cpu      │ memory   │
    ├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
    │ 0  │ my-process-name    │ fork     │ 0    │ online    │ 0%       │ 10.2mb   │
    └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le nouveau processus est créé et démarré 🎉 &lt;/p&gt;

&lt;p&gt;D'autres applications peuvent ainsi être ajoutées à la liste des processus PM2.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;ℹ️ La commande &lt;code&gt;pm2 startup ubuntu&lt;/code&gt; permet de garantir le redémarrage de vos processus après un éventuel crash/reboot intempestif de votre machine.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;L'exécution, l'arrêt et le redémarrage de votre application devra à présent passer par PM2.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    // Lister les processus référencés
    pm2 list

    // Démarrer une application
    pm2 start my-process-name

    // Arrêter une application
    pm2 stop my-process-name

    // Redémarrer une application
    pm2 restart my-process-name

    // Accéder aux logs d'une application
    pm2 logs my-process-name

    // Accéder au panel de monitoring instantanée
    pm2 monit

    // Sauvegarder le référencement des applications chargées
    pm2 save

    // Restaurer des applications depuis une sauvegarde
    pm2 resurrect

    // Démarrer les processus lors du démarrage du système
    pm2 startup ubuntu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  📥 Installer et configurer le serveur web NGINX
&lt;/h2&gt;

&lt;p&gt;L'utilisation d'un serveur web en guise de surcouche à &lt;strong&gt;Node.js&lt;/strong&gt; est tout à fait recommandé. Le serveur web &lt;strong&gt;Nginx&lt;/strong&gt; offre par exemple de nombreux avantages :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Concentrer l'audience de vos applications sur un même port &lt;em&gt;(80 par défaut)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Intégrer d'indénombrables fichiers de configuration clairs et documentés. &lt;em&gt;(&lt;a href="http://nginx.org/en/docs/" rel="noopener noreferrer"&gt;Voir la documentation officielle&lt;/a&gt;)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Optimiser le traitement des requêtes&lt;/li&gt;
&lt;li&gt;Gérer la mise en cache et la compression des données&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;[...] Et surtout : &lt;strong&gt;permettre l'utilisation d'un Reverse Proxy&lt;/strong&gt; 🔥&lt;/p&gt;

&lt;p&gt;Le &lt;strong&gt;proxy inverse&lt;/strong&gt; permet de :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rediriger des requêtes entrantes vers différents serveurs internes utilisant différents protocoles.&lt;/li&gt;
&lt;li&gt;Agir en tant que répartiteur de charge entre plusieurs serveurs, si configuré comme tel.&lt;/li&gt;
&lt;li&gt;Décharger les serveurs d'une charge d'objets/pages statiques via la gestion d'un cache web local.&lt;/li&gt;
&lt;li&gt;Agir en tant que point de terminaison &lt;strong&gt;TLS&lt;/strong&gt; et permettre le chiffrement via &lt;strong&gt;certificat SSL&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Réduire la charge de traitement cryptographique (= &lt;strong&gt;HTTPS&lt;/strong&gt;) si installé sur une machine différente des serveurs applicatifs.&lt;/li&gt;
&lt;li&gt;[...]&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;ℹ️ L'utilisation du reverse proxy a pour vocation de n'exposer que le port 80. Ainsi, les différents ports qu'occupent nos applications internes pourront être bloqués par notre pare-feu.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Procédons à l'installation de &lt;strong&gt;Nginx&lt;/strong&gt; sur notre machine distante :&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo apt install nginx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Reading package lists... Done
    Building dependency tree... Done
    Reading state information... Done
    The following packages were automatically installed and are no longer required:
      javascript-common libc-ares2 libjs-highlight.js libnode72 nodejs-doc
    Use 'sudo apt autoremove' to remove them.
    The following additional packages will be installed:
      libnginx-mod-http-geoip2 libnginx-mod-http-image-filter
      libnginx-mod-http-xslt-filter libnginx-mod-mail libnginx-mod-stream
      libnginx-mod-stream-geoip2 nginx-common nginx-core
    Suggested packages:
      fcgiwrap nginx-doc ssl-cert
    The following NEW packages will be installed:
      libnginx-mod-http-geoip2 libnginx-mod-http-image-filter
      libnginx-mod-http-xslt-filter libnginx-mod-mail libnginx-mod-stream
      libnginx-mod-stream-geoip2 nginx nginx-common nginx-core
    0 upgraded, 9 newly installed, 0 to remove and 0 not upgraded.
    Need to get 696 kB of archives.
    After this operation, 2395 kB of additional disk space will be used.
    Do you want to continue? [Y/n]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Saisissez &lt;strong&gt;&lt;em&gt;"Y"&lt;/em&gt;&lt;/strong&gt; et appuyez sur &lt;strong&gt;&lt;em&gt;ENTRER&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;✅ Nginx est à présent installé sur votre machine.&lt;/p&gt;

&lt;p&gt;Remplacez intégralement le contenu du fichier &lt;code&gt;/etc/nginx/sites-available/default&lt;/code&gt; :&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo nano /etc/nginx/sites-available/default&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;[...] par le code suivant :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    server {
        listen 80 default_server;
        listen [::]:80 default_server;

        server_name yourdomain.com www.yourdomain.com;
        location / {
            proxy_pass 'http://127.0.0.1:{port}';
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-Host $host;
            proxy_set_header X-Forwarded-Port $server_port;
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;ℹ️ Soyez certain(e) d'avoir remplacer la variable &lt;code&gt;{port}&lt;/code&gt; par le port utilisé par votre application Node.js et &lt;code&gt;(www.)yourdomain.com&lt;/code&gt; par votre nom de domaine. Nous pourrons ensuite installer un certificat SSL.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Lorsque vous installez &lt;strong&gt;Nginx&lt;/strong&gt;, la taille maximale d'une requête prise en charge par Nginx est restreinte à 1MB par défaut. Au delà, le requête sera rejetée ou ignorée. &lt;/p&gt;

&lt;p&gt;Pour modifier ce paramètre, ouvrez le fichier de configuration &lt;code&gt;/etc/nginx/nginx.conf&lt;/code&gt; et ajoutez la ligne suivante dans la section &lt;code&gt;http&lt;/code&gt; si elle n'existe pas déjà :&lt;/p&gt;

&lt;p&gt;&lt;code&gt;client_max_body_size 50M;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;⚠️ Vérifiez que la configuration globale soit conforme en exécutant &lt;code&gt;sudo nginx -t&lt;/code&gt; puis redémarrez &lt;strong&gt;Nginx&lt;/strong&gt; pour intégrer les modifications.&lt;/p&gt;

&lt;p&gt;Utilisez &lt;code&gt;sudo service nginx restart&lt;/code&gt; ou &lt;code&gt;sudo systemctl restart nginx.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Lancez à présent votre application via &lt;strong&gt;PM2&lt;/strong&gt; et essayez de joindre votre application via &lt;strong&gt;HTTP&lt;/strong&gt; &lt;em&gt;(port 80 de votre machine)&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ L'installation d'un &lt;strong&gt;certificat SSL&lt;/strong&gt; via la librairie &lt;strong&gt;Certbot&lt;/strong&gt; viendra compléter le fichier de configuration &lt;code&gt;/etc/nginx/sites-available/default&lt;/code&gt; pour permettre l'utilisation et la redirection &lt;strong&gt;HTTPS&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Si vous vouliez par exemple créer des sous-domaines et utiliser plusieurs méthodes de Proxy Reverse, vous pourriez configurer votre fichier &lt;code&gt;/etc/nginx/sites-available/default&lt;/code&gt; selon le principe suivant :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    # Domain | www.domain.com

    server {
        server_name domain.com www.domain.com;
        location / {
            proxy_pass 'http://hostname:{port}';
        }
    }

    # Sub-domain n°1 | subdomain1.domain.com

    server {
        server_name subdomain1.domain.com www.subdomain1.domain.com;
        location / {
            proxy_pass 'http://hostname:{port}';
        }
    }

    # Sub-domain n°2 | subdomain2.domain.com

    server {
        server_name subdomain2.domain.com www.subdomain2.domain.com;
        location / {
            proxy_pass 'http://hostname:{port}';
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pour garantir de bonnes conditions de maintenance et renforcer l'organisation structurelle de notre Reverse Proxy, il serait préférable de supprimer notre fichier &lt;code&gt;/etc/nginx/sites-available/default&lt;/code&gt; et de créer un fichier pour chaque (sous-)domaine.&lt;/p&gt;

&lt;p&gt;L'arborescence des (sous-)domaines serait alors plus simple à distinguer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    # Domain | www.domain.com

    server {
        server_name domain.com www.domain.com;
        location / {
            proxy_pass 'http://hostname:{port}';
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    # Sub-domain n°1 | subdomain1.domain.com

    server {
        server_name subdomain1.domain.com www.subdomain1.domain.com;
        location / {
            proxy_pass 'http://hostname:{port}';
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Une fois les fichiers de configurations référents à vos différents (sous-)domaines créés dans le répertoire &lt;code&gt;/etc/nginx/sites-available&lt;/code&gt;, créez un lien symbolique pour chacun de vos fichiers depuis ce répertoire, au répertoire &lt;code&gt;/etc/nginx/sites-enabled/&lt;/code&gt; en utilisant :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    cd ~
    sudo ln -s /etc/nginx/sites-available/domain.com /etc/nginx/sites-enabled/domain.com
    sudo ln -s /etc/nginx/sites-available/subdomain1.domain.com /etc/nginx/sites-enabled/subdomain1.domain.com
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ Vérifiez que la configuration globale soit conforme en exécutant &lt;code&gt;sudo nginx -t&lt;/code&gt; puis redémarrez &lt;strong&gt;Nginx&lt;/strong&gt; pour intégrer les modifications.&lt;/p&gt;

&lt;p&gt;Utilisez &lt;code&gt;sudo service nginx restart&lt;/code&gt; ou &lt;code&gt;sudo systemctl restart nginx&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛡️ Activation du pare-feu serveur
&lt;/h2&gt;

&lt;p&gt;Dès lorsque nos applications &lt;strong&gt;Node.js&lt;/strong&gt; sont accessible via le port 80 qu'utilise notre serveur web Nginx, il est judicieux d'activer le pare-feu et ainsi n'autoriser que les protocoles ayant pour vocation d'être exploités sur notre machine tels que :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSH / SFTP (22)&lt;/li&gt;
&lt;li&gt;HTTP(S) (80 / 443)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Exécutez les commandes suivantes :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    sudo ufw enable
    sudo ufw allow ssh
    sudo ufw allow http
    sudo ufw allow https
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Utilisez la commande &lt;code&gt;sudo ufw status&lt;/code&gt; pour obtenir une synthèse des réglementations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Status: active

    To                         Action      From
    --                         ------      ----
    22/tcp                     ALLOW       Anywhere                  
    80                         ALLOW       Anywhere                  
    443                        ALLOW       Anywhere                  
    22/tcp (v6)                ALLOW       Anywhere (v6)             
    80 (v6)                    ALLOW       Anywhere (v6)             
    443 (v6)                   ALLOW       Anywhere (v6)      
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🌐 Configuration de la zone DNS d'un nom de domaine
&lt;/h2&gt;

&lt;p&gt;C'est une étape primordiale lorsqu'une activitée prend naissance sur le web : la configuration de la zone DNS du nom de domaine.&lt;/p&gt;

&lt;p&gt;La &lt;strong&gt;zone DNS&lt;/strong&gt; d'un nom de domaine est un espace administratif gérée par une organisation ou un administrateur spécifique qui permet le contrôle sur les différentes entrées du nom de domaine.&lt;/p&gt;

&lt;p&gt;Lorsque vous reçevez votre nouveau nom de domaine, la zone DNS de votre nom de domaine est souvent pré-configuré par votre hébergeur de telle sorte à ce que vous puissiez dors et déjà par exemple accéder au protocole &lt;strong&gt;FTP&lt;/strong&gt; du serveur cible via la pré-configuration d'un enregistrement &lt;code&gt;CNAME&lt;/code&gt; &lt;em&gt;(= ftp.domain.com)&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ Retrouvez la &lt;a href="https://fr.wikipedia.org/wiki/Liste_des_enregistrements_DNS" rel="noopener noreferrer"&gt;liste des différents enregistrements DNS&lt;/a&gt; existants&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Exemple d'espace d'administration de la &lt;strong&gt;zone DNS d'un nom de domaine&lt;/strong&gt; &lt;em&gt;(OVH)&lt;/em&gt; 👇&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz6ostq2h00168v1jyx6v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz6ostq2h00168v1jyx6v.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ci-dessous, un aperçu textuel de la configuration de la zone DNS de notre nom de domaine :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    $TTL 3600
    @   IN SOA dns200.anycast.me. tech.ovh.net. (2023020018 86400 3600 3600000 60)
                              IN NS     ns200.anycast.me.
                              IN NS     dns200.anycast.me.
                              IN MX     1 mx4.mail.ovh.net.
                              IN MX     10 mx3.mail.ovh.net.
                              IN A      164.132.50.110
                              IN TXT    "1|www.domain.com"
    _autodiscover._tcp        IN SRV    0 0 443 mailconfig.ovh.net.
    _imaps._tcp               IN SRV    0 0 993 ssl0.ovh.net.
    _submission._tcp          IN SRV    0 0 465 ssl0.ovh.net.
    subdomain1                IN CNAME  domain.com.
    autoconfig                IN CNAME  mailconfig.ovh.net.
    autodiscover              IN CNAME  mailconfig.ovh.net.
    ftp                       IN CNAME  domain.com.
    subdomain2                IN CNAME  domain.com.
    www                       IN A      164.132.50.110
    www                       IN TXT    "3|welcome"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La première action à entreprendre : &lt;strong&gt;modifier l'adresse IPv4&lt;/strong&gt; cible de &lt;strong&gt;l'enregistrement de Type A&lt;/strong&gt; en renseignant &lt;strong&gt;l'adresse IPv4 de votre serveur distant&lt;/strong&gt;, pour les deux entrées existantes : &lt;code&gt;domain.com&lt;/code&gt; &lt;em&gt;(Type A)&lt;/em&gt; et &lt;code&gt;www.domain.com&lt;/code&gt; &lt;em&gt;(Type A)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Le nom de domaine sera dès lors reconnu par n'importe quel naviguateur et dirigera le client à votre serveur distant.&lt;/p&gt;

&lt;p&gt;Pour créer un sous-domaine, créez une nouvelle entrée de Type &lt;code&gt;CNAME&lt;/code&gt;, ciblez votre nom de domaine et spécifiez l'intitulé du sous-domaine à créer, puis validez.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;ℹ️ Un délai de propagation implique parfois d'attendre jusqu'à 24 heures avant de garantir l'accessibilité à votre nouveau sous-domaine&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Lorsque vous requêterez votre sous-domaine, &lt;strong&gt;Nginx&lt;/strong&gt; sera capable d'interprêter l'origine de la requête et vous redirigera auprès de l'application cible &lt;em&gt;(selon votre configuration via proxy)&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔒 Mise en place d'un certificat SSL avec Certbot
&lt;/h2&gt;

&lt;p&gt;Maintenant que notre nom de domaine nous redirige vers notre serveur distant, que le serveur web Nginx interprête l'origine de notre requête et nous redirige vers l'application cible ;&lt;/p&gt;

&lt;p&gt;Etablissons une connexion sécurisée entre client/serveur grâce au protocole &lt;strong&gt;HTTPS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;L'utilisation du protocole &lt;strong&gt;HTTPS&lt;/strong&gt; nécessite l'utilisation d'un certificat numérique permettant aux navigateurs de vérifier l'identité des serveurs web. Des tiers de confiance, soit des &lt;strong&gt;autorités de certification (AC)&lt;/strong&gt; sont chargées de déléguer les certificats aux serveurs web.&lt;/p&gt;

&lt;p&gt;Une librairie nommée « &lt;strong&gt;Certbot&lt;/strong&gt; » récupère des certificats depuis l'autorité de certification &lt;strong&gt;Let's Encrypt&lt;/strong&gt;, &lt;em&gt;(autorité de certification lancée à l'initiative de &lt;strong&gt;Mozilla&lt;/strong&gt; notament)&lt;/em&gt;. Nous l'utiliserons pour implémenter rapidement notre &lt;strong&gt;certificat SSL&lt;/strong&gt; avec &lt;strong&gt;Nginx&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Installons &lt;strong&gt;Certbot&lt;/strong&gt;, depuis notre machine distante :&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo apt install certbot python3-certbot-nginx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Procédons à l'&lt;strong&gt;installation de notre certificat SSL&lt;/strong&gt; en utilisant &lt;strong&gt;Certbot&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo certbot --nginx -d domain.com -d www.domain.com&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Saving debug log to '/var/log/letsencrypt/letsencrypt.log'
    Enter email address (used for urgent renewal and security notices)
     (Enter 'c' to cancel): your_email@domain.com

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Please read the Terms of Service at
    https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf. You must
    agree in order to register with the ACME server. Do you agree?
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    (Y)es/(N)o: y

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Would you be willing, once your first certificate is successfully issued, to
    share your email address with the Electronic Frontier Foundation, a founding
    partner of the Lets Encrypt project and the non-profit organization that
    develops Certbot? We would like to send you email about our work encrypting the web,
    EFF news, campaigns, and ways to support digital freedom.
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    (Y)es/(N)o: y
    Account registered.
    Requesting a certificate for 'domain.com' and 'www.domain.com'

    Successfully received certificate.
    Certificate is saved at: '/etc/letsencrypt/live/domain.com/fullchain.pem'
    Key is saved at:         '/etc/letsencrypt/live/domain.com/privkey.pem'
    This certificate expires on 2023-07-01.
    These files will be updated when the certificate renews.
    Certbot has set up a scheduled task to automatically renew this certificate in the background.

    Deploying certificate
    Successfully deployed certificate for 'domain.com to' '/etc/nginx/sites-enabled/default'
    Successfully deployed certificate for 'www.domain.com' to '/etc/nginx/sites-enabled/default'
    Congratulations! You have successfully enabled HTTPS on 'https://domain.com' and 'https://www.domain.com'

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    If you like Certbot, please consider supporting our work by:
     * Donating to ISRG / Lets Encrypt:   https://letsencrypt.org/donate
     * Donating to EFF:                    https://eff.org/donate-le
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Une règle a été créée auprès de votre serveur web pour rediriger le client du protocole &lt;strong&gt;HTTP&lt;/strong&gt; au &lt;strong&gt;HTTPS&lt;/strong&gt;. Une tâche pour renouveller le certificat a quant à elle été initialisée et sera renouvellée chaque 90 jours.&lt;/p&gt;

&lt;p&gt;Vérifiez que celle-ci soit créée via &lt;code&gt;sudo systemctl status certbot.timer&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    ● certbot.timer - Run certbot twice daily
    Loaded: loaded ('/lib/systemd/system/certbot.timer'; enabled; vendor preset:&amp;gt;
    Active: active (waiting) since Sun 2023-04-02 10:50:45 UTC; 4min 58s ago
    Trigger: Sun 2023-04-02 15:19:19 UTC; 4h 23min left
    Triggers: ● certbot.service

    Apr 02 10:50:45 vps systemd[1]: Started Run certbot twice daily.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Un email de &lt;strong&gt;Let's Encrypt&lt;/strong&gt; devrait vous parvenir avant l'expiration de votre certificat.&lt;/p&gt;

&lt;p&gt;Utilisez &lt;code&gt;sudo certbot renew --dry-run&lt;/code&gt; à l'avenir pour renouveller votre certificat.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ Vous pourrez remarquer que le fichier de configuration &lt;strong&gt;Nginx&lt;/strong&gt; lié au nom de domaine que vous avez certifié à été modifié par &lt;strong&gt;Certbot&lt;/strong&gt;. Notre fichier &lt;code&gt;/etc/nginx/sites-available/default&lt;/code&gt; ou &lt;code&gt;/etc/nginx/sites-available/domain.com&lt;/code&gt; a hérité de nouvelles propriétés pour permettre l'utilisation du protocole &lt;strong&gt;HTTPS&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Félicitations 🎉&lt;/p&gt;

&lt;p&gt;Vous pouvez dorénavant profiter de votre application depuis le protocole &lt;strong&gt;HTTPS&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>node</category>
      <category>deploy</category>
      <category>devops</category>
    </item>
    <item>
      <title>👤 Implémenter un système d'authentification via LinkedIn avec React &amp; NestJS</title>
      <dc:creator>Grégory CHEVALLIER</dc:creator>
      <pubDate>Tue, 17 Oct 2023 07:05:38 +0000</pubDate>
      <link>https://forem.com/messagegit/implementer-un-systeme-dauthentification-via-linkedin-avec-react-nestjs-47ok</link>
      <guid>https://forem.com/messagegit/implementer-un-systeme-dauthentification-via-linkedin-avec-react-nestjs-47ok</guid>
      <description>&lt;p&gt;L'implémentation d'un système d'authentification via &lt;strong&gt;LinkedIn&lt;/strong&gt; peut présenter de nombreux avantages pour votre plateforme.&lt;/p&gt;

&lt;p&gt;Dans un premier temps, il permet de vous prémunir contre les bots &amp;amp; les logiciels automatisés 🤖. En effet, la création d'un compte &lt;strong&gt;LinkedIn&lt;/strong&gt; requiert le renseignement d'un numéro de téléphone. On peut dès lors considérer qu'un utilisateur authentifié via &lt;strong&gt;LinkedIn&lt;/strong&gt; auprès de votre plateforme est une personne réelle 🙍‍♂️.&lt;/p&gt;

&lt;p&gt;Mais l'authentification via &lt;strong&gt;LinkedIn&lt;/strong&gt; permet surtout l'obtention de données ayant trait à l'identité réelle de l'utilisateur se connectant à votre plateforme. Il s'agit donc d'une solution pertinente pour obtenir le profil professionnel d'un internaute.&lt;/p&gt;

&lt;p&gt;Je vous propose, dans cet article, de découvrir comment implémenter une solution d'authentification via &lt;strong&gt;LinkedIn&lt;/strong&gt; en créant : une application &lt;a href="https://fr.legacy.reactjs.org/" rel="noopener noreferrer"&gt;React&lt;/a&gt; ainsi qu'une API &lt;a href="https://nestjs.com/" rel="noopener noreferrer"&gt;NestJS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source Github 👇&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/MessageGit/oauth-sign-in-with-linkedin" rel="noopener noreferrer"&gt;https://github.com/MessageGit/oauth-sign-in-with-linkedin&lt;/a&gt;. &lt;/p&gt;


&lt;h2&gt;
  
  
  ⚠️ Pré-requis
&lt;/h2&gt;

&lt;p&gt;Pour implémenter un système d'authentification via &lt;strong&gt;LinkedIn&lt;/strong&gt;, vous devez tout d'abord :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Être détenteur d'un compte utilisateur LinkedIn valide&lt;/strong&gt;
&lt;em&gt;(Si vous n'avez pas de compte LinkedIn, créez-en un en &lt;a href="https://www.linkedin.com/signup?_l=fr" rel="noopener noreferrer"&gt;cliquant ici&lt;/a&gt;).&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Être propriétaire d'une page professionnelle LinkedIn (à l'effigie de votre plateforme)&lt;/strong&gt;
&lt;em&gt;(Si vous n'avez pas de page professionnelle, créez-en une en &lt;a href="https://www.linkedin.com/company/setup/new/" rel="noopener noreferrer"&gt;cliquant ici&lt;/a&gt;)&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Créer une application LinkedIn (nécessite de satisfaire les deux pré-requis précédents)&lt;/strong&gt;.
&lt;em&gt;Pour cela, rendez-vous sur la &lt;a href="https://www.linkedin.com/developers/apps/new" rel="noopener noreferrer"&gt;page de création d'une nouvelle application&lt;/a&gt; puis complétez le formulaire en renseignant les informations suivantes : nom de l'application, URL de votre page professionnelle, upload d'un logo, acceptation des CGU&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbuv6u71jw5kbzz4pu6v0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbuv6u71jw5kbzz4pu6v0.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[...] Votre nouvelle application, une fois créée, devrait apparaître dans le tabeau de bord « développeurs » de votre compte LinkedIn, &lt;a href="https://www.linkedin.com/developers/apps" rel="noopener noreferrer"&gt;cliquez-ici&lt;/a&gt; pour y accéder.&lt;/p&gt;

&lt;p&gt;Il ne vous reste plus qu'à cliquer sur votre nouvelle application pour accéder à son panel de gestion 🎉&lt;br&gt;
&lt;em&gt;(Voir exemple ci-dessous)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft58u1kuambur3txjocyh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft58u1kuambur3txjocyh.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  ⚙️ Configuration préalable via LinkedIn Developers
&lt;/h2&gt;

&lt;p&gt;Le fonctionnement de la connexion OAuth 2 via &lt;strong&gt;LinkedIn&lt;/strong&gt; ne diffère pas grandement des autres services qui l'intègrent (comme par exemple la connexion via &lt;strong&gt;Google&lt;/strong&gt;/&lt;strong&gt;Facebook&lt;/strong&gt;). &lt;/p&gt;

&lt;p&gt;Un système de « scopes » est présent et permet à l'utilisateur de réguler l'accord des permissions léguées à l'application, point positif en faveur de l'aspect sécuritaire de la plateforme.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;ℹ️ Si la présence de « scopes » semble être standard, rappelons tout de même que certain(e)s réseaux sociaux/applications n'intègrent pas ce système précautionneux voué à limiter l'exploitation des ressources par une application tierce. C'est le cas de la messagerie sécurisée &lt;strong&gt;Telegram&lt;/strong&gt; notamment.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Par défaut, votre application (fraîchement créée dans le chapitre précédent) n'intègre pas la connexion via &lt;strong&gt;LinkedIn&lt;/strong&gt;, il faudra l'activer manuellement en vous rendant dans la rubrique « &lt;strong&gt;Products&lt;/strong&gt; » de votre application (toujours dans le panel de gestion de votre application &lt;strong&gt;LinkedIn developers&lt;/strong&gt;). &lt;/p&gt;

&lt;p&gt;Cliquez ensuite sur le bouton « &lt;strong&gt;Request access&lt;/strong&gt; » lié au produit nommé « &lt;strong&gt;Sign In with LinkedIn&lt;/strong&gt; ».&lt;/p&gt;

&lt;p&gt;Une fois le produit activé, deux nouveaux scopes feront leur apparition dans la section « &lt;strong&gt;&lt;em&gt;OAuth 2.0 scopes&lt;/em&gt;&lt;/strong&gt; » de la rubrique « &lt;strong&gt;&lt;em&gt;Auth&lt;/em&gt;&lt;/strong&gt; » : &lt;code&gt;r_emailaddress&lt;/code&gt; ainsi que &lt;code&gt;r_liteprofile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;C'est dans cette même rubrique que vont se manifester de nombreuses informations essentielles à l'implémentation de la solution d'authentification via LinkedIn. &lt;/p&gt;

&lt;p&gt;La section « &lt;strong&gt;&lt;em&gt;Application credentials&lt;/em&gt;&lt;/strong&gt; » va répertorier le &lt;strong&gt;Client ID&lt;/strong&gt; &amp;amp; le &lt;strong&gt;Client Secret&lt;/strong&gt;, deux informations primordiales permettant d'identifier votre application lorsque vos futurs utilisateurs accéderont au portail d'authentification via &lt;strong&gt;LinkedIn&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;La section « &lt;strong&gt;OAuth 2.0 settings&lt;/strong&gt; » quant à elle va permettre d'autoriser des URLs de redirection pour votre application. Une fois l'authentification de vos utilisateurs validée, &lt;strong&gt;LinkedIn&lt;/strong&gt; les redirigera vers l'URL demandée (sous réserve que cette dernière soit autorisée dans cette même section). &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr5mfw4a0otv76ah5rjf3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr5mfw4a0otv76ah5rjf3.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Une fois la configuration de l'application via le panel &lt;strong&gt;LinkedIn developers&lt;/strong&gt; terminée, il est temps de passer à l'implémentation technique du système d'authentification !&lt;/p&gt;


&lt;h2&gt;
  
  
  📸 Développement de la web application React
&lt;/h2&gt;

&lt;p&gt;Avant de débuter l'initialisation de notre application &lt;strong&gt;React&lt;/strong&gt;, il me semble judicieux de résumer les opérations à entreprendre via l'application front pour procéder à l'authentification d'un utilisateur &lt;strong&gt;LinkedIn&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  👤 Aspect procédural de l'authentification client
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1ère étape de l'authentification 👇&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Rediriger le client vers le portail d'authentification &lt;strong&gt;LinkedIn&lt;/strong&gt; en y apportant des informations telles que : l'URI de redirection souhaité (dans l'éventualité où l'authentification de notre client est un succès), les scopes requis par notre application, l'identifiant même de notre application, etc..&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;2ème étape de l'authentification 👇&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Partons du principe que notre client soit parvenu à se connecter à &lt;strong&gt;LinkedIn&lt;/strong&gt; : ce dernier est dès lors redirigé à l'URI que nous avons fournit à LinkedIn lorsque nous l'avons renvoyé vers le portail d'authentification. Sa redirection vers notre URI personnalisée est accompagnée d'un &lt;a href="https://fr.wikipedia.org/wiki/Cha%C3%AEne_de_requ%C3%AAte" rel="noopener noreferrer"&gt;query params&lt;/a&gt; « code », il s'agit là d'un « authorization code » que nous enverrons à notre API pour obtenir un droit d'exploitation des ressources de notre client (sous réserve que l'exploitation soit faite par notre API exclusivement).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;[...] Nous verrons dans le chapitre suivant comment échanger le code d'autorisation, résultant de la connexion de notre client à &lt;strong&gt;LinkedIn&lt;/strong&gt; puis comment obtenir l'autorisation d'exploitation des ressources &lt;strong&gt;LinkedIn&lt;/strong&gt; de notre client.&lt;/p&gt;
&lt;h3&gt;
  
  
  🌐 Initialisation de la web application React
&lt;/h3&gt;

&lt;p&gt;Maintenant que nous avons étudié l'aspect procédural de l'authentification côté client, tâchons de l'implémenter via une application &lt;strong&gt;React&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Commençons par initialiser une nouvelle application &lt;strong&gt;React&lt;/strong&gt; en saisissant la commande suivante dans un terminal : &lt;code&gt;npx create-react-app linkedin-oauth-app&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Nous idéaliserons une architecture/arborescence d'application très minimaliste et assez peu élaborée telle que la suivante :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;gt; linkedin-oauth-app
        &amp;gt; node_modules
        &amp;gt; public
        &amp;gt; src
            &amp;gt; api
            &amp;gt; assets
            App.tsx
            index.css
            index.js
        .env
        .env.example
        .gitignore
        package.json
        README.md
        tsconfig.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🗒️ Déclaration des variables d'environnement
&lt;/h3&gt;

&lt;p&gt;Déclarons sans plus attendre les variables d'environnement indispensables au fonctionnement de notre système d'authentification, et plus globalement, au fonctionnement de notre application :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    # MAIN
    REACT_APP_API_URL=

    # LINKEDIN
    REACT_APP_LINKEDIN_CLIENT_ID=
    REACT_APP_LINKEDIN_SCOPES=
    REACT_APP_LINKEDIN_REDIRECT_URI=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;[...] Notre fichier &lt;code&gt;.env&lt;/code&gt; &lt;em&gt;(registre des variables d'environnement)&lt;/em&gt; sera identique au fichier &lt;code&gt;.env.example&lt;/code&gt; &lt;em&gt;(ci-dessus)&lt;/em&gt;, à la seule différence que celui-ci intégrera des valeurs. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🤔 Pourquoi un fichier &lt;code&gt;.env&lt;/code&gt; et &lt;code&gt;.env.example&lt;/code&gt; ?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ Le principe de cette mesure sécuritaire est simple. Pour celles &amp;amp; ceux qui n'en ont pas connaissance : le fichier &lt;code&gt;.env&lt;/code&gt;, référencé dans notre fichier &lt;code&gt;.gitignore&lt;/code&gt;, peut inclure une infinité d'informations sensibles selon la complexité de l'application, il n'a donc pas vocation à intégrer notre répertoire Github.&lt;br&gt;
Notre fichier &lt;code&gt;.env.example&lt;/code&gt; est, quant à lui, le modèle des variables d'environnement. Il sera respectivement publié sur notre répertoire Github à titre d'exemple et n'intégrera donc aucune valeur.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Vous pouvez d'ores et déjà incrémenter la clé &lt;code&gt;REACT_APP_LINKEDIN_SCOPES&lt;/code&gt; par la valeur suivante : &lt;code&gt;r_liteprofile,r_emailaddress&lt;/code&gt;. Cette dernière correspond à la solution « &lt;strong&gt;Sign-In with LinkedIn&lt;/strong&gt; ».&lt;/p&gt;

&lt;p&gt;Vous retrouverez la valeur de la clé &lt;code&gt;REACT_APP_LINKEDIN_CLIENT_ID&lt;/code&gt; et &lt;code&gt;REACT_APP_LINKEDIN_REDIRECT_URI&lt;/code&gt; dans le panel &lt;a href="https://www.linkedin.com/developers/apps" rel="noopener noreferrer"&gt;LinkedIn Developers&lt;/a&gt; de votre application, rubrique « &lt;strong&gt;Auth&lt;/strong&gt; ».&lt;/p&gt;

&lt;p&gt;La clé &lt;code&gt;REACT_APP_API_URL&lt;/code&gt; correspondra quant à elle à l'URL de votre API, mais nous ne l'avons pas encore créée, nous y reviendrons donc ultérieurement 😜&lt;/p&gt;

&lt;p&gt;Dans notre fichier &lt;code&gt;App.tsx&lt;/code&gt;, nous importerons les variables d'environnement correspondantes à &lt;strong&gt;LinkedIn&lt;/strong&gt; sous forme d'object Javascript :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';

const App = () =&amp;gt; {
    /* Load LinkedIn credentials from dotenv */
    const LINKEDIN_ENV = {
        CLIENT_ID: process.env['REACT_APP_LINKEDIN_CLIENT_ID'],
        SCOPES: process.env['REACT_APP_LINKEDIN_SCOPES'],
        REDIRECT_URI: process.env['REACT_APP_LINKEDIN_REDIRECT_URI'],
    }

    // (...)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  📤 Implémentation technique et redirection client
&lt;/h3&gt;

&lt;p&gt;A titre d'exemple, un simple bouton « &lt;strong&gt;&lt;em&gt;Se connecter via LinkedIn&lt;/em&gt;&lt;/strong&gt; » permettant la redirection du client au portail d'authentification LinkedIn fera l'affaire. Inutile de configurer plusieurs pages ou d'aborder un développement plus complexe, l'enjeu de cet exemple étant de saisir le fonctionnement de l'authentification.&lt;/p&gt;

&lt;p&gt;Choisissez un jolie &lt;a href="https://upload.wikimedia.org/wikipedia/commons/thumb/8/81/LinkedIn_icon.svg/1200px-LinkedIn_icon.svg.png" rel="noopener noreferrer"&gt;logo LinkedIn&lt;/a&gt;, il intégrera notre bouton personnalisé « &lt;strong&gt;&lt;em&gt;Se connecter via LinkedIn&lt;/em&gt;&lt;/strong&gt; » et nous permettra de rediriger notre client à l'adresse suivante :&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://www.linkedin.com/oauth/v2/authorization?response_type=code&amp;amp;client_id=${LINKEDIN_ENV.CLIENT_ID}&amp;amp;scope=${LINKEDIN_ENV.SCOPES}&amp;amp;redirect_uri=${LINKEDIN_ENV.REDIRECT_URI}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Décomposons cette adresse pour l'analyser &amp;amp; la comprendre :&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://www.linkedin.com/oauth/v2/authorization&lt;/code&gt; correspond à l'URL du portail d'authentification.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;?response_type=code&lt;/code&gt; précise que nous attendons un code d'authorization.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;amp;client_id=${LINKEDIN_ENV.CLIENT_ID}&lt;/code&gt; permet d'identifier notre application auprès des serveurs &lt;strong&gt;LinkedIn&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;amp;scope=${LINKEDIN_ENV.SCOPES}&lt;/code&gt; permet de spécifier les scopes requis par notre application.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;amp;redirect_uri=${LINKEDIN_ENV.REDIRECT_URI}&lt;/code&gt; permet de préciser l'URI à laquelle &lt;strong&gt;LinkedIn&lt;/strong&gt; doit rediriger le client connecté.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';

import LinkedInIcon from './assets/linkedin_icon.png';

const App = () =&amp;gt; {

  /* Load LinkedIn credentials from dotenv */
  const LINKEDIN_ENV = {
    CLIENT_ID: process.env['REACT_APP_LINKEDIN_CLIENT_ID'],
    SCOPES: process.env['REACT_APP_LINKEDIN_SCOPES'],
    REDIRECT_URI: process.env['REACT_APP_LINKEDIN_REDIRECT_URI'],
  }

  /* Handle the client redirection to LinkedIn authentication portal */
  const onSignInLinkedIn = () =&amp;gt; {
    window.location.replace(`https://www.linkedin.com/oauth/v2/authorization?response_type=code&amp;amp;client_id=${LINKEDIN_ENV.CLIENT_ID}&amp;amp;scope=${LINKEDIN_ENV.SCOPES}&amp;amp;redirect_uri=${LINKEDIN_ENV.REDIRECT_URI}`);
  }

  return (
    &amp;lt;div className="App"&amp;gt;

        &amp;lt;button className="LinkedIn_Button" onClick={onSignInLinkedIn}&amp;gt;
          &amp;lt;img src={LinkedInIcon} alt="Logo LinkedIn" className="LinkedIn_Icon" /&amp;gt;
          &amp;lt;span&amp;gt;Se connecter via LinkedIn&amp;lt;/span&amp;gt;
        &amp;lt;/button&amp;gt;

    &amp;lt;/div&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A ce stade, le bouton « &lt;strong&gt;&lt;em&gt;Se connecter via LinkedIn&lt;/em&gt;&lt;/strong&gt; » nous redirige vers le portail d'authentification &lt;strong&gt;LinkedIn&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjdzijtutbfp46gj2mhn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjdzijtutbfp46gj2mhn.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Si une erreur survient, vérifiez que les variables d'environnement saisies correspondent bien aux credentials fournis pour votre application, que le produit « &lt;strong&gt;&lt;em&gt;Sign-In with LinkedIn&lt;/em&gt;&lt;/strong&gt; » soit bien activé, que les scopes spécifiés soient valides &amp;amp; que l'URI de redirection transmise via l'URI du portail d'authentification soit autorisée via le panel de gestion de votre application depuis &lt;a href="https://www.linkedin.com/developers/apps" rel="noopener noreferrer"&gt;LinkedIn developers&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;ℹ️ N'oubliez pas de re-démarrer votre application React une fois les variables d'environnement modifiées. Celles-ci ne se synchronisent pas automatiquement lors de leur modification.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Si la configuration de votre application est correcte &amp;amp; que le formulaire d'authentification s'affiche sans problèmes, essayez alors de vous connecter :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frkbc1lle7uvjfir6rrul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frkbc1lle7uvjfir6rrul.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;L'application requiert dès lors, comme prévu, les scopes préalablement configurés tels que :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Les informations basiques du profil utilisateur&lt;/strong&gt; &lt;em&gt;(= Utiliser votre nom et photo)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;L'adresse e-mail de l'utilisateur&lt;/strong&gt; &lt;em&gt;(= Utiliser l'adresse e-mail principale associée à votre compte LinkedIn)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;[...] Il ne vous reste plus qu'à autoriser l'exploitation de ces scopes par l'application pour être redirigé à l'URI transmise au préalable, accompagné d'un « &lt;strong&gt;&lt;em&gt;authorization code&lt;/em&gt;&lt;/strong&gt; » qui se présente sous forme de &lt;a href="https://en.wikipedia.org/wiki/Query_string" rel="noopener noreferrer"&gt;query params&lt;/a&gt;, tel que :&lt;/p&gt;

&lt;p&gt;&lt;code&gt;?code=AQVumAQMn0w5BWznTRD0nnMRDA_fBYNzBBHe3yxmOxMQvm9nW0yw8b7QrUNffvuEkSYlYOXU26FGL7vvm7W0-oe8pqyDFXj5w6JxW3A0bhCLPIXQsYMZfP_9QwRrJDPDdjZIYhPILu0xfGEWaM44Isy6FxYTtzMLf1HxkXdqGUVoJjR_TGPEpC-c3Dcj-oqJue6I7NgsurIZltYFuQw&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;ℹ️ Notez qu'en procédant à cette authentification, vous léguez des permissions d'exploitation des ressources liées à votre profil LinkedIn, à une application tierce. Pour révoquer ces permissions, vous pouvez vous rendre dans les préférences de votre profil LinkedIn puis dans : &lt;strong&gt;Confidentialité des données &amp;gt; Autres applications &amp;gt; Services autorisés &amp;gt; Nom de l'application &amp;gt; Supprimer&lt;/strong&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  📥 Récupération du code d'autorisation et transmission à l'API
&lt;/h3&gt;

&lt;p&gt;Actuellement, nous avons un bouton permettant de rediriger le client vers le portail d'authentification LinkedIn. Si la connexion d'un utilisateur est valide, il est redirigée vers notre application React, avec un code d'autorisation.&lt;/p&gt;

&lt;p&gt;L'enjeu est maintenant de récupérer ce code d'autorisation pour le transmettre à notre API.&lt;/p&gt;

&lt;p&gt;Toujours dans notre fichier &lt;code&gt;App.tsx&lt;/code&gt;, tâchons sans plus tarder de le récupérer :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    /* Check if a Linkedin authorization code is provided */
    const authorizationCode = new URLSearchParams(window.location.search).get('code');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La constante &lt;code&gt;authorizationCode&lt;/code&gt; intègre désormais le code d'autorisation retourné par LinkedIn. &lt;/p&gt;

&lt;p&gt;A défaut, cette dernière sera respectivement &lt;code&gt;undefined&lt;/code&gt;. Il ne nous reste plus qu'à traiter ce code dès lorsque notre &lt;code&gt;App.tsx&lt;/code&gt; est initialisé, dans l'hypothèse que ce dernier ne soit pas &lt;code&gt;undefined&lt;/code&gt;. Nous utiliserons le hook &lt;strong&gt;React&lt;/strong&gt; &lt;code&gt;useEffect&lt;/code&gt; pour se faire :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, {useEffect} from 'react';

import LinkedInIcon from './assets/linkedin_icon.png';

const App = () =&amp;gt; {

    /* Load LinkedIn credentials from dotenv */
    const LINKEDIN_ENV = {
        CLIENT_ID: process.env['REACT_APP_LINKEDIN_CLIENT_ID'],
        SCOPES: process.env['REACT_APP_LINKEDIN_SCOPES'],
        REDIRECT_URI: process.env['REACT_APP_LINKEDIN_REDIRECT_URI'],
    }

    /* Handle the client redirection to LinkedIn authentication portal */
    const onSignInLinkedIn = () =&amp;gt; {
        window.location.replace(`https://www.linkedin.com/oauth/v2/authorization?response_type=code&amp;amp;client_id=${LINKEDIN_ENV.CLIENT_ID}&amp;amp;scope=${LINKEDIN_ENV.SCOPES}&amp;amp;redirect_uri=${LINKEDIN_ENV.REDIRECT_URI}`);
    }

    /* Check if a Linkedin authorization code is provided */
    const authorizationCode = new URLSearchParams(window.location.search).get('code');

    useEffect(() =&amp;gt; {
        if (authorizationCode) {
          // TODO: Send authorization code to server here
        }
    }, [authorizationCode]);

    return (
        &amp;lt;div className="App"&amp;gt;

            &amp;lt;button className="LinkedIn_Button" onClick={onSignInLinkedIn}&amp;gt;
              &amp;lt;img src={LinkedInIcon} alt="Logo LinkedIn" className="LinkedIn_Icon" /&amp;gt;
              &amp;lt;span&amp;gt;Se connecter via LinkedIn&amp;lt;/span&amp;gt;
            &amp;lt;/button&amp;gt;

        &amp;lt;/div&amp;gt;
    );

}

export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dans notre hook &lt;code&gt;useEffect&lt;/code&gt;, le code d'autorisation peut enfin être exploité (sous réserve qu'il soit définit).&lt;/p&gt;

&lt;p&gt;Installons à présent la librairie &lt;a href="https://www.npmjs.com/package/axios" rel="noopener noreferrer"&gt;axios&lt;/a&gt;, librairie très réputée permettant d'émettre des requêtes. Nous pourrions également utiliser n'importe quelle autre librairie de ce type, voir utiliser simplement l'API &lt;a href="https://developer.mozilla.org/fr/docs/Web/API/fetch" rel="noopener noreferrer"&gt;fetch&lt;/a&gt; intégrée nativement dans Javascript et permettant de d'exploiter des requêtes AJAX.&lt;/p&gt;

&lt;p&gt;Une fois la librairie installée, nous créerons une instance &lt;strong&gt;axios&lt;/strong&gt; qui se basera sur la variable d'environnement &lt;code&gt;REACT_APP_API_URL&lt;/code&gt; préalablement déclarée :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    import axios from 'axios';

    const baseURL = process.env['REACT_APP_API_URL'];
    export default axios.create({ baseURL, withCredentials: true });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Une fois que notre API sera créée et exposée, nous n'aurons qu'à incrémenter notre variable d'environnement &lt;code&gt;REACT_APP_API_URL&lt;/code&gt; par l'URL de notre API et utiliser l'instance déclarée ci-dessus pour échanger avec cette dernière.&lt;/p&gt;

&lt;p&gt;Revenons dans notre fichier &lt;code&gt;App.tsx&lt;/code&gt;, importons notre instance &lt;strong&gt;axios&lt;/strong&gt; puis requêtons le futur end-point de notre API auquel nous enverrons le code d'autorisation reçu :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, {useEffect} from 'react';
import API from './api/api'; // import of our axios instance

import LinkedInIcon from './assets/linkedin_icon.png';

const App = () =&amp;gt; {

    /* Load LinkedIn credentials from dotenv */
    const LINKEDIN_ENV = {
        CLIENT_ID: process.env['REACT_APP_LINKEDIN_CLIENT_ID'],
        SCOPES: process.env['REACT_APP_LINKEDIN_SCOPES'],
        REDIRECT_URI: process.env['REACT_APP_LINKEDIN_REDIRECT_URI'],
    }

    /* Handle the client redirection to LinkedIn authentication portal */
    const onSignInLinkedIn = () =&amp;gt; {
        window.location.replace(`https://www.linkedin.com/oauth/v2/authorization?response_type=code&amp;amp;client_id=${LINKEDIN_ENV.CLIENT_ID}&amp;amp;scope=${LINKEDIN_ENV.SCOPES}&amp;amp;redirect_uri=${LINKEDIN_ENV.REDIRECT_URI}`);
    }

    /* Check if a Linkedin authorization code is provided */
    const authorizationCode = new URLSearchParams(window.location.search).get('code');

    useEffect(() =&amp;gt; {
        if (authorizationCode) {
            API.post('linkedin/auth/login', { authorization_code: authorizationCode })
                .then((res) =&amp;gt; console.log('Request received', res))
                .catch((err) =&amp;gt; console.log('An error occured', err));
        }
    }, [authorizationCode]);

    return (
        &amp;lt;div className="App"&amp;gt;

            &amp;lt;button className="LinkedIn_Button" onClick={onSignInLinkedIn}&amp;gt;
              &amp;lt;img src={LinkedInIcon} alt="Logo LinkedIn" className="LinkedIn_Icon" /&amp;gt;
              &amp;lt;span&amp;gt;Se connecter via LinkedIn&amp;lt;/span&amp;gt;
            &amp;lt;/button&amp;gt;

        &amp;lt;/div&amp;gt;
    );

}

export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dans l'implémentation de notre requête ci-dessus, nous spéculons à la fois sur :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;l'end-point cible de notre API (nous avons choisi &lt;code&gt;linkedin/auth/login&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;la dénomination de la clé passée dans le &lt;code&gt;body&lt;/code&gt; de notre requête (nous avons choisi &lt;code&gt;authorization_code&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;u&gt;📌 Récapitulons&lt;/u&gt;&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;Si l'on part du principe que l'URL de notre futur API est &lt;code&gt;http://localhost:8080/&lt;/code&gt; et que la variable d'environnement &lt;code&gt;REACT_APP_API_URL&lt;/code&gt; est respectivement incrémentée par cette valeur/url, notre &lt;strong&gt;application React&lt;/strong&gt; émettra ici une requête &lt;code&gt;POST&lt;/code&gt; à l'adresse &lt;code&gt;http://localhost:8080/linkedin/auth/login&lt;/code&gt; avec une clé &lt;code&gt;authorization_code&lt;/code&gt; en &lt;code&gt;body&lt;/code&gt; incluant la valeur du code d'autorisation retourné par &lt;strong&gt;LinkedIn&lt;/strong&gt; pour notre utilisateur.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏗️ Développement de l'API avec NestJS
&lt;/h2&gt;

&lt;p&gt;Maintenant que notre application web est fonctionnelle et capable de transmettre le code d'autorisation client à l'API, conçevoir l'API il va falloir pour exploiter les données de l'utilisateur LinkedIn en toute sécurité.&lt;/p&gt;

&lt;p&gt;Nous utiliserons NestJS, un framework Typescript basé sur Node.js mais vous pouvez évidement choisir un framework ou une technologie plus à votre aise selon vos habitudes. &lt;/p&gt;

&lt;p&gt;Ci-dessous, nous verrons comment entreprendre l'implémentation via NestJS.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧰 Initialisation du serveur &amp;amp; configuration préalable
&lt;/h3&gt;

&lt;p&gt;Une fois NestJS installé, initialisez un nouveau projet via la commande : &lt;code&gt;nest new linkedin-oauth-api&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Encore une fois, nous privilégierons une arborescence de projet simpliste/minimaliste et une architecture en microservices, NestJS étant idéal pour ce type d'architecture.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;gt; linkedin-oauth-api
        &amp;gt; dist
        &amp;gt; node_modules
        &amp;gt; src
            &amp;gt; config
            &amp;gt; microservices
            app.module.ts
            main.ts
        .env
        .env.example
        .eslintrc.js
        .gitignore
        .prettierrc
        nest-cli.json
        package.json
        tsconfig.build.json
        tsconfig.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Au même titre que notre application web, configurons les fichiers relatifs aux variables d'environnement &lt;code&gt;.env&lt;/code&gt; et &lt;code&gt;.env.example&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    PORT=8080
    NODE_ENV=development

    LINKEDIN_API_CLIENT_ID=
    LINKEDIN_API_CLIENT_SECRET=
    LINKEDIN_API_REDIRECT_URI=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Il ne tient plus qu'à vous de le compléter selon les credentials fournis pour votre application LinekdIn.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;ℹ️ Rappel : retrouvez les credentials liées à votre application via le panel &lt;a href="https://www.linkedin.com/developers/apps" rel="noopener noreferrer"&gt;LinkedIn developers&lt;/a&gt;, au sein de la rubrique « &lt;strong&gt;Auth&lt;/strong&gt; »&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pour lire les variables d'environnement avec NestJS, nous aurons besoin d'installer une librairie conçue par les soins des développeurs du framework, à savoir : &lt;code&gt;@nestjs/config&lt;/code&gt;, utilisez donc &lt;code&gt;npm i --save @nestjs/config&lt;/code&gt; si ce n'est pas déjà fait.&lt;/p&gt;

&lt;p&gt;Initialisez ensuite le fichier &lt;code&gt;root &amp;gt; src &amp;gt; config &amp;gt; config.ts&lt;/code&gt; en y ajoutant le code suivant :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface LinkedInConfig {
    clientId: string;
    clientSecret: string;
    redirectUri: string;
}

interface ConfigProps {
    port: number;
    linkedIn: LinkedInConfig;
}

export const config = (): ConfigProps =&amp;gt; ({
    port: parseInt(process.env.PORT, 10) || 8080,
    linkedIn: {
        clientId: process.env.LINKEDIN_API_CLIENT_ID,
        clientSecret: process.env.LINKEDIN_API_CLIENT_SECRET,
        redirectUri: process.env.LINKEDIN_API_REDIRECT_URI,
    }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Même si nous avons ajouté la clé &lt;code&gt;NODE_ENV&lt;/code&gt; par convention, aux fichiers relatifs aux variables d'environnement, nous n'en ferons pas grand chose dans ce tutoriel.&lt;/p&gt;

&lt;p&gt;Une fois le fichier de config configuré et les types ajoutés, il faudra dorénavant l'importer dans le fichier &lt;code&gt;app.module.ts&lt;/code&gt; comme suit :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Module } from '@nestjs/common';

/* NestJS Config */
import { ConfigModule } from '@nestjs/config'; // import of our @nestjs/config lib.
import { config } from './config/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [config]
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;[...] Le plus haut fichier de votre projet &lt;code&gt;main.ts&lt;/code&gt; devra intégrer le code suivant :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { NestFactory } from '@nestjs/core';
import { Logger } from '@nestjs/common';
import { AppModule } from './app.module';
import { ConfigService } from '@nestjs/config'; // import of our @nestjs/config lib.

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.enableCors({
    origin: true,
    methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
    credentials: true,
  });

  const configService = app.get(ConfigService);
  const port = configService.get&amp;lt;number&amp;gt;('port'); // get port from dotenv file

  await app.listen(port, '0.0.0.0');
  console.log(`\n\n\x1b[34m🚀 Server is now available at the \x1b[37m${await app.getUrl()}\x1b[34m address!\x1b[0m`);
}

bootstrap();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🎉 Notre API peut maintenant être démarrée en mode développement via la commande : &lt;code&gt;npm run start:dev&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚙️ Initialisation de notre nouveau microservice
&lt;/h3&gt;

&lt;p&gt;Maintenant que notre API est exposée via &lt;code&gt;http://localhost:8080/&lt;/code&gt;, il nous faut créer la route permettant l'échange du code d'autorisation reçu par notre web-application contre un jeton d'accès permettant l'exploitation des ressources utilisateurs. Créons alors le microservice &lt;code&gt;linkedin&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;gt; microservices
        &amp;gt; linkedin
            &amp;gt; api
                linkedin.api.ts // next chapter
            &amp;gt; controllers
                linkedin.controller.ts
            &amp;gt; objects
                linkedin-user-profile.object.ts // next chapter
                linkedin-user-token.object.ts // next chapter
            &amp;gt; services
                linkedin.service.ts
            linkedin.module.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;1️⃣ Déclaration du service linkedin&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Le fichier &lt;code&gt;src &amp;gt; microservices &amp;gt; linkedin &amp;gt; services &amp;gt; linkedin.service.ts&lt;/code&gt; nous permettra d'implémenter l'intégralité des méthodes liées à LinkedIn.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Injectable } from '@nestjs/common';

@Injectable()
export class LinkedInService {
    constructor() {}

    async login(authorizationCode: string): Promise&amp;lt;any&amp;gt; {
        console.log('&amp;gt; Authorization code received :', authorizationCode);
        // ...
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2️⃣ Déclaration du controlleur linkedin&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Le fichier &lt;code&gt;src &amp;gt; microservices &amp;gt; linkedin &amp;gt; controllers &amp;gt; linkedin.controller.ts&lt;/code&gt; permettra de définir la route d'accès, gérer la réception des données fournis dans le corps de la requête et s'assurera de transmettre les informations aux méthodes de notre service préalablement déclaré.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Body, Controller, Post } from '@nestjs/common';

import { LinkedInService } from 'src/microservices/linkedin/services/linkedin.service';

@Controller('linkedin')
export class LinkedInController {
  constructor(private readonly linkedInService: LinkedInService) {}

  @Post('auth/login')
  async login(@Body('authorization_code') authorizationCode: string): Promise&amp;lt;any&amp;gt; {
      return await this.linkedInService.login(authorizationCode);
  }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3️⃣ Déclaration du module linkedin&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Le fichier &lt;code&gt;src &amp;gt; microservices &amp;gt; linkedin &amp;gt; linkedin.module.ts&lt;/code&gt; fera office de porte d'accès à notre controlleur puis à notre service, il assurera l'import global des fichiers préalablement déclarés et permettra d'y greffer des modules / services complémentaires, il est une base rattaché au corps de notre serveur.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Module } from '@nestjs/common';

import { LinkedInController } from 'src/microservices/linkedin/controllers/linkedin.controller';
import { LinkedInService } from 'src/microservices/linkedin/services/linkedin.service';

@Module({
  imports: [],
  controllers: [LinkedInController],
  providers: [LinkedInService],
})
export class LinkedInModule {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4️⃣ Import de notre module auprès du module global&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Notre fichier &lt;code&gt;app.module.ts&lt;/code&gt; est l'un des fichiers des plus haut-niveau dans l'architecture de notre application, il est en tout cas le module parent à notre serveur. C'est auprès de ce dernier qu'il nous faudra s'assurer de l'import de notre nouveau module linkedin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Module } from '@nestjs/common';

/* NestJS Config */
import { ConfigModule } from '@nestjs/config';
import { config } from './config/config';

/* Imported modules */
import { LinkedInModule } from './microservices/linkedin/linkedin.module'; // import of LinkedIn module

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [config]
    }),
    LinkedInModule, // import of LinkedIn module
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A ce stade, la connexion &lt;strong&gt;LinkedIn&lt;/strong&gt; via notre &lt;strong&gt;web-application React&lt;/strong&gt; doit être capable de provoquer l'impression du code d'autorisation via notre serveur 🎉&lt;/p&gt;

&lt;h3&gt;
  
  
  🔐 Obtention d'un jeton d'accès depuis un code d'autorisation
&lt;/h3&gt;

&lt;p&gt;Maintenant que nous reçevons le code d'autorisation au sein de la méthode &lt;code&gt;login(...)&lt;/code&gt; de notre service &lt;strong&gt;LinkedIn&lt;/strong&gt;, tâchons de l'échanger à &lt;strong&gt;LinkedIn&lt;/strong&gt; contre un jeton d'accès.&lt;/p&gt;

&lt;p&gt;Pour commencer, nous créerons le &lt;strong&gt;SDK LinkedIn&lt;/strong&gt;. Celui-ci nous permettra de faire appel à des méthodes qui, quant à elles, se chargeront de formuler les requêtes adéquates auprès des serveurs de &lt;strong&gt;LinkedIn&lt;/strong&gt;. Il s'agit ni plus ni moins que de déclarer une instance &lt;strong&gt;axios&lt;/strong&gt; &lt;em&gt;(comme nous l'avons fait avec &lt;strong&gt;React&lt;/strong&gt;)&lt;/em&gt; et de déclarer des méthodes invoquant cette instance, en ciblant des routes spécifiques.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ConfigService } from "@nestjs/config";
import axios from "axios";

const linkedInAuthentication = () =&amp;gt; {
    const axiosInstance = axios.create({
        baseURL: 'https://www.linkedin.com/oauth/v2',
        withCredentials: true,
    });
    return axiosInstance;
}
export const loginLinkedIn = async (code: string) =&amp;gt; /* Get accessToken from an authorization code */
    await linkedInAuthentication().get('/accessToken', {
        params: {
            grant_type: 'authorization_code',
            client_id: new ConfigService().get('LINKEDIN_API_CLIENT_ID'),
            client_secret: new ConfigService().get('LINKEDIN_API_CLIENT_SECRET'),
            code,
            redirect_uri: new ConfigService().get('LINKEDIN_API_REDIRECT_URI'),
        },
    });
export const checkSessionValidity = async (accessToken: string) =&amp;gt; {/* Introspect and verify an accessToken */
    const body = {
        client_id: new ConfigService().get('LINKEDIN_API_CLIENT_ID'),
        client_secret: new ConfigService().get('LINKEDIN_API_CLIENT_SECRET'),
        token: accessToken,
    };
    return await linkedInAuthentication().post('/introspectToken', new URLSearchParams(body), {
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La méthode &lt;code&gt;checkSessionValidity&lt;/code&gt; ci-dessus est d'une importance toute particulière. &lt;/p&gt;

&lt;p&gt;En effet, nombreu.x.ses sont les développeu.r.ses à vérifier l'état de validité d'un jeton d'accès en essayant d'obtenir des ressources utilisateurs. Le problème de ce procédé est le suivant : l'obtention de ressources liées à un utilisateur augmente le quota d'exploitation de votre application pour une route spécifique. &lt;/p&gt;

&lt;p&gt;Si à priori, en phase de test et/ou lors d'une utilisation de votre application à faible traffic, aucun problème ne survient, l'application montrera vite des signes de faiblesse et rendra des fonctionnalités obsolètes lors de son évolution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhdhqqsdu3sggdjhsa9j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhdhqqsdu3sggdjhsa9j.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La route cible de la méthode &lt;code&gt;checkSessionValidity&lt;/code&gt; n'a, quant à elle, aucun impact sur le quota d'utilisation des services par votre application. Elle peut être sollicitée par un &lt;code&gt;guard&lt;/code&gt;, &lt;code&gt;decorator&lt;/code&gt; mais encore un &lt;code&gt;middleware&lt;/code&gt; pour attester ou non de la validité d'un jeton d'accès utilisateur.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;ℹ️ Les plateformes comme &lt;strong&gt;LinkedIn, Facebook, Google&lt;/strong&gt; etc.. se prémunissent de la surexploitation de leurs serveurs en instaurant ces limitations à quota réinitialisable&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;[...] Après avoir installé la librairie &lt;code&gt;class-validator&lt;/code&gt; en utilisant &lt;code&gt;npm i --save class-validator&lt;/code&gt;, il nous faudra créer l'objet &lt;code&gt;LinkedInUserTokenObject&lt;/code&gt;, cet object incluera le jeton d'accès utilisateur ainsi que sa date d'expiration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { IsNumber, IsString } from "class-validator";

export class LinkedInUserTokenObject {
    @IsString()
    accessToken: string;

    @IsNumber()
    expireIn: number;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Revenons maintenant à la méthode &lt;code&gt;login(...)&lt;/code&gt; de notre service &lt;code&gt;linkedin.service.ts&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Injectable, UnauthorizedException } from '@nestjs/common';
import { loginLinkedIn } from 'src/microservices/linkedin/api/linkedin.api';

import { LinkedInUserTokenObject } from '../objects/linkedin-user-token.object';

@Injectable()
export class LinkedInService {
    constructor() {}

    async login(authorizationCode: string): Promise&amp;lt;LinkedInUserTokenObject&amp;gt; {
        try {
            const response = await loginLinkedIn(authorizationCode);
            const { access_token: accessToken, expires_in: expireIn } = response.data;
            return { accessToken, expireIn }
        } catch (err) {
            throw new UnauthorizedException('Unable to login as LinkedIn user from the provided authorization code!');
        }
    }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Puis à notre controlleur &lt;code&gt;linkedin.controller.ts&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Body, Controller, Post } from '@nestjs/common';

import { LinkedInService } from 'src/microservices/linkedin/services/linkedin.service';
import { LinkedInUserTokenObject } from '../objects/linkedin-user-token.object';

@Controller('linkedin')
export class LinkedInController {
    constructor(private readonly linkedInService: LinkedInService) {}

    @Post('auth/login')
    async login(@Body('authorization_code') authorizationCode: string): Promise&amp;lt;LinkedInUserTokenObject&amp;gt; {
        const token: LinkedInUserTokenObject = 
            await this.linkedInService.login(authorizationCode); // Get permissions from LinkedIn server(s)
        return token;
    }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si nous inspectons à présent l'onglet "&lt;strong&gt;Réseau/Network&lt;/strong&gt;" de notre navigateur, une fois la redirection depuis le portail d'authentification &lt;strong&gt;LinkedIn&lt;/strong&gt; effectuée, on peut remarquer que la requête émise avec le code d'autorisation reçoit bel &amp;amp; bien une réponse incluant les propriétés de l'utilisateur : &lt;code&gt;accessToken&lt;/code&gt; et &lt;code&gt;expireIn&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Evidemment, nous retournons ces informations uniquement pour analyser le résultat et savoir notre code fonctionnel.&lt;/p&gt;

&lt;p&gt;Nous allons maintenant voir comment obtenir les informations d'un utilisateur depuis ce précieux &lt;code&gt;accessToken&lt;/code&gt; que nous obtenons.&lt;/p&gt;

&lt;h3&gt;
  
  
  📥 Exploitation des ressources liées à un utilisateur LinkedIn
&lt;/h3&gt;

&lt;p&gt;Pour exploiter les ressources auxquelles notre application est autorisée à accéder, une mise à jour de notre SDK LinkedIn s'impose, nous ajouterons dans notre fichier &lt;code&gt;src &amp;gt; microservices &amp;gt; linkedin &amp;gt; api &amp;gt; linkedin.api.ts&lt;/code&gt;, x2 nouvelles routes permettant de récupérer :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;l'adresse e-mail de l'utilisateur&lt;/li&gt;
&lt;li&gt;le nom/prénom &amp;amp; image de profil de l'utilisateur
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ConfigService } from "@nestjs/config";
import axios from "axios";

/* (1) LinkedIn API | global endpoint */
const linkedInAPI = () =&amp;gt; {
    const axiosInstance = axios.create({
        baseURL: 'https://api.linkedin.com/v2',
        withCredentials: true,
    });
    return axiosInstance;
}
/* [LinkedIn] Get email address */
export const getLinkedInEmailAddress = async (accessToken: string) =&amp;gt; 
    linkedInAPI().get(`/emailAddress?q=members&amp;amp;projection=(elements*(handle~))`, {headers: { Authorization: `Bearer ${accessToken}` }});
/* [LinkedIn] Get (first/last)name and profile picture */
export const getLinkedInProfile = async (accessToken: string) =&amp;gt; 
    linkedInAPI().get(`/me?projection=(id,firstName,lastName,emailAddress,profilePicture(displayImage~:playableStreams))`, {headers: { Authorization: `Bearer ${accessToken}` }});


/* (2) Authentication */
const linkedInAuthentication = () =&amp;gt; {
    const axiosInstance = axios.create({
        baseURL: 'https://www.linkedin.com/oauth/v2',
        withCredentials: true,
    });
    return axiosInstance;
}
export const loginLinkedIn = async (code: string) =&amp;gt; /* Get accessToken from an authorization code */
    await linkedInAuthentication().get('/accessToken', {
        params: {
            grant_type: 'authorization_code',
            client_id: new ConfigService().get('LINKEDIN_API_CLIENT_ID'),
            client_secret: new ConfigService().get('LINKEDIN_API_CLIENT_SECRET'),
            code,
            redirect_uri: new ConfigService().get('LINKEDIN_API_REDIRECT_URI'),
        },
    });
export const checkSessionValidity = async (accessToken: string) =&amp;gt; {/* Introspect and verify an accessToken */
    const body = {
        client_id: new ConfigService().get('LINKEDIN_API_CLIENT_ID'),
        client_secret: new ConfigService().get('LINKEDIN_API_CLIENT_SECRET'),
        token: accessToken,
    };
    return await linkedInAuthentication().post('/introspectToken', new URLSearchParams(body), {
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le type de l'object &lt;code&gt;LinkedInUserProfileObject&lt;/code&gt; ayant pour vocation de répertorier les informations de notre utilisateur doit être créé :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { IsNumber, IsString } from "class-validator";

export class LinkedInUserProfileObject {
    @IsString()
    firstName: string;

    @IsString()
    lastName: string;

    @IsString()
    email: string;

    @IsString()
    profileImageUrl: string;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Une nouvelle méthode permettant de récupérer le profil utilisateur depuis l'&lt;code&gt;accessToken&lt;/code&gt; obtenu doit ensuite être implémentée dans notre fichier &lt;code&gt;linkedin.service.ts&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Injectable, InternalServerErrorException, UnauthorizedException } from '@nestjs/common';
import { getLinkedInEmailAddress, getLinkedInProfile, loginLinkedIn } from 'src/microservices/linkedin/api/linkedin.api';

import { LinkedInUserTokenObject } from '../objects/linkedin-user-token.object';
import { LinkedInUserProfileObject } from '../objects/linkedin-user-profile.object';

@Injectable()
export class LinkedInService {
    constructor() {}

    async login(authorizationCode: string): Promise&amp;lt;LinkedInUserTokenObject&amp;gt; {
        try {
            const response = await loginLinkedIn(authorizationCode);
            const { access_token: accessToken, expires_in: expireIn } = response.data;
            return { accessToken, expireIn }
        } catch (err) {
            throw new UnauthorizedException('Unable to login as LinkedIn user from the provided authorization code!');
        }
    }

    async getProfile(accessToken: string): Promise&amp;lt;LinkedInUserProfileObject&amp;gt; {
        /* (1) Get email of LinkedIn client */
        const req1 = await getLinkedInEmailAddress(accessToken);
        const email = req1?.data?.elements[0]['handle~'].emailAddress;

        /* (2) Get (first/last)name and profile image of LinkedIn client */
        const req2 = await getLinkedInProfile(accessToken);
        const firstName = req2?.data?.firstName?.localized?.fr_FR;
        const lastName = req2?.data?.lastName?.localized?.fr_FR;
        const profileImageUrl = req2?.data?.profilePicture['displayImage~']?.elements[0]?.identifiers[0]?.identifier;

        if (!firstName || !lastName || !profileImageUrl || !email) throw new InternalServerErrorException('An error occured while parsing the retrieved profile from LinkedIn!');
        return {
            firstName,
            lastName,
            email,
            profileImageUrl,
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Il ne nous reste plus qu'à modifier notre fichier &lt;code&gt;linkedin.controller.ts&lt;/code&gt; pour retourner les informations de l'utilisateur plutôt que de retourner le jeton d'accès client :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Body, Controller, Post } from '@nestjs/common';

import { LinkedInService } from 'src/microservices/linkedin/services/linkedin.service';
import { LinkedInUserTokenObject } from '../objects/linkedin-user-token.object';
import { LinkedInUserProfileObject } from '../objects/linkedin-user-profile.object';

@Controller('linkedin')
export class LinkedInController {
  constructor(private readonly linkedInService: LinkedInService) {}

  @Post('auth/login')
  async login(@Body('authorization_code') authorizationCode: string): Promise&amp;lt;any&amp;gt; {
    const token: LinkedInUserTokenObject = 
        await this.linkedInService.login(authorizationCode); // Get permissions from LinkedIn server(s)
    const profile: LinkedInUserProfileObject = 
        await this.linkedInService.getProfile(token.accessToken); // Get profile from LinkedIn server(s)
    return profile;
  }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Félicitations 🎉&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Les informations de l'utilisateur ayant procédé à l'authentification &lt;strong&gt;LinkedIn&lt;/strong&gt; sont dorénavant renvoyées au client.&lt;/p&gt;

&lt;p&gt;Il ne tient plus qu'à vous d'utiliser ces informations selon vos ambitions, vous pourriez par exemple :&lt;/p&gt;

&lt;p&gt;&lt;em&gt;- Sauvegarder les informations liées à cet utilisateur en base de données dans le cadre d'une connexion/inscription via LinkedIn.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;- Garder en cache ou retourner les informations de l'utilisateur et les sauvegarder en cache front-end (via cookies http-only ou local storage, etc..)&lt;/em&gt;&lt;br&gt;
&lt;em&gt;- etc..&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎁 Bonus
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;-&amp;gt; (Source) Retrouvez l'intégralité du code qui constitue ce tutoriel via &lt;a href="https://github.com/MessageGit/oauth-sign-in-with-linkedin" rel="noopener noreferrer"&gt;ce répertoire Github&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>nestjs</category>
      <category>oauth2</category>
    </item>
    <item>
      <title>⏰ Les fonctions Debounce et Throttle : comment ça fonctionne ?</title>
      <dc:creator>Grégory CHEVALLIER</dc:creator>
      <pubDate>Tue, 17 Oct 2023 07:05:28 +0000</pubDate>
      <link>https://forem.com/messagegit/les-fonctions-debounce-et-throttle-comment-ca-fonctionne--3j9l</link>
      <guid>https://forem.com/messagegit/les-fonctions-debounce-et-throttle-comment-ca-fonctionne--3j9l</guid>
      <description>&lt;p&gt;Intégrées dans les applications web / mobiles &amp;amp; logiciels bureau, elles se cachent souvent derrière un champ de recherche pour habriter client/serveur d'une pluie de requêtes indésirables, voir même dans le défilement vertical d'une page pour épargner une infinité d'opérations inutiles à votre navigateur.&lt;/p&gt;

&lt;p&gt;Les fonctions &lt;strong&gt;Debounce&lt;/strong&gt; et &lt;strong&gt;Throttle&lt;/strong&gt; régulent au quotidien les performances de nos applications, mais comment fonctionnent t-elles ? Quelle approche aborder en utilisant React ?  &lt;/p&gt;




&lt;h2&gt;
  
  
  🔄 La fonction « Debounce »
&lt;/h2&gt;

&lt;p&gt;La fonction « Debounce » &lt;strong&gt;permet de limiter le nombre d'exécution d'une fonction&lt;/strong&gt; en l'exécutant &lt;strong&gt;une seule fois&lt;/strong&gt; à terme d'un délai donné, sous réserve que la fonction ne soit pas réinvoquée, le cas échéant, &lt;strong&gt;le délai est alors renouvellé&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Elle consiste à &lt;strong&gt;réguler le nombre d'appels à une fonction&lt;/strong&gt; en l'exécutant une seule fois, &lt;strong&gt;à terme d'une série d'évènements&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A quel moment est-ce nécessaire ?
&lt;/h3&gt;

&lt;p&gt;Pour comprendre l'intérêt du debounce, nous utiliserons l'exemple d'&lt;strong&gt;une barre de recherche&lt;/strong&gt; grâce à laquelle nous pourrions &lt;strong&gt;suggérer des résultats au fur et à mesure de notre saisie&lt;/strong&gt; &lt;em&gt;(sans avoir à utiliser de bouton de validation)&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, {useState} from "react";

const Debounce = () =&amp;gt; {
    const [searchValue, setSearchValue] = useState('');

    // Fetch articles with HTTP Request
    const fetchArticles = () =&amp;gt; {
        console.log('fetchArticles() is called!');
    }

    /* Handle the value of search input */
    const handleSearchValue = (e) =&amp;gt; {
        const newValue = e.target.value;
        setSearchValue(newValue);
        fetchArticles(); // Request to fetch articles
    }

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;input 
                type="text" 
                name="searchValue" 
                value={searchValue}
                onChange={handleSearchValue} 
                placeholder="Votre recherche ici.."
            /&amp;gt;
            &amp;lt;br /&amp;gt;
            {searchValue &amp;amp;&amp;amp; (
                &amp;lt;p&amp;gt;Voici les articles correspondant à:&amp;lt;br/&amp;gt;&amp;lt;b&amp;gt;« {searchValue} »&amp;lt;/b&amp;gt;&amp;lt;/p&amp;gt;
                /* ... Display articles ... */
            )}
        &amp;lt;/div&amp;gt;
    );
}

export default Debounce;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La première idée à nous venir à l'esprit, et généralement la plus simple à mettre en oeuvre : se mettre à l'écoute d'un input de type texte et suggérer des articles chaque fois que le texte change.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ Dans l'exemple ci-dessus, l'état &lt;code&gt;searchValue&lt;/code&gt; a pour vocation d'être synchronisé avec le champ de recherche. Il sera actualisé selon la valeur de notre input grâce à la callback &lt;code&gt;handleSearchValue&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Si le résultat escompté semble fonctionnel, il convient cependant d'admettre que le fetch permettant de récupérer nos articles via l'utilisation de la fonction &lt;code&gt;fetchArticles(..)&lt;/code&gt; sera quant à lui effectué autant de fois que la valeur de notre champ de recherche sera modifiée.&lt;/p&gt;

&lt;p&gt;La conséquence de cette maladresse : de nombreux problèmes de performance menaçant directement l'intégrité de votre application/serveur.&lt;/p&gt;

&lt;p&gt;En effet, supposons qu'un internaute effectue la recherche suivante: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;« Debounce et throttle, comment ça fonctionne ? »&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;La callback &lt;code&gt;handleSearchValue&lt;/code&gt; et la fonction &lt;code&gt;fetchArticles(..)&lt;/code&gt; sont alors invoquées &lt;strong&gt;45 fois&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Si l'on suppose que la requête effectuée pour récupérer les articles auprès de notre serveur requiert &lt;strong&gt;70.0kB&lt;/strong&gt;, pas moins de &lt;strong&gt;3,15Mo&lt;/strong&gt; seront en réalité téléchargés à travers l'exécution de &lt;strong&gt;45 requêtes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;Résultat de la recherche&lt;/u&gt; : &lt;strong&gt;3,08Mo&lt;/strong&gt; téléchargé inutilement.&lt;/p&gt;

&lt;p&gt;C'est donc à l'occasion de l'exécution de notre fonction &lt;code&gt;fetchArticles(..)&lt;/code&gt; que le debounce va être nécessaire et ainsi permettre une seule et unique exécution de notre requête, à terme de la saisie de l'utilisateur et non pas à chaque fois que la valeur de notre champ de recherche est modifiée.&lt;/p&gt;

&lt;h3&gt;
  
  
  Comment ça fonctionne ?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;🟨 Approche globale en Javascript&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;La fonction Debounce nécessite généralement deux paramètres :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Une callback, nous utiliserons &lt;code&gt;cb&lt;/code&gt; &lt;em&gt;(qui sera retournée sous condition)&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Un temps donné, nous utiliserons &lt;code&gt;delay&lt;/code&gt; &lt;em&gt;(en millisecondes)&lt;/em&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const debounce = (cb, delay = 1000) =&amp;gt; {
    let timer;
    return (...args) =&amp;gt; {
        clearTimeout(timer); // Clear of timeout if exist
        timer = setTimeout(() =&amp;gt; {
            cb(...args); // Return the callback
        }, delay);
    };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La fonction &lt;code&gt;debounce(..)&lt;/code&gt; à pour rôle d'initialiser un timer selon &lt;code&gt;delay&lt;/code&gt; et de le réinitialiser chaque fois que la fonction est appelée. Lorsque le timer arrive à échéance, il retourne la callback &lt;code&gt;cb&lt;/code&gt; fournie en premier paramètre qui permet son exécution.&lt;/p&gt;

&lt;p&gt;Ainsi, la callback &lt;code&gt;cb&lt;/code&gt; ne sera exécutée qu'à terme du delai spécifié si aucun ré-appel de la fonction ne survient d'ici là.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Note : La fonction debounce est fournie dans de nombreuses librairies Javascript dont &lt;a href="https://lodash.com/docs/4.17.15#debounce"&gt;lodash&lt;/a&gt;, la plus connue&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;⚙️ Hook React personnalisé &lt;u&gt;useDebounce&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;La fonction debounce peut parfois hériter de paramètres complémentaires, ça peut être le cas notament lors de la création d'un &lt;strong&gt;hook personnalisé avec React&lt;/strong&gt;. La dénomination de notre hook devient alors &lt;code&gt;useDebounce&lt;/code&gt;, c'est la solution que je vous propose de passer en revue 👇&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useRef, useEffect } from 'react';

export const useDebounce = (cb, delay, deps) =&amp;gt; {
    const timeout = useRef();
    useEffect(() =&amp;gt; {
        timeout.current = setTimeout(cb, delay);
        return () =&amp;gt; clearTimeout(timeout.current);
    }, deps);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On retrouve ici la logique propre au debounce qui permet d'initialiser un timer depuis un &lt;code&gt;delay&lt;/code&gt; dans l'optique de retourner une callback : le timer est initialisé lorsque le composant est monté et détruit lorsque le composant est démonté grâce à l'utilisation du hook &lt;code&gt;useEffect&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Voyons à présent comment exploiter ce nouveau hook depuis notre blog :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, {useState} from "react";

import {useDebounce} from "src/hooks/useDebounce"; // CUSTOM HOOK PATH

const Debounce = () =&amp;gt; {
    const [searchValue, setSearchValue] = useState('');

    // Fetch articles with HTTP Request
    const fetchArticles = () =&amp;gt; {
        console.log('fetchArticles() is called!');
    }

    /* Use of debounce hook to fetch articles */
    useDebounce(async () =&amp;gt; {
        if (searchValue) fetchArticles();
        // else .. 
    }, 1000, [searchValue]); 

    /* Handle the value of search input */
    const handleSearchValue = (e) =&amp;gt; {
        const newValue = e.target.value;
        setSearchValue(newValue);
    }

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;input 
                type="text" 
                name="searchValue" 
                value={searchValue}
                onChange={handleSearchValue} 
                placeholder="Votre recherche ici.."
            /&amp;gt;
            &amp;lt;br /&amp;gt;
            {searchValue &amp;amp;&amp;amp; (
                &amp;lt;p&amp;gt;Voici les articles correspondant à:&amp;lt;br/&amp;gt;&amp;lt;b&amp;gt;« {searchValue} »&amp;lt;/b&amp;gt;&amp;lt;/p&amp;gt;
                /* ... Display articles ... */
            )}
        &amp;lt;/div&amp;gt;
    );
}

export default Debounce;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notre debounce est enfin opérationnel 🎉&lt;/p&gt;

&lt;p&gt;Vous l'avez certainement remarqué 🧐 : notre hook &lt;code&gt;useDebounce&lt;/code&gt; a finalement l'allure d'un &lt;code&gt;useEffect&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Seulement, il inclut à présent la logique et le comportement de notre fonction debounce.&lt;/p&gt;

&lt;p&gt;La callback &lt;code&gt;handleSearchValue&lt;/code&gt; est de ce fait réduite à sa seule et unique fonction : garantir la synchronicité de la valeur de notre barre de recherche avec l'état &lt;code&gt;searchValue&lt;/code&gt;, alors que : parallèlement, notre debounce est à l'écoute des changements de cet état.&lt;/p&gt;

&lt;p&gt;Le fetch des articles sera ainsi exécuté 1 seconde après la fin de saisie de l'utilisateur dans la barre de recherche (sachant que &lt;code&gt;delay&lt;/code&gt; équivaut 1000ms), sous réserve que la recherche ne soit pas nulle.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Note : La callback transmise à notre hook &lt;code&gt;useDebounce&lt;/code&gt; sera exécutée une première fois par défaut lorsque le composant &lt;code&gt;Debounce&lt;/code&gt; sera monté. Cela peut représenter une contrainte dans certaines situations, une mesure conditionnelle devient nécessaire&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ⏯️ La fonction « Throttle »
&lt;/h2&gt;

&lt;p&gt;La fonction « &lt;strong&gt;Throttle&lt;/strong&gt; » permet de réguler le nombre d'exécution d'une fonction invoquée par une série d'évènements &lt;strong&gt;en l'exécutant une seule fois&lt;/strong&gt; &lt;em&gt;(toutes les 1000ms par exemple)&lt;/em&gt; &lt;strong&gt;selon un interval donné&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Elle est souvent utilisée pour appréhender une &lt;strong&gt;surexploitation des ressources&lt;/strong&gt; et d'éventuels &lt;strong&gt;problèmes de performance&lt;/strong&gt; à travers un excès d'opérations. &lt;/p&gt;

&lt;h3&gt;
  
  
  Quand est-ce nécessaire ?
&lt;/h3&gt;

&lt;p&gt;Reprenons l'exemple de notre blog et intéressons-nous à présent à la page de visualisation des articles : supposons que nous voulions implémenter un système de suivi des chapitres selon la position vertical du client par rapport à la hauteur de la page.&lt;/p&gt;

&lt;p&gt;Nous aurions naturellement besoin des propriétés retournées par l'évènement &lt;code&gt;scroll&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;useEffect(() =&amp;gt; {
    /* Chapters following feature */
    const updateCurrentChapter = (scrollTop, scrollAvailable) =&amp;gt; {
        console.log(`scroll progress =&amp;gt; ${scrollTop}/${scrollAvailable}`); // debug
        // Feature implementation here..
    }
    /* Handle scroll */
    const handleScroll = (e) =&amp;gt; {
        const scrollAvailable = (document.body.scrollHeight - window.innerHeight);
        const scrollTop = window.pageYOffset;
        updateCurrentChapter(scrollTop, scrollAvailable); // Throttle is required here
    }
    window.addEventListener("scroll", handleScroll);
    return () =&amp;gt; window.removeEventListener("scroll", handleScroll);
}, []);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si implémenter la nouvelle feature dans notre callback &lt;code&gt;handleScroll&lt;/code&gt; semble idéal, elle peut pour autant impliquer de véritables problèmes de performance au fil de l'évolution de l'application.&lt;/p&gt;

&lt;p&gt;En effet, de nouvelles fonctionnalités sont susceptibles d'être ajoutées et greffées à l'évènement &lt;code&gt;scroll&lt;/code&gt;,  les opérations cumulées les unes avec les autres peuvent représenter une charge de calculs trop importante quant au nombre d'occurences excessif de l'évènement &lt;code&gt;scroll&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;C'est à cette occasion que le « &lt;strong&gt;Throttle&lt;/strong&gt; » se révèle utile.&lt;/p&gt;

&lt;p&gt;Il va permettre d'exécuter notre fonction &lt;code&gt;updateCurrentChapter(..)&lt;/code&gt; et potentiellement d'autres fonctions nécessitant l'évènement &lt;code&gt;scroll&lt;/code&gt;, au fil du déroulement de la page grâce à l'évènement &lt;code&gt;scroll&lt;/code&gt;, à une fréquence plus raisonnable.&lt;/p&gt;

&lt;p&gt;Si l'évènement &lt;code&gt;scroll&lt;/code&gt; peut, dans cet exemple, invoquer notre fonction &lt;code&gt;updateCurrentChapter(..)&lt;/code&gt; jusqu'à 18 fois par seconde, on pourrait dorénavant décider de ne l'exécuter qu'une seule fois en l'espace d'une seconde et ainsi déshériter notre naviguateur d'une charge de calculs inutile pour parvenir au même résultat.&lt;/p&gt;

&lt;h3&gt;
  
  
  Comment ça fonctionne ?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;🟨 Approche globale en Javascript&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;La fonction &lt;strong&gt;Throttle&lt;/strong&gt; nécessite généralement deux paramètres:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Une callback, nous utiliserons &lt;code&gt;cb&lt;/code&gt; &lt;em&gt;(qui sera retourné sous condition)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Un delai, nous utiliserons &lt;code&gt;delay&lt;/code&gt; &lt;em&gt;(en millisecondes)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const throttle = (cb, delay) =&amp;gt; {
    let wait = false;
    return (...args) =&amp;gt; {
        if (wait) return;
        cb(...args);
        wait = true;
        setTimeout(() =&amp;gt; {
            wait = false;
        }, delay);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lorsque notre fonction &lt;code&gt;throttle(..)&lt;/code&gt; est invoquée, la callback &lt;code&gt;cb&lt;/code&gt; fournie est instantanément retournée (sous réserve qu'elle n'ai pas été invoquée depuis &lt;code&gt;delay&lt;/code&gt; millisecondes), il faudra à présent attendre &lt;code&gt;delay&lt;/code&gt; millisecondes avant qu'elle puisse être retournée à nouveau.&lt;/p&gt;

&lt;p&gt;Ainsi, le &lt;strong&gt;throttle&lt;/strong&gt; permet de réduire le nombre d'occurence d'une fonction lorsqu'elle est parfois excessivement invoquée par une série d'évènements.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;ℹ️ Note : La fréquence d'appels est réduite et on peut s'assurer que la fonction ne soit exécutée qu'une seule fois toutes les &lt;code&gt;delay&lt;/code&gt; millisecondes&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;⚙️ Hook React personnalisé &lt;u&gt;useThrottle&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Oublions à présent notre blog et concentrons-nous sur le fonctionnement du throttle grâce à un bouton à cliquer. L'enjeu est simple : cliquer rapidement sur un bouton et incrémenter un compteur tout en l'empêchant de s'incrémenter plus d'une fois toutes les X secondes selon &lt;code&gt;delay&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Le principe est le même que le cas précédent : &lt;strong&gt;réguler l'exécution d'une fonction invoquée par une série d'évènements excessivement nombreux&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Pour cela, commencons par créer notre nouveau hook React &lt;code&gt;useThrottle&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useEffect, useRef, useState } from 'react';

export const useThrottle = (value, delay = 500) =&amp;gt; {
    const [throttledValue, setThrottledValue] = useState(value);
    const lastExecuted = useRef(Date.now());

    useEffect(() =&amp;gt; {
        if (Date.now() &amp;gt;= lastExecuted.current + delay) {
            lastExecuted.current = Date.now();
            setThrottledValue(value);
        } else {
            /* Last update */
            const timeout = setTimeout(() =&amp;gt; {
                lastExecuted.current = Date.now();
                setThrottledValue(value);
            }, delay);
            return () =&amp;gt; clearTimeout(timeout);
        }
    }, [value, delay]);

    return throttledValue;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vous l'avez remarqué, notre callback &lt;code&gt;cb&lt;/code&gt; auparavant utilisée a été délaissée au profit d'une valeur &lt;code&gt;value&lt;/code&gt;. Quelque soit la fréquence à laquelle &lt;code&gt;value&lt;/code&gt; est modifiée, notre throttle garantie de l'actualiser et la retourner mais ne l'actualisera pas plus d'une fois chaque &lt;code&gt;delay&lt;/code&gt; millisecondes.&lt;/p&gt;

&lt;p&gt;Une fois le composant monté, l'object &lt;code&gt;lastExecuted&lt;/code&gt; devient persistant : on lui attribuera une date &lt;code&gt;Date.now()&lt;/code&gt; grâce à laquelle nous déterminerons si la valeur &lt;code&gt;value&lt;/code&gt; doit être modifiée et si la date &lt;code&gt;lastExecuted.current&lt;/code&gt; doit être raffraichit.&lt;/p&gt;

&lt;p&gt;Lorsque &lt;code&gt;value&lt;/code&gt; change, le throttle est sollicité. Si elle n'a pas été changée durant les &lt;code&gt;delay&lt;/code&gt; millisecondes, on raffraichit la valeur retournée et actualisons la date &lt;code&gt;lastExecuted.current&lt;/code&gt;, le cas échéant nous lançons un timer pour une dernière exécution dans l'hypothèse où aucun nouvel appel ne vienne l'annuler.&lt;/p&gt;

&lt;p&gt;Implémentons à présent notre bouton et nos compteurs :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState, useEffect } from 'react';

import { useThrottle } from 'src/hooks/useThrottle';

const Throttle = () =&amp;gt; {
    const [clickCount, setClickCount] = useState(0);
    const [clickCountThrottle, setClickCountThrottle] = useState(0);

    const throttledValue = useThrottle(clickCount, 1000);
    useEffect(() =&amp;gt; {
        if (throttledValue) setClickCountThrottle(clickCountThrottle + 1);
    }, [throttledValue]);

    return (
        &amp;lt;div className="Container"&amp;gt;
            &amp;lt;h2&amp;gt;Throttle&amp;lt;/h2&amp;gt;
            &amp;lt;div className="Content"&amp;gt;
                &amp;lt;button onClick={() =&amp;gt; setClickCount(clickCount + 1)}&amp;gt;Cliquez-moi&amp;lt;/button&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div className="Content"&amp;gt;
                &amp;lt;div className="Result_Item"&amp;gt;
                    &amp;lt;i&amp;gt;Nb. de click (sans throttle)&amp;lt;/i&amp;gt;&amp;lt;br /&amp;gt;
                    &amp;lt;span&amp;gt;{clickCount}&amp;lt;/span&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div className="Result_Item"&amp;gt;
                    &amp;lt;i&amp;gt;Nb. de click (avec throttle 1000ms)&amp;lt;/i&amp;gt;&amp;lt;br /&amp;gt;
                    &amp;lt;span&amp;gt;{clickCountThrottle}&amp;lt;/span&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default Throttle;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Et voilà 🎉&lt;/p&gt;

&lt;p&gt;Si l'on clique 11 fois sur notre bouton en moins de 2 secondes, notre état &lt;code&gt;clickCountThrottle&lt;/code&gt; ne sera incrémenté que 2 fois. &lt;code&gt;clickCount&lt;/code&gt; en revanche sera égal à 11.&lt;/p&gt;

&lt;p&gt;La valeur &lt;code&gt;throttledValue&lt;/code&gt; utilisant notre fonction &lt;code&gt;throttle(..)&lt;/code&gt; et basé sur la valeur de &lt;code&gt;clickCount&lt;/code&gt; peut à présent être utilisée et devenir une dépendance pour ainsi permettre l'exécution d'une ou plusieurs fonctions grâce au hook &lt;code&gt;useEffect&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Liens et sources
&lt;/h2&gt;

&lt;p&gt;Les snippets de code inclus dans cet articles peuvent être retrouvés depuis une source accessible depuis un repo. Github (voir ci-dessous).&lt;/p&gt;

&lt;p&gt;Les exemples peuvent être simulés via une démo live hébergé par &lt;strong&gt;CodeSandbox.io&lt;/strong&gt; &lt;em&gt;(lien ci-dessous)&lt;/em&gt;, ainsi, le code incluant les différentes fonctions étudiées est accessible à tou.s.tes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Github Repository&lt;/strong&gt; &lt;em&gt;(Source)&lt;/em&gt;&lt;br&gt;
&lt;a href="https://github.com/MessageGit/debounce-and-throttle-react-hooks"&gt;https://github.com/MessageGit/debounce-and-throttle-react-hooks&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live Demo&lt;/strong&gt; &lt;em&gt;(CodeSandbox.io)&lt;/em&gt;&lt;br&gt;
&lt;a href="https://licvw6-3000.csb.app/"&gt;https://licvw6-3000.csb.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lodash&lt;/strong&gt; &lt;em&gt;(JS Lib. including &lt;strong&gt;Debounce&lt;/strong&gt; and &lt;strong&gt;Throttle&lt;/strong&gt; functions)&lt;/em&gt;&lt;br&gt;
&lt;a href="https://lodash.com/"&gt;https://lodash.com/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>react</category>
    </item>
    <item>
      <title>⭐ Les 10 outils indispensables dans le quotidien d'un(e) developpeu.r.se</title>
      <dc:creator>Grégory CHEVALLIER</dc:creator>
      <pubDate>Tue, 17 Oct 2023 07:05:18 +0000</pubDate>
      <link>https://forem.com/messagegit/les-10-outils-indispensables-dans-le-quotidien-dune-developpeurse-2a6a</link>
      <guid>https://forem.com/messagegit/les-10-outils-indispensables-dans-le-quotidien-dune-developpeurse-2a6a</guid>
      <description>&lt;p&gt;Si l'on peut facilement assimiler le monde du développement à un long parcours abondant de problèmes en tout genre, un véritable arsenal de solutions s'est construit avec le temps pour faciliter le quotidien des développeu.rs.ses.&lt;/p&gt;

&lt;p&gt;Ces solutions sont des outils web, programmes et librairies. Des utilitaires qui s'associent avec vos projets du quotidien et garantissent leur bon développement.&lt;/p&gt;

&lt;p&gt;La plupart de ces utilitaires sont &lt;strong&gt;gratuits d'utilisation&lt;/strong&gt; et vous permettent de gagner un temps considérable ☀️&lt;/p&gt;

&lt;p&gt;Voici 10 utilitaires que j'utilise quotidiennement en tant que développeur 👇&lt;/p&gt;




&lt;h2&gt;
  
  
  ℹ️ Shields.io
&lt;/h2&gt;

&lt;p&gt;Shields.io est un &lt;strong&gt;générateur de badges en ligne&lt;/strong&gt; utilisé par exemple pour compléter les informations liées à un répertoire Github. Il permet notament d'informer quant aux propriétés du projet : &lt;em&gt;Numéro de version, Nombre de pull requests / issues, statut d'un serveur Discord, Suivi des informations liées au dernier build, etc..&lt;/em&gt;&lt;br&gt;
Visitez ce site à l'adresse : &lt;a href="https://shields.io/"&gt;https://shields.io/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🌐 Ngrok
&lt;/h2&gt;

&lt;p&gt;Ngrok est un utilitaire permettant d'&lt;strong&gt;exposer un(e) application/serveur local(e) publiquement sur internet&lt;/strong&gt; &lt;em&gt;(depuis un protocole quel qu'il soit)&lt;/em&gt;. Un sous-domaine vous est dédié et une adresse web est générée. &lt;strong&gt;Ngrok&lt;/strong&gt; permet dès lors à n'importe quel client disposant d'une connexion internet de communiquer avec votre service.&lt;br&gt;
Visitez ce site à l'adresse : &lt;a href="https://ngrok.com/"&gt;https://ngrok.com/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  〽️ Upptime
&lt;/h2&gt;

&lt;p&gt;Upptime est une librairie Github open-source permettant de &lt;strong&gt;suivre l'accessibilité et l'état de vie de vos différentes applications web&lt;/strong&gt; en fournissant un rapport de statistiques via &lt;strong&gt;Github Pages&lt;/strong&gt; et par l'intermédiaire de commits.&lt;/p&gt;

&lt;p&gt;Site de la librairie : &lt;a href="https://upptime.js.org/"&gt;https://upptime.js.org/&lt;/a&gt;&lt;br&gt;
Lien Github : &lt;a href="https://github.com/upptime/upptime"&gt;https://github.com/upptime/upptime&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🐳 Docker
&lt;/h2&gt;

&lt;p&gt;On ne le présente plus. &lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt; est le célèbre utilitaire d'origine 🇲🇫 permettant la conteneurisation de processus : une solution plus légère que la virtualisation qui permet d'empaqueter une application ainsi que ses dépendances dans un conteneur isolé. Le conteneur, une fois configuré, peut ainsi être lancé sur n'importe quel serveur équipé de Docker.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ L'outil Docker Compose permet le déploiement de plusieurs conteneurs via la configuration d'un fichier YAML.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;En bref, &lt;strong&gt;Docker&lt;/strong&gt; exploite et émule les ressources d'OS légers/minifiés pour lancer des processus divers (par exemple : héberger une base de donnée, lancer un serveur Redis, etc..)&lt;/p&gt;

&lt;p&gt;Site de la plateforme : &lt;a href="https://www.docker.com/"&gt;https://www.docker.com/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📋 Notion
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.notion.so/"&gt;Notion&lt;/a&gt; est une application de &lt;strong&gt;prise de notes&lt;/strong&gt; permettant la &lt;strong&gt;création de wikis&lt;/strong&gt;, &lt;strong&gt;agencement de documentations, création de rappels&lt;/strong&gt; etc.. Elle est dédiée à un usage collectif et individuel : aussi bien utile en entreprise que pour soi. Elle est la copie numérique parfaite d'un calepin à toujours avoir sous la main.&lt;/p&gt;

&lt;p&gt;L'application est disponible avec tous les systèmes d'exploitations (y compris mobile).&lt;br&gt;
Site de la plateforme : &lt;a href="https://www.notion.so/"&gt;https://www.notion.so/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🔴 FileZilla
&lt;/h2&gt;

&lt;p&gt;Le logiciel client FileZilla est strictement indispensable dans les missions quotidiennes d'un(e) DevOps. Il permet l'exploitation du protocole FTP (Port 21) ou SFTP via SSH (Port 22). A savoir : le transfert de fichiers depuis différentes machines.&lt;/p&gt;

&lt;p&gt;Une arborescence fluide et concise des répertoires et fichiers est proposée par FileZilla. Le logiciel garantit la bonne exécution de l'intégralité de vos opérations liées au transfert de fichiers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️  FileZilla prend également en charge l'authentification via paire de clés depuis l'agent SSH, dans le cas d'une connexion SFTP.&lt;br&gt;
Site de la plateforme : &lt;a href="https://filezilla-project.org/"&gt;https://filezilla-project.org/ &lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧍Postman
&lt;/h2&gt;

&lt;p&gt;Postman est un logiciel permettant l'exécution de requêtes HTTP(S). Il propose une interface claire et optimisée qui facilite la perception structurelle des données envoyées/reçues autorisant la gestion intégrale des paramètres liées aux requêtes (Ex: headers, cookies, méthode, etc..)&lt;/p&gt;

&lt;p&gt;Le logiciel permet l'&lt;strong&gt;importation/exportation de collections&lt;/strong&gt; personnalisables &lt;strong&gt;au format JSON&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;L'utilisateur peut dès lors :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Référencer ses requêtes&lt;/li&gt;
&lt;li&gt;Lier des exemples de réponse pour chaque requête&lt;/li&gt;
&lt;li&gt;Créer et utiliser des variables d'environnement&lt;/li&gt;
&lt;li&gt;Apporter de la documentation pour chaque requête
...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;[...] Puis publier le fichier &lt;code&gt;.json&lt;/code&gt; sur le dépôt distant d'un projet collectif pour garantir l'accessibilité du schema/mapping lié aux différents end-points de l'API du projet.&lt;br&gt;
Site du logiciel : &lt;a href="https://www.postman.com/"&gt;https://www.postman.com/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🗒️ Dev.to
&lt;/h2&gt;

&lt;p&gt;Dev.to regroupe une communauté de développeu.rs.euses garantissant une veille technologique active quant aux dernières évolutions / tendances. On y trouve quotidiennement : documentation, tips du quotidien, retours d'expérience, synthèses, « cheatsheets », comparaisons techniques, etc..&lt;/p&gt;

&lt;p&gt;La plateforme propose également une newsletter synthétique pour recevoir les dernières publications de la communauté chaque semaine.&lt;br&gt;
Site de la plateforme : &lt;a href="https://dev.to/"&gt;https://dev.to/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🗃️ DevHints.io
&lt;/h2&gt;

&lt;p&gt;DevHints.io est un site répertoriant les différentes syntaxes et conventions liées à chaque language de programmation / framework.&lt;/p&gt;

&lt;p&gt;Ce site est très utile pour les dévelopeu.rs.euses passant régulièrement d'un language à un autre, il permet via des « cheatsheets » pertinentes de se remémorer les bonnes pratiques liées à la syntaxe d'un language de programmation.&lt;/p&gt;

&lt;p&gt;Site de la plateforme : &lt;a href="https://devhints.io/"&gt;https://devhints.io/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  👥 DeepL
&lt;/h2&gt;

&lt;p&gt;DeepL est un traducteur en ligne performant permettant de traduire jusqu'à 31 langues différentes en entrée ou sortie. DeepL est réputé pour la puissance et la pertinence de sa capacité de traduction.&lt;br&gt;
Site de la plateforme : &lt;a href="https://www.deepl.com/translator"&gt;https://www.deepl.com/translator&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tooling</category>
      <category>frontend</category>
      <category>backend</category>
    </item>
    <item>
      <title>⛏ Le scraping, une technologie d'extraction de contenu web</title>
      <dc:creator>Grégory CHEVALLIER</dc:creator>
      <pubDate>Tue, 17 Oct 2023 07:04:13 +0000</pubDate>
      <link>https://forem.com/messagegit/le-scraping-une-technologie-dextraction-de-contenu-web-33d</link>
      <guid>https://forem.com/messagegit/le-scraping-une-technologie-dextraction-de-contenu-web-33d</guid>
      <description>&lt;p&gt;A l'heure où l'on ne dénombre pas moins de 1,8Md de sites mis en ligne sur le Web, la quantité de données qui y transite quotidiennement reste pour le moins difficile à estimer. Certaines données font parfois l'objet de toutes les convoitises et les méthodes employées pour les importer nécessitent parfois un certain niveau d'expertise.&lt;/p&gt;




&lt;h2&gt;
  
  
  Le scraping, qu'est ce que c'est ?
&lt;/h2&gt;

&lt;p&gt;Le scraping (ou "grattage" en français) consiste à extraire des données depuis un site web par l'intermédiaire d'un programme informatique automatisé. Son fonctionnement est étroitement similaire à celui des robots quotidiennement déployés par les moteurs de recherches pour permettre notamment l'indexation des sites web.&lt;/p&gt;

&lt;p&gt;D'un point de vue technique, plusieurs langages de programmation permettent la conception d'un logiciel de scraping, parmi les plus connus, on retrouve Python, Javascript ainsi que de nombreux frameworks facilitant leur utilisation. Pour Javascript : Selenium, PhantomJS et pour Python : BeautifulSoup et Scrapy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Le scraping étape par étape
&lt;/h2&gt;

&lt;p&gt;Le processus commence tout d'abord par l'exécution d'une requête HTTP qui réceptionne le contenu distant d'une page web (autrement dit, le document HTML). Le programme de scraping effectue par la suite un travail dit de "Parsing" (découpage), qui consiste à analyser la structure du code réceptionné depuis les balises qui le composent et prélever les informations essentielles. Une fois les données prélevées, elles pourront ensuite être réunies au sein d'un document texte/json ou d'une base de données, puis ré-utilisées au bon vouloir de celui ou celle qui les détiennent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var webPage = require('webpage')
var page = webPage.create();

page.open('http://phantomjs.org', function (status) {
  var content = page.content;
  console.log('Content: ' + content);
  phantom.exit();
});;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Ci-dessus, un exemple de code permettant l'importation intégrale du contenu de la page web distante au format HTML&lt;br&gt;
Framework: PhantomJS.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;L'un des principaux avantages à automatiser une tâche de scraping est la garantie d'importer une vaste quantité de données "fraîches" en un temps record et, facultativement, de manière quotidienne.&lt;/p&gt;

&lt;p&gt;Certains programmes de scraping sont commercialisés et publiquement déployés comme des services à part entière. L'intérêt de recourir à de telles pratiques varie également selon les besoins et les secteurs d'activités. Globalement, les entreprises utilisent ces données pour analyser leur concurrence ou un secteur qu'elles jugent stratégique. &lt;/p&gt;




&lt;h2&gt;
  
  
  Pourquoi cette pratique est t-elle malgré tout critiquée ?
&lt;/h2&gt;

&lt;p&gt;Le scraping, de manière modérée ou ponctuelle ne représente pas de problème majeur en soi : il permet d'accélérer et automatiser ce qu'un humain aurait réalisé en beaucoup plus de temps en ayant recours au copier/coller. Cependant, il génère à grande échelle un trafic important et indésirable qui crée un ralentissement des sites victimes de ce processus, les utilisateurs légitimes sont alors pénalisés.&lt;/p&gt;

&lt;p&gt;De plus, aucune entreprise ne souhaite retrouver ses données exploitées, et, parfois, abusivement ré-utilisées par une autre entité. On parle alors d'un détournement de traffic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Comment s'en protéger ?
&lt;/h2&gt;

&lt;p&gt;Une multitude de protections, aux niveaux de difficultés techniques variables, peuvent être mises en place pour échapper à ce phénomène.&lt;/p&gt;

&lt;h3&gt;
  
  
  La limitation des requêtes
&lt;/h3&gt;

&lt;p&gt;Cette précaution consiste à identifier chaque visiteur et limiter le nombre de requêtes par seconde à destination du site, au-delà de cette limite, il est possible de le bloquer momentanément voir définitivement en cas d'abus avéré. Cette protection, bien qu'efficace, doit être configurée en bonne et due forme, au risque de bloquer des utilisateurs légitimes et ainsi compromettre l'accessibilité du site.&lt;/p&gt;

&lt;h3&gt;
  
  
  La mise en place d'un captcha.
&lt;/h3&gt;

&lt;p&gt;Il s'agit d'une vérification manuelle ou automatisée permettant de déterminer si le visiteur est bien un humain et non pas un robot. En proposant par exemple une énigme simple à résoudre ou un texte à recopier. Le captcha invisible est aujourd'hui privilégié, il ne force pas l'utilisateur à réaliser d'action et ne cause aucun tort à l'UX du site, mais requiert d'autres méthodes d'identifications (mouvement de souris, scroll sur la page ...) pour déterminer si celui-ci est bel &amp;amp; bien humain.&lt;/p&gt;

&lt;h3&gt;
  
  
  La modification régulière et/ou intempestive des codes de balisage HTML.
&lt;/h3&gt;

&lt;p&gt;Un programme de scrapping, pour l'analyse d'une page, se focalise essentiellement sur la structure statique qui compose le document. De nombreuses méthodes relatives au changement de cette structure permet de rendre caduque la plupart des programmes de scraping. Ainsi, lors de leur exécution, plus rien ne leur permet de savoir où chercher les informations pour lesquelles ils ont été configurés.&lt;/p&gt;

&lt;h3&gt;
  
  
  La mise en images d'informations textuelles sensibles.
&lt;/h3&gt;

&lt;p&gt;Cette option est à éviter autant que possible, les images étant inaccessibles aux utilisateurs malvoyants.&lt;/p&gt;

&lt;p&gt;C'est ainsi que les développeurs s'adonnent à un véritable jeu du chat et de la souris. Et pour cause, les “scrapers“ peuvent malgré tout redoubler d'ingéniosité et trouver des parades aux précautions mentionnées ci-dessus. &lt;/p&gt;

&lt;p&gt;Ils peuvent par exemple :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Recourir (pour les plus motivés) à des technologies d'analyses optique (OCR) pour remplir les captcha ou prélever des informations cachées dans du contenu mis en image.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automatiser l'utilisation de Proxy/VPN pour leur permettre d'exécuter un programme depuis plusieurs IP's différentes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ralentir leurs scripts pour qu'ils restent sous le seuil de limitations des requêtes de votre site et ensuite l'exécuter sur plusieurs machines possédant des IP's différentes.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Que dit la loi ?
&lt;/h2&gt;

&lt;p&gt;Un dernier point reste malgré tout à éclaircir vis à vis de cette pratique, si la mise en service d'un de ces programme ne relève pas d'un manquement direct aux législations en vigueur, qu'en est t-il du seuil de tolérance lié à ces méthodes aux opinions controversées et dans quel cas est t-il outrepassé ?&lt;/p&gt;

&lt;p&gt;L'article 323-3 du Code Pénal indique "Le fait d'introduire frauduleusement des données dans un système de traitement automatisé, d'extraire, de détenir, de reproduire, de transmettre, de supprimer ou de modifier frauduleusement les données qu'il contient est puni de cinq ans d'emprisonnement et de 150 000 € d'amende." le terme "système de traitement automatisé" n'a pas de définition exacte et peux couvrir la majorité des sites.&lt;/p&gt;

&lt;p&gt;Pour finir, l'Article L342-1 du code de la propriété intellectuelle ne manque surtout pas à rappeler qu'un producteur de base de données à le droit d'interdire:&lt;/p&gt;

&lt;p&gt;L'extraction, par transfert permanent ou temporaire de la totalité ou d'une partie qualitativement ou quantitativement substantielle du contenu d'une base de données sur un autre support, par tout moyen et sous toute forme que ce soit.&lt;/p&gt;

&lt;p&gt;La réutilisation, par la mise à la disposition du public de la totalité ou d'une partie qualitativement ou quantitativement substantielle du contenu de la base, quelle qu'en soit la forme.&lt;/p&gt;

&lt;p&gt;Mais aussi que ces droits peuvent être transmis, cédés ou faire l'objet d'une licence. &lt;/p&gt;

</description>
      <category>scraping</category>
      <category>database</category>
      <category>datascience</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
