# Le Guide Node.js par Wishtack

Après 5 ans de développement d'applications, d'animation de formations et d'accompagnement d'entreprises dans leurs développements, nous avons décidé de produire cet ouvrage gratuit afin de partager notre expérience.

## Nos objectifs <a href="#nos-objectifs" id="nos-objectifs"></a>

* Produire **rapidement** des applications **performantes**, **robustes** et **maintenables**.
* Privilégier le **pragmatisme** et mettre l'accent sur les **bonnes pratiques**.
* Partager le fruit de nos heures de **veille**, de **recherche** et de nos **retours d'expérience**.

## Copyright <a href="#copyright" id="copyright"></a>

Ce livre est l'oeuvre et la propriété de la société Wishtack.

Il ne peut être utilisé partiellement ou intégralement comme support de prestations rémunérées sauf par les employés de la société Wishtack.

En cas de doute, merci de contacter l'équipe Wishtack : <contact@wishtack.com>​

![©Wishtack](https://2204762747-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LMff_psU4HTG0nL7muv%2F-LMhxQibTGOIQKk69w9T%2F-LMhxeB7rtoVjeuuBlIa%2Fimage.png?alt=media\&token=af479f3f-d6e1-42a1-86f9-9d0cf39d4e2c)


# Node.js


# Pourquoi Node.js ?

## Ecosystème

* NPM propose des **centaines de milliers de modules** réutilisables.
* Large communauté.
* De nombreuses ressources *(articles, formations, échanges)*.
* Hosting **facile à mettre en oeuvre et peu coûteux** : [Images docker](https://hub.docker.com/_/node/), [Heroku](https://devcenter.heroku.com/articles/getting-started-with-nodejs), [AWS Lambda](https://aws.amazon.com/lambda/), [Google Cloud Functions](https://cloud.google.com/functions/), [OpenWhisk](https://openwhisk.apache.org/).

## Performance

Node.js est particulièrement performant dans un univers data-driven grâce au paradigme asynchrone et single-threaded du JavaScript.

Node.js est idéal pour les applications avec de nombreux échanges I/O *(temps réel, single-page applications, streaming etc...)* mais ce n'est pas forcément le choix optimal pour du traitement CPU gourmand.

## Fullstack

Possibilité de développer en JavaScript du frontend *(e.g.: Angular)* à la data source *(e.g.: MongoDB)*.


# Modules

Un **module Node.js** est **une fonctionnalité** regroupée dans un fichier JavaScript *(ou parfois plusieurs grâce aux fichiers `index.js`)* dans le but d'être réutilisée à différents endroits d'une application.

Chaque **module Node.js** dispose de son propre contexte et ne peut donc interférer avec d'autres modules ou polluer le **global scope**.

## Utilisation d'un module

### Importation d'un module standard

```javascript
const http = require('http');
const dns = require('dns');
```

### Importation d'un module provenant d'un package externe

Il faut tout d'abord installer le package associé...

```bash
yarn add express # ou npm install --save express
```

... puis importer le module tel un module natif.

```javascript
const express = require('express');
```

## Modules "custom"

### Création d'un module simple (un seul fichier)

```javascript
const settings = {
    hostname: 'www.wishtack.com',
    coolMode: true
};

module.exports = settings;
```

### Importation du module

Les modules faisant partie de l'application *(hors modules natifs ou dépendances)* doivent être importés par chemin relatif.

```javascript
const settings = require('./settings');

console.log(settings.coolMode); // true
```

{% hint style="info" %}
Une fois le support des ES Modules (<https://nodejs.org/api/esm.html>) stabilisé, `import` remplacera `require`.
{% endhint %}


# Global objects

Quelques global objects utiles :

* **process :** Informations et interactions avec le process actuel.
* **console :** Affichage d'informations sur la console.
* **module :** Référence vers le module actuel.


# Events & Listeners

## Exemple avec le package IRC

```bash
yarn add irc
```

```javascript
const { Client } = require('irc');

const client = new Client('irc.freenode.net', 'myIrcBot', {
    channels: ['#wishtack']
});

client.on('error', message => console.error(`error: ${message}`));

client.on('connect', () => console.log('connected to the irc server'));

client.on('message', (from, to, message) => {
    console.log(`${from} => ${to}: ${message}`);
});

client.on('pm', (from, message) => console.log(`${from} => ME: ${message}`));
```

## `EventEmitter`

```javascript
const EventEmitter = require('events');
const eventEmitter = new EventEmitter();

/* Listen to 'greetings' events. */
eventEmitter.on('greetings', name => console.log(`Hi ${name}!`));

/* Emit 'hello' event. */
eventEmitter.emit('greetings', 'Wishtack');            
```


# Streams

Pour simplifier l'implémentation asynchrone du traitement d'un flux de données, Node.js propose l'utilisation de la notion de **stream**.

NodeJS définit des interfaces d'implémentation de **stream** : Readable, Writable, Duplex et Transform.

<https://nodejs.org/api/stream.html#stream_api_for_stream_implementors>

{% embed url="<https://nodejs.org/api/stream.html#stream_api_for_stream_implementors>" %}

## NodeJS implémente quelques streams et fonctionnalités de transformation

```javascript
const crypto = require('crypto');
const fs = require('fs');
const zlib = require('zlib');

const password = new Buffer(process.env.PASS || 'password');
const encryptStream = crypto.createCipher('aes-256-cbc', password);

const gzip = zlib.createGzip();
const readStream = fs.createReadStream('secret-data.txt');
const writeStream = fs.createWriteStream('encrypted-secret-data.gz');

/* Read current file. */
readStream

    /* Encrypt data. */
    .pipe(encryptStream)

    /* Compress data. */
    .pipe(gzip)

    /* Write data to output file. */
    .pipe(writeStream)

    /* We are done. */
    .on('finish', function () {
        console.log('done');
    });
```


# Error Management

## Convention Node.js

Si le premier paramètre est non `null` alors une erreur s'est produite et les informations de l'erreur sont dans ce paramètre.

Si le premier paramètre est `null` alors il s'agit d'un succès.

```javascript
fs.readFile('wishtack.txt', function (error, data) {

    if (error !== null) {
        /* Something went wrong. */
        return;
    }

    /* Everything is just fine. */
    ...

});

```

Si l'erreur a une solution alternative *("retry" ou solution de backup)* alors gérez l'erreur immédiatement sinon il faut remonter l'erreur à l'utilisateur.

{% hint style="warning" %}
Aucune erreur ne doit être ignorée.
{% endhint %}

## Détection d'erreur avec l'objet `EventEmitter`

```javascript
const EventEmitter = require('events');

const emitter = new EventEmitter();

emitter.on('error', error => {
    /* Handle error case. */
});
```

{% hint style="danger" %}
Avec un `EventEmitter`, si l'erreur n'est pas capturée, une exception sera levée et interrompra le programme.
{% endhint %}


# Express


# Getting Started

Express est un framework web Node.js performant et **minimaliste**, accompagné d'un large écosystème.

Express fournit tous les outils pour l'implémentation d'applications web et de Restful APIs.

Express fait parti de la stack web **MEAN&#x20;*****(Mongo/Express/Angular/Node)*** actuellement très répandue.

{% hint style="info" %}
Comme indiqué précédemment concernant Node.js, Express nécessite une logique d'implémentation asynchrone. En fonction de vos besoins, il peut être plus judicieux dans certains cas d'utiliser un framework web Python avec [gevent](http://www.gevent.org/) pour implémenter un serveur web asynchrone avec une logique d'implémentation synchrone. 🤭
{% endhint %}

### Installation

```bash
yarn add express
```

### Utilisation

```javascript
const express = require('express');
const app = express();

/* Routing. */
app.get('/', (req, res) => res.send('Welcome to Wishtack!'));

/* Run server and listen on port 3000. */
const server = app.listen(3000, () => {

    const host = server.address().address;
    const port = server.address().port;

    console.log(`App listening on http://${host}:${port}`);
    
});
```

Il suffit ensuite de lancer l'application :

```bash
node app.js
```


# Express Generator

{% hint style="info" %}
Vous n'allez bien sûr pas implémenter l'intégralité de l'application en un seul fichier.
{% endhint %}

Pour initialiser la structure du produit, vous pouvez utiliser le module `express-generator` qu'il vaut mieux installer globalement.

```bash
yarn global add express-generator # or: npm install --global express-generator

# Initialize product with Handlebars templating engine, SASS as a css engine and initialize .gitignore file.
express product-name --hbs --css sass --git

# Install node dependencies.
cd product-name && yarn install
```


# Middlewares

Comme la plupart des frameworks web, Express permet d'utiliser des middlewares tiers ou implémenter des middlewares personnalisés.

## Rôle des middlewares

Les middlewares permettent :

* D'exécuter du code à chaque requête.
* Modifier la requête et/ou la réponse.
* Interrompre la requête.
* Appeler le middleware suivant ou interrompre la chaîne de middlewares.

## Types de middlewares

Il existe plusieurs types de middlewares :

### Application-level

Le middleware est appliqué à l'ensemble des requêtes avec la méthode `app.use` ou sur un type de requêtes en particulier avec les méthode `app.get` ou `app.post` etc... ou encore sur une URL en particulier :

```javascript
app.get('/users/:userId', (req, res, next) => {
    console.log(req.params.userId);
    next();
});
```

### Router-level

Ce middleware fonctionne similairement à l' application-level mais ne s'applique qu'au router en question et donc une partie des URLs.

### Error-handling

Ce middleware permet de capturer uniquement les erreurs. Il s'implémente en ajoutant simplement le paramètre `err` à la fonction du middleware.

```javascript
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('Shit! Something went wrong!');
});
```

### Built-in

Express fournit des middlewares natifs comme `express.static` pour servir les fichiers statiques.

Le middleware `express.static` permet de définir les ressources statiques distribuées par l'application.

```javascript
app.use('/assets', express.static('public'));
```

{% hint style="info" %}
Il reste préférable d'utiliser un CDN *(Content Delivery Network)* pour servir des ressources statiques ou encore mieux : Firebase Hosting <https://firebase.google.com/docs/hosting/>, AWS Cloudfront + S3, Netlify <https://www.netlify.com/>...
{% endhint %}

### Third-party

Tous les middlewares disponibles sur NP&#x4D;**.**


# Routing

Le *routing* définit le **lien** entre les **URLs** d'une application et le **traitement** à effectuer pour chaque requête.

Express permet de définir le *routing* en établissant un lien entre une méthode HTTP, un *path* et la fonction qui sera exécutée à chaque requête.

```javascript
app.METHOD(path, callback);
```

### Exemples

```javascript
app.get('/', (req, res) => res.send('Welcome!'));

/* Activate body parser for both url-encoded and JSON data. */
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());

app.post('/users', (req, res) => {
    console.log(req.body.firstName);
    res.end();
});
```

### Il est possible de configurer le *routing* avec des expressions régulières

```javascript
app.get(/^\/blogs\/(\d+)$/, (req, res) => {
    const blogId = req.params[0];
    ...
});
```

#### APIs des objets `request` et `response`

<http://expressjs.com/en/4x/api.html#req>

{% embed url="<http://expressjs.com/en/4x/api.html#req>" %}

\
<http://expressjs.com/en/4x/api.html#res>

{% embed url="<http://expressjs.com/en/4x/api.html#res>" %}

{% hint style="warning" %}
Express étant asynchrone, si aucune réponse n'est envoyée explicitement au client, la connexion sera maintenue jusqu'au timeout.
{% endhint %}

{% hint style="warning" %}
Certaines fonctions utilisent des callbacks distinctes pour gérer le cas de succès et le cas d'erreur. Si vous oubliez de gérer le cas d'erreur, on tombe dans le cas du "timeout".
{% endhint %}


# Templating

Express est compatible avec de nombreux moteurs de *templating* JavaScript.

Express est fourni avec les moteurs suivants : Jade, EJS et Mustache.

## Jade *(moteur par défaut)*

Une affaire de goût.

```
extends layout

block content

    if data
        div.container
            h1= title
            ul
                each person in data
                li= person.name
```

## EJS *(Embedded JavaScript)*

Syntaxe complexe.

```markup
<%- include head.ejs %>

    <% if(data) { %>
        <div class="container">
            <h1> <%= title %> </h1>
            <ul>
                <% data.forEach(function(person){ %>
                <li title="job: <%= person.job %> status: <%= person.status %>">
                <%= person.name %>
                </li>
                <% }); %>
            </ul>
        </div>
    <% } %>

<%- include foot.ejs %>
```

## JSHTML

```markup
<html>
<head>
    <title>@locals.title</title>
</head>

<body>

<ul class="Task">
@for(var taskIndex = 0, taskCount = locals.taskList.length; taskIndex < taskCount; taskIndex ++){
    var task = locals.taskList[taskIndex];
    <li class="@(taskIndex % 2 ? "Odd" : "Even")">
    <a href="/task/@task.id">@task.name</a>
    </li>
}
</ul>

</body>
</html>
```

## Mustache / Hogan.js.

Pas de précompilation des templates avec *Mustache*.

*Hoga&#x6E;**.**&#x6A;s* implémente les mêmes fonctionnalités que *Mustache* avec la précompilation en plus.

```markup
{{>head}}

{{#hasData}}
<div>
    <h1> {{title}} </h1>
    <ul>
        {{#data}}
        <li title="job: {{job}} status: {{status}}">
            {{name}}
        </li>
        {{/data}}
    </ul>
</div>
{{/hasData}}

{{>foot}}
```

## Handlebars.js

Précompilation et fonctionnalités supplémentaires par rapport à *Mustache.*

```markup
{{#if data}}
<div>
    <h1> {{title}} </h1>
    <ul>
        {{#data}}
        <li title="job: {{job}} status: {{status}}">
            {{name}}
        </li>
        {{/data}}
    </ul>
</div>
{{/if}}
```

<https://www.bearfruit.org/2014/01/20/node-js-template-showdown-5-options-compared/>

{% embed url="<https://www.bearfruit.org/2014/01/20/node-js-template-showdown-5-options-compared/>" %}


# Quelques Liens

<https://github.com/expressjs/body-parser>

{% embed url="<https://github.com/expressjs/body-parser>" %}

<https://express-validator.github.io/docs/schema-validation.html>

{% embed url="<https://express-validator.github.io/docs/schema-validation.html>" %}


# LoopBack

LoopBack est un framework Node.js *(racheté par IBM 😿)* moderne. Il permet :

* de générer **très rapidement** vos APIs à partir de nombreuses sources de données,
* de profiter pleinement de la puissance de TypeScript,
* de **générer automatiquement une description d'API** conforme à la spécification OpenAPI,
* de **générer du code rapidement** grâce à sa CLI,
* de profiter d'un système d'injection de dépendance,
* ...

{% hint style="info" %}
La version 4 est à suivre de près !
{% endhint %}

<https://loopback.io>

{% embed url="<https://loopback.io>" %}


# Testing

L'unit-testing n'est pas optionnel.

Avec un langage dynamiquement typé comme le JavaScript, si la couverture de code par les tests n'est pas suffisante, nous ne sommes même pas à l'abris des erreurs de syntaxe.

{% hint style="warning" %}
Pas de tests unitaires\
\=> pas de refactoring & pas d'update de dépendances\
\=> dette technique & régression perpétuelle\
\=> peur du changement\
\=> vélocité 0
{% endhint %}

{% hint style="info" %}
Les tests unitaires permettent de localiser immédiatement les sources des erreurs.
{% endhint %}

**Les tests unitaires doivent être implémentés en premier.**

<https://guide-agile.wishtack.io/extreme-programming/testing>

{% embed url="<https://guide-agile.wishtack.io/extreme-programming/testing>" %}

La modularité facilite l'implémentation des tests unitaires.

## Les Outils

Il existe plusieurs frameworks et outils pour implémenter les tests unitaires Node.js

Les frameworks les plus utilisés sont Jasmine et Mocha. Ceux-ci étant très similaires, il est recommandé d'utiliser Jasmine qui permet également d'implémenter les tests Angular.

<http://thejsguy.com/2015/01/12/jasmine-vs-mocha-chai-and-sinon.html>

{% embed url="<http://thejsguy.com/2015/01/12/jasmine-vs-mocha-chai-and-sinon.html>" %}

\
<http://jasmine.github.io/>

{% embed url="<http://jasmine.github.io/>" %}

<https://mochajs.org/>

{% embed url="<https://mochajs.org/>" %}

<https://jestjs.io/>

{% embed url="<https://jestjs.io/>" %}

## Jasmine

### Exemple avec Jasmine

```javascript
const cache = require('cache');
const User = require('user');
const Wish = require('wish');

describe('User', () => {

    beforeEach(() => {
        cache.clear();
    });

    afterEach(() => {
        cache.clear();
    });

    it('should add wishes', function() {

        const user = new User();
        const wish = new Wish({title: 'Holidays'});

        /* Add a wish. */
        user.addWish(wish);

        /* Check wishlist. */
        expect(user.wishList().length).toEqual(1);
        expect(user.wishList()[0].title()).toEqual('Holidays');

    });

});
```

### Exécution des tests avec `jasmine-node`

```javascript
yarn add --dev jasmine-node

yarn jasmine-node --autoTest --watchFolders app.js routes views test/unit
```

### Jasmine's Spies

Les *spies* de Jasmine sont une implémentation de *mocks*.

Ils permettent d'implémenter des tests comportementaux. Autrement dit, ils permettent de simuler le comportement d'une partie de code que l'on souhaite exclure du test unitare.

Sans *mocks*, les tests seraient des tests d'intégration et non des tests unitaires.

```javascript
describe('SearchEngine', () => {

    it('should pass locale to third party api', () => {

        /* Spying on `thirdPartySearchApi.search` and faking result. */
        spyOn(thirdPartySearchApi, 'search').and.returnValue([
            {
                title: 'Wishtack - Making Your Wishes Come True',
                url: 'https://www.wishtack.com'
            }
        ]);

        /* Trigger search. */
        searchEngine.search({keywords: 'Wishtack'});

        /* Check spy's call count. */
        expect(thirdPartySearchApi.search.callCount).toBe(1);

        /* Check spy's call args. */
        expect(thirdPartySearchApi.search).toHaveBeenCalledWith({
            country: 'US',
            keywords: 'Wishtack',
            language: 'en'
        });

    });

});
```

{% hint style="warning" %}
Pensez à tester les cas d'erreur !
{% endhint %}

## **HTTP Testing**

Pour tester le *routing* et les interactions avec le serveur, il faut simuler les requêtes à l'aide d'outils comme `supertest`.

**<https://github.com/visionmedia/supertest>**

{% embed url="<https://github.com/visionmedia/supertest>" %}

```javascript
const express = require('express');
const request = require('supertest');

describe('wishes RESTful API', () => {

    it('should return wishes', (done) => {

        request(app)
            .get('/users/123456/wishes/')
            .set('Accept', 'application/json')
            .expect(200, [
                {
                    id: 'abcdef',
                    title: 'Holidays'
                }
            ], done);

    });
    
});
```


# Databases

Node.js permet d'utiliser facilement toutes sortes de *databases* : SQL, NoSQL etc...

<https://github.com/felixge/node-mysql>

{% embed url="<https://github.com/felixge/node-mysql>" %}

<https://github.com/mongodb/node-mongodb-native>

{% embed url="<https://github.com/mongodb/node-mongodb-native>" %}

Pour la plupart des applications, il est préférable d'utiliser une base NoSQL telle que MongoDB pour bénéficier des avantages suivants :

* L'absence de schéma statique permet de s'adapter rapidement aux nouveaux besoins.
* La *scalability* grâce au *sharding*.
* Syntaxe simplifiée.
* Map/Reduce.
* ...

## ORM (Object-Relational Mapping) / ODM (Object-Document Mapping)

Comme dans les autres langages, il est fortement recommandé d'utiliser une couche d'abstraction pour des raisons de factorisation, simplification et sécurité.

### ORM SQL

<http://sequelizejs.com>

{% embed url="<http://sequelizejs.com>" %}

```javascript
const Sequelize = require('sequelize');
const sequelize = new Sequelize('database', 'username', 'password');

const User = sequelize.define('User', {
  firstName: Sequelize.STRING,
  lastName: Sequelize.STRING
});

const main = async () => {

    await sequelize.sync();
    
    const userModel = await User.create({firstName: 'Foo', lastName: 'BAR'});

    const user = user.get({plain: true})

};

main();
```

### ODM MongoDB

<http://mongoosejs.com/>

{% embed url="<http://mongoosejs.com/>" %}

```javascript
const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost/test');

const User = mongoose.model('User', {
    firstName: String,
    lastName: String
});

const foo = new User({
    firstName: 'Foo',
    lastName: 'BAR'
});

await foo.save();
```


# WebSocket

Le protocole WebSocket RFC6455 est standardisé depuis décembre 2011 et implémenté par les navigateurs suivants : Chrome 16, Firefox 11, Internet Explorer 10, Opera 12.10 et Safari 6.

<https://tools.ietf.org/html/rfc6455>

{% embed url="<https://tools.ietf.org/html/rfc6455>" %}

Les WebSockets permettent d'établir une **connexion bi-directionnelle entre le client et le serveur**.

Il devient alors possible d'envoyer des messages en temps réel du client vers le serveur mais surtout du serveur vers le client en utilisant la même connexion.

A titre d'exemple, comparons la bande passante nécessaire pour une application web qui affiche les résultats temps réel d'un match de sport par **polling** puis en utilisant les **WebSockets**.

**Polling :** 1 000 000 d'utilisateurs => 1 000 000 requêtes toutes les 5 secondes => 1KB \* 1 000 000 / 5 => 200 MBP&#x53;**.**

**WebSocket :** 1 000 000 d'utilisateurs => un message serveur/client à chaque évènement *(en moyenne, une fois toutes les 10 secondes)* => 50B \* 1 000 000 / 10 => 5 MBPS.

{% hint style="info" %}
Pour émettre des données du serveur au client, pensez à utiliser les **Server-Sent Events** qui s'avèrent généralement plus simple à mettre en place.

<https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events>
{% endhint %}

Une connexion WebSocket est établie suite au handshake HTTP suivant :

### Requête du client

```http
GET /users/123456/wishes/abcdef HTTP/1.1
Host: www.wishtack.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: comments
Sec-WebSocket-Version: 13
Origin: https://www.wishtack.com
```

### Réponse du serveur

```

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: comments
```

## Socket.IO

<https://socket.io/>

{% embed url="<https://socket.io/>" %}

Socket.IO est une couche d'abstraction des WebSockets.

En l'absence de l'implémentation de WebSockets, Socket.IO peut utiliser du polling par exemple.

Socket.IO fournit également un module pour simplifier l'implémentation des WebSockets côté serveur avec Node.js.

### Code Node.js

```javascript
const express = require('express');
const app = express();
const http = require('http');
const server = http.createServer(app);
const io = require('socket.io')(server);

/* Listen! */
app.listen(80);

/* Messaging. */
io.on('connection', (socket) => {

    socket.on('comment', (data) => {

        console.log(data);

        socket.emit('thanks', 'Thank you for your comment.');

    });

});
```

### Code Client

```javascript
const socket = io.connect('http://localhost/comment');

socket.on('connect', () => socket.emit('message', 'hi!'));

socket.on('thanks', (data) => socket.emit('message', data));
```

### Pensez à utiliser les *rooms* pour diviser les clients en différents groupes

```javascript
io.on('connection', (socket) {

    socket.on('comment', (data) {

        socket.join(socket.handshake.url);

        socket.to(socket.handshake.url).emit('comment', data);

    });

});
```


