Comment configurer un projet backend d'API express avec PostgreSQL

Publié: 2022-03-10
Résumé rapide ↬ Dans cet article, nous allons créer un ensemble de points de terminaison d'API à l'aide d'Express à partir de rien dans la syntaxe ES6 et couvrir certaines bonnes pratiques de développement. Découvrez comment toutes les pièces fonctionnent ensemble lorsque vous créez un petit projet à l'aide de l'intégration continue et du développement piloté par les tests avant le déploiement sur Heroku.

Nous adopterons une approche de développement piloté par les tests (TDD) et la tâche de configuration de l'intégration continue (CI) pour exécuter automatiquement nos tests sur Travis CI et AppVeyor, avec des rapports sur la qualité du code et la couverture. Nous en apprendrons davantage sur les contrôleurs, les modèles (avec PostgreSQL), la gestion des erreurs et le middleware Express asynchrone. Enfin, nous terminerons le pipeline CI/CD en configurant le déploiement automatique sur Heroku.

Cela semble beaucoup, mais ce tutoriel est destiné aux débutants qui sont prêts à s'essayer à un projet back-end avec un certain niveau de complexité, et qui peuvent encore être confus quant à la façon dont toutes les pièces s'emboîtent dans un projet réel. .

Il est robuste sans être écrasant et se décompose en sections que vous pouvez compléter dans un délai raisonnable.

Commencer

La première étape consiste à créer un nouveau répertoire pour le projet et à démarrer un nouveau projet de nœud. Node est requis pour continuer avec ce didacticiel. Si vous ne l'avez pas installé, rendez-vous sur le site officiel, téléchargez-le et installez-le avant de continuer.

J'utiliserai le fil comme gestionnaire de paquets pour ce projet. Vous trouverez ici des instructions d'installation pour votre système d'exploitation spécifique. N'hésitez pas à utiliser npm si vous le souhaitez.

Plus après saut! Continuez à lire ci-dessous ↓

Ouvrez votre terminal, créez un nouveau répertoire et démarrez un projet Node.js.

 # create a new directory mkdir express-api-template # change to the newly-created directory cd express-api-template # initialize a new Node.js project npm init

Répondez aux questions qui suivent pour générer un fichier package.json . Ce fichier contient des informations sur votre projet. Un exemple de ces informations inclut les dépendances utilisées, la commande pour démarrer le projet, etc.

Vous pouvez maintenant ouvrir le dossier du projet dans l'éditeur de votre choix. J'utilise le code Visual Studio. C'est un IDE gratuit avec des tonnes de plugins pour vous faciliter la vie, et il est disponible pour toutes les principales plates-formes. Vous pouvez le télécharger sur le site officiel.

Créez les fichiers suivants dans le dossier du projet :

  • LISEZMOI.md
  • .editorconfig

Voici une description de ce que fait .editorconfig sur le site Web EditorConfig. (Vous n'en avez probablement pas besoin si vous travaillez en solo, mais cela ne fait pas de mal, donc je vais le laisser ici.)

"EditorConfig aide à maintenir des styles de codage cohérents pour plusieurs développeurs travaillant sur le même projet dans différents éditeurs et IDE."

Ouvrez .editorconfig et collez le code suivant :

 root = true [*] indent_style = space indent_size = 2 charset = utf-8 trim_trailing_whitespace = false insert_final_newline = true

Le [*] signifie que nous voulons appliquer les règles qui en découlent à chaque fichier du projet. Nous voulons une taille d'indentation de deux espaces et un jeu UTF-8 . Nous souhaitons également supprimer les espaces blancs de fin et insérer une dernière ligne vide dans notre fichier.

Ouvrez README.md et ajoutez le nom du projet comme élément de premier niveau.

 # Express API template

Ajoutons tout de suite le contrôle de version.

 # initialize the project folder as a git repository git init

Créez un fichier .gitignore et saisissez les lignes suivantes :

 node_modules/ yarn-error.log .env .nyc_output coverage build/

Ce sont tous les fichiers et dossiers que nous ne voulons pas suivre. Nous ne les avons pas encore dans notre projet, mais nous les verrons au fur et à mesure.

À ce stade, vous devriez avoir la structure de dossiers suivante.

 EXPRESS-API-TEMPLATE ├── .editorconfig ├── .gitignore ├── package.json └── README.md

Je considère que c'est un bon point pour valider mes modifications et les pousser vers GitHub.

Commencer un nouveau projet express

Express est un framework Node.js pour la création d'applications Web. Selon le site officiel, il s'agit d'un

Framework Web rapide, sans opinion et minimaliste pour Node.js.

Il existe d'autres excellents frameworks d'applications Web pour Node.js, mais Express est très populaire, avec plus de 47 000 étoiles GitHub au moment de la rédaction de cet article.

Dans cet article, nous n'aurons pas beaucoup de discussions sur toutes les parties qui composent Express. Pour cette discussion, je vous recommande de consulter la série de Jamie. La première partie est ici, et la deuxième partie est ici.

Installez Express et démarrez un nouveau projet Express. Il est possible de configurer manuellement un serveur Express à partir de zéro, mais pour nous faciliter la vie, nous utiliserons le générateur express pour configurer le squelette de l'application.

 # install the express generator globally yarn global add express-generator # install express yarn add express # generate the express project in the current folder express -f

L'indicateur -f force Express à créer le projet dans le répertoire courant.

Nous allons maintenant effectuer quelques opérations de ménage.

  1. Supprimez le fichier index/users.js .
  2. Supprimez les dossiers public/ et views/ .
  3. Renommez le fichier bin/www en bin/www.js .
  4. Désinstallez jade avec la commande yarn remove jade .
  5. Créez un nouveau dossier nommé src/ et déplacez-y les éléments suivants : 1. fichier app.js 2. bin/ dossier 3. routes/ dossier à l'intérieur.
  6. Ouvrez package.json et mettez à jour le script de start pour ressembler à ci-dessous.
 "start": "node ./src/bin/www"

À ce stade, la structure de votre dossier de projet ressemble à celle ci-dessous. Vous pouvez voir comment VS Code met en évidence les modifications de fichiers qui ont eu lieu.

 EXPRESS-API-TEMPLATE ├── node_modules ├── src | ├── bin │ │ ├── www.js │ ├── routes │ | ├── index.js │ └── app.js ├── .editorconfig ├── .gitignore ├── package.json ├── README.md └── yarn.lock

Ouvrez src/app.js et remplacez le contenu par le code ci-dessous.

 var logger = require('morgan'); var express = require('express'); var cookieParser = require('cookie-parser'); var indexRouter = require('./routes/index'); var app = express(); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(cookieParser()); app.use('/v1', indexRouter); module.exports = app;

Après avoir demandé certaines bibliothèques, nous demandons à Express de gérer chaque requête provenant de /v1 avec indexRouter .

Remplacez le contenu de routes/index.js par le code ci-dessous :

 var express = require('express'); var router = express.Router(); router.get('/', function(req, res, next) { return res.status(200).json({ message: 'Welcome to Express API template' }); }); module.exports = router;

Nous récupérons Express, créons un routeur à partir de celui-ci et servons la route / , qui renvoie un code d'état de 200 et un message JSON.

Démarrez l'application avec la commande ci-dessous :

 # start the app yarn start

Si vous avez tout configuré correctement, vous ne devriez voir que $ node ./src/bin/www dans votre terminal.

Visitez https://localhost:3000/v1 dans votre navigateur. Vous devriez voir le message suivant :

 { "message": "Welcome to Express API template" }

C'est un bon point pour commiter nos modifications.

  • La branche correspondante dans mon dépôt est 01-install-express.

Conversion de notre code ES6

Le code généré par express-generator est en ES5 , mais dans cet article, nous allons écrire tout notre code en syntaxe ES6 . Convertissons donc notre code existant en ES6 .

Remplacez le contenu de routes/index.js par le code ci-dessous :

 import express from 'express'; const indexRouter = express.Router(); indexRouter.get('/', (req, res) => res.status(200).json({ message: 'Welcome to Express API template' }) ); export default indexRouter;

C'est le même code que nous avons vu ci-dessus, mais avec l'instruction d'importation et une fonction de flèche dans le gestionnaire de / route.

Remplacez le contenu de src/app.js par le code ci-dessous :

 import logger from 'morgan'; import express from 'express'; import cookieParser from 'cookie-parser'; import indexRouter from './routes/index'; const app = express(); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(cookieParser()); app.use('/v1', indexRouter); export default app;

Voyons maintenant le contenu de src/bin/www.js . Nous allons le construire progressivement. Supprimez le contenu de src/bin/www.js et collez-le dans le bloc de code ci-dessous.

 #!/usr/bin/env node /** * Module dependencies. */ import debug from 'debug'; import http from 'http'; import app from '../app'; /** * Normalize a port into a number, string, or false. */ const normalizePort = val => { const port = parseInt(val, 10); if (Number.isNaN(port)) { // named pipe return val; } if (port >= 0) { // port number return port; } return false; }; /** * Get port from environment and store in Express. */ const port = normalizePort(process.env.PORT || '3000'); app.set('port', port); /** * Create HTTP server. */ const server = http.createServer(app); // next code block goes here

Ce code vérifie si un port personnalisé est spécifié dans les variables d'environnement. Si aucun n'est défini, la valeur de port par défaut de 3000 est définie sur l'instance de l'application, après avoir été normalisée en chaîne ou en nombre par normalizePort . Le serveur est ensuite créé à partir du module http , avec app comme fonction de rappel.

La ligne de #!/usr/bin/env node est facultative puisque nous spécifierions node lorsque nous voulons exécuter ce fichier. Mais assurez-vous qu'il se trouve sur la ligne 1 du fichier src/bin/www.js ou supprimez-le complètement.

Examinons la fonction de gestion des erreurs. Copiez et collez ce bloc de code après la ligne où le serveur est créé.

 /** * Event listener for HTTP server "error" event. */ const onError = error => { if (error.syscall !== 'listen') { throw error; } const bind = typeof port === 'string' ? `Pipe ${port}` : `Port ${port}`; // handle specific listen errors with friendly messages switch (error.code) { case 'EACCES': alert(`${bind} requires elevated privileges`); process.exit(1); break; case 'EADDRINUSE': alert(`${bind} is already in use`); process.exit(1); break; default: throw error; } }; /** * Event listener for HTTP server "listening" event. */ const onListening = () => { const addr = server.address(); const bind = typeof addr === 'string' ? `pipe ${addr}` : `port ${addr.port}`; debug(`Listening on ${bind}`); }; /** * Listen on provided port, on all network interfaces. */ server.listen(port); server.on('error', onError); server.on('listening', onListening);

La fonction onError écoute les erreurs dans le serveur http et affiche les messages d'erreur appropriés. La fonction onListening sort simplement le port sur lequel le serveur écoute sur la console. Enfin, le serveur écoute les demandes entrantes à l'adresse et au port spécifiés.

À ce stade, tout notre code existant est dans la syntaxe ES6 . Arrêtez votre serveur (utilisez Ctrl + C ) et lancez yarn start . Vous obtiendrez une erreur SyntaxError: Invalid or unexpected token . Cela se produit parce que Node (au moment de la rédaction) ne prend pas en charge certaines des syntaxes que nous avons utilisées dans notre code.

Nous allons maintenant corriger cela dans la section suivante.

Configuration des dépendances de développement : babel , nodemon , eslint et prettier

Il est temps de mettre en place la plupart des scripts dont nous aurons besoin à cette phase du projet.

Installez les bibliothèques requises avec les commandes ci-dessous. Vous pouvez simplement tout copier et le coller dans votre terminal. Les lignes de commentaire seront ignorées.

 # install babel scripts yarn add @babel/cli @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/register @babel/runtime @babel/node --dev

Cela installe tous les scripts babel répertoriés en tant que dépendances de développement. Vérifiez votre fichier package.json et vous devriez voir une section devDependencies . Tous les scripts installés y seront listés.

Les scripts babel que nous utilisons sont expliqués ci-dessous :

@babel/cli Une installation requise pour utiliser babel . Il permet l'utilisation de Babel depuis le terminal et est disponible sous ./node_modules/.bin/babel .
@babel/core Fonctionnalité de base de Babel. Il s'agit d'une installation requise.
@babel/node Cela fonctionne exactement comme la CLI Node.js, avec l'avantage supplémentaire de compiler avec des préréglages et des plugins babel . Ceci est requis pour une utilisation avec nodemon .
@babel/plugin-transform-runtime Cela permet d'éviter la duplication dans la sortie compilée.
@babel/preset-env Une collection de plugins chargés d'effectuer des transformations de code.
@babel/register Cela compile les fichiers à la volée et est spécifié comme une exigence lors des tests.
@babel/runtime Cela fonctionne en conjonction avec @babel/plugin-transform-runtime .

Créez un fichier nommé .babelrc à la racine de votre projet et ajoutez le code suivant :

 { "presets": ["@babel/preset-env"], "plugins": ["@babel/transform-runtime"] }

nodemon

 # install nodemon yarn add nodemon --dev

nodemon est une bibliothèque qui surveille le code source de notre projet et redémarre automatiquement notre serveur chaque fois qu'il observe des changements.

Créez un fichier nommé nodemon.json à la racine de votre projet et ajoutez le code ci-dessous :

 { "watch": [ "package.json", "nodemon.json", ".eslintrc.json", ".babelrc", ".prettierrc", "src/" ], "verbose": true, "ignore": ["*.test.js", "*.spec.js"] }

La clé watch indique à nodemon quels fichiers et dossiers surveiller les modifications. Ainsi, chaque fois que l'un de ces fichiers change, nodemon redémarre le serveur. La clé ignore indique aux fichiers de ne pas surveiller les modifications.

Maintenant, mettez à jour la section scripts de votre fichier package.json pour qu'elle ressemble à ceci :

 # build the content of the src folder "prestart": "babel ./src --out-dir build" # start server from the build folder "start": "node ./build/bin/www" # start server in development mode "startdev": "nodemon --exec babel-node ./src/bin/www"
  1. prestart scripts de pré-démarrage construisent le contenu du dossier src/ et le placent dans le dossier build/ . Lorsque vous lancez la commande yarn start , ce script s'exécute avant le script de start .
  2. Le script de start sert maintenant le contenu du dossier build/ au lieu du dossier src/ que nous servions auparavant. Il s'agit du script que vous utiliserez lors de la diffusion du fichier en production. En fait, des services comme Heroku exécutent automatiquement ce script lors du déploiement.
  3. yarn startdev est utilisé pour démarrer le serveur pendant le développement. À partir de maintenant, nous utiliserons ce script pour développer l'application. Notez que nous utilisons maintenant babel-node pour exécuter l'application au lieu de node normal. Le drapeau --exec force babel-node à servir le dossier src/ . Pour le script de start , nous utilisons node car les fichiers du dossier build/ ont été compilés dans ES5.

Lancez yarn startdev et visitez https://localhost:3000/v1. Votre serveur devrait être à nouveau opérationnel.

La dernière étape de cette section consiste à configurer ESLint et prettier . ESLint aide à appliquer les règles de syntaxe tandis que plus joli aide à formater correctement notre code pour la lisibilité.

Ajoutez les deux avec la commande ci-dessous. Vous devez l'exécuter sur un terminal séparé tout en observant le terminal sur lequel notre serveur s'exécute. Vous devriez voir le serveur redémarrer. C'est parce que nous surveillons le fichier package.json pour les changements.

 # install elsint and prettier yarn add eslint eslint-config-airbnb-base eslint-plugin-import prettier --dev

Créez maintenant le fichier .eslintrc.json à la root du projet et ajoutez le code ci-dessous :

 { "env": { "browser": true, "es6": true, "node": true, "mocha": true }, "extends": ["airbnb-base"], "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" }, "parserOptions": { "ecmaVersion": 2018, "sourceType": "module" }, "rules": { "indent": ["warn", 2], "linebreak-style": ["error", "unix"], "quotes": ["error", "single"], "semi": ["error", "always"], "no-console": 1, "comma-dangle": [0], "arrow-parens": [0], "object-curly-spacing": ["warn", "always"], "array-bracket-spacing": ["warn", "always"], "import/prefer-default-export": [0] } }

Ce fichier définit principalement certaines règles par rapport auxquelles eslint vérifiera notre code. Vous pouvez voir que nous étendons les règles de style utilisées par Airbnb.

Dans la section "rules" , nous définissons si eslint doit afficher un avertissement ou une erreur lorsqu'il rencontre certaines violations. Par exemple, il affiche un message d'avertissement sur notre terminal pour toute indentation qui n'utilise pas 2 espaces. Une valeur de [0] désactive une règle, ce qui signifie que nous n'obtiendrons pas d'avertissement ou d'erreur si nous enfreignons cette règle.

Créez un fichier nommé .prettierrc et ajoutez le code ci-dessous :

 { "trailingComma": "es5", "tabWidth": 2, "semi": true, "singleQuote": true }

Nous définissons une largeur de tabulation de 2 et imposons l'utilisation de guillemets simples dans toute notre application. Consultez le guide plus joli pour plus d'options de style.

Ajoutez maintenant les scripts suivants à votre package.json :

 # add these one after the other "lint": "./node_modules/.bin/eslint ./src" "pretty": "prettier --write '**/*.{js,json}' '!node_modules/**'" "postpretty": "yarn lint --fix"

Exécutez yarn lint . Vous devriez voir un certain nombre d'erreurs et d'avertissements dans la console.

La pretty commande embellit notre code. La commande postpretty est exécutée immédiatement après. Il exécute la commande lint avec l'indicateur --fix ajouté. Cet indicateur indique à ESLint de résoudre automatiquement les problèmes courants de peluchage. De cette façon, j'exécute principalement la commande yarn pretty sans me soucier de la commande lint .

Exécutez yarn pretty . Vous devriez voir que nous n'avons que deux avertissements sur la présence d' alert dans le fichier bin/www.js .

Voici à quoi ressemble notre structure de projet à ce stade.

 EXPRESS-API-TEMPLATE ├── build ├── node_modules ├── src | ├── bin │ │ ├── www.js │ ├── routes │ | ├── index.js │ └── app.js ├── .babelrc ├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── nodemon.json ├── package.json ├── README.md └── yarn.lock

Vous pouvez constater que vous avez un fichier supplémentaire, yarn-error.log à la racine de votre projet. Ajoutez-le au fichier .gitignore . Validez vos modifications.

  • La branche correspondante à ce stade de mon dépôt est 02-dev-dependencies.

Paramètres et variables d'environnement dans notre fichier .env

Dans presque tous les projets, vous aurez besoin d'un endroit pour stocker les paramètres qui seront utilisés dans votre application, par exemple une clé secrète AWS. Nous stockons ces paramètres en tant que variables d'environnement. Cela les éloigne des regards indiscrets et nous pouvons les utiliser dans notre application au besoin.

J'aime avoir un fichier settings.js avec lequel je lis toutes mes variables d'environnement. Ensuite, je peux me référer au fichier de paramètres depuis n'importe où dans mon application. Vous êtes libre de nommer ce fichier comme vous le souhaitez, mais il existe une sorte de consensus sur le fait de nommer ces fichiers settings.js ou config.js .

Pour nos variables d'environnement, nous les conserverons dans un fichier .env et les lirons dans notre fichier de settings à partir de là.

Créez le fichier .env à la racine de votre projet et saisissez la ligne ci-dessous :

 TEST_ENV_VARIABLE="Environment variable is coming across"

Pour pouvoir lire les variables d'environnement dans notre projet, il existe une belle bibliothèque, dotenv qui lit notre fichier .env et nous donne accès aux variables d'environnement définies à l'intérieur. Installons-le.

 # install dotenv yarn add dotenv

Ajoutez le fichier .env à la liste des fichiers surveillés par nodemon .

Maintenant, créez le fichier settings.js dans le dossier src/ et ajoutez le code ci-dessous :

 import dotenv from 'dotenv'; dotenv.config(); export const testEnvironmentVariable = process.env.TEST_ENV_VARIABLE;

Nous importons le package dotenv et appelons sa méthode de configuration. Nous exportons ensuite le testEnvironmentVariable que nous avons défini dans notre fichier .env .

Ouvrez src/routes/index.js et remplacez le code par celui ci-dessous.

 import express from 'express'; import { testEnvironmentVariable } from '../settings'; const indexRouter = express.Router(); indexRouter.get('/', (req, res) => res.status(200).json({ message: testEnvironmentVariable })); export default indexRouter;

La seule modification que nous avons apportée ici est que nous importons testEnvironmentVariable à partir de notre fichier de settings et que nous l'utilisons comme message de retour pour une requête de la route / .

Visitez https://localhost:3000/v1 et vous devriez voir le message, comme indiqué ci-dessous.

 { "message": "Environment variable is coming across." }

Et c'est tout. Désormais, nous pouvons ajouter autant de variables d'environnement que nous le souhaitons et nous pouvons les exporter depuis notre fichier settings.js .

C'est un bon point pour valider votre code. N'oubliez pas d'embellir et de pelucher votre code.

  • La branche correspondante sur mon repo est 03-env-variables.

Écrire notre premier test

Il est temps d'intégrer les tests dans notre application. Les tests sont l'une des choses qui donnent confiance au développeur dans son code. Je suis sûr que vous avez vu d'innombrables articles sur le Web prêchant le développement piloté par les tests (TDD). On ne saurait trop insister sur le fait que votre code nécessite des tests. TDD est très facile à suivre lorsque vous travaillez avec Express.js.

Dans nos tests, nous effectuerons des appels à nos points de terminaison API et vérifierons si ce qui est renvoyé correspond à ce que nous attendons.

Installez les dépendances requises :

 # install dependencies yarn add mocha chai nyc sinon-chai supertest coveralls --dev

Chacune de ces bibliothèques a son propre rôle à jouer dans nos tests.

mocha testeur
chai utilisé pour faire des affirmations
nyc recueillir le rapport de couverture des tests
sinon-chai étend les affirmations de chai
supertest utilisé pour effectuer des appels HTTP vers nos points de terminaison API
coveralls pour télécharger la couverture de test sur coveralls.io

Créez un nouveau dossier test/ à la racine de votre projet. Créez deux fichiers dans ce dossier :

  • test/setup.js
  • test/index.test.js

Mocha trouvera le dossier test/ automatiquement.

Ouvrez test/setup.js et collez le code ci-dessous. Il s'agit simplement d'un fichier d'aide qui nous aide à organiser toutes les importations dont nous avons besoin dans nos fichiers de test.

 import supertest from 'supertest'; import chai from 'chai'; import sinonChai from 'sinon-chai'; import app from '../src/app'; chai.use(sinonChai); export const { expect } = chai; export const server = supertest.agent(app); export const BASE_URL = '/v1';

C'est comme un fichier de paramètres, mais pour nos tests. De cette façon, nous n'avons pas à tout initialiser dans chacun de nos fichiers de test. Nous importons donc les packages nécessaires et exportons ce que nous avons initialisé — que nous pouvons ensuite importer dans les fichiers qui en ont besoin.

Ouvrez index.test.js et collez le code de test suivant.

 import { expect, server, BASE_URL } from './setup'; describe('Index page test', () => { it('gets base url', done => { server .get(`${BASE_URL}/`) .expect(200) .end((err, res) => { expect(res.status).to.equal(200); expect(res.body.message).to.equal( 'Environment variable is coming across.' ); done(); }); }); });

Ici, nous faisons une demande pour obtenir le point de terminaison de base, qui est / et affirmons que le res. L'objet body a une clé de message avec une valeur de Environment variable is coming across.

Si vous n'êtes pas familier avec le modèle describe , it , je vous encourage à jeter un coup d'œil au document "Getting Started" de Mocha.

Ajoutez la commande test à la section scripts de package.json .

 "test": "nyc --reporter=html --reporter=text --reporter=lcov mocha -r @babel/register"

Ce script exécute notre test avec nyc et génère trois types de rapport de couverture : un rapport HTML, sorti dans le dossier coverage/ ; un rapport texte envoyé au terminal et un rapport lcov envoyé au dossier .nyc_output/ .

Exécutez maintenant le yarn test . Vous devriez voir un rapport texte dans votre terminal, tout comme celui de la photo ci-dessous.

Rapport de couverture de test ( Grand aperçu )

Notez que deux dossiers supplémentaires sont générés :

  • .nyc_output/
  • coverage/

Regardez à l'intérieur .gitignore et vous verrez que nous ignorons déjà les deux. Je vous encourage à ouvrir coverage/index.html dans un navigateur et à afficher le rapport de test pour chaque fichier.

C'est un bon point pour valider vos modifications.

  • La branche correspondante dans mon référentiel est 04-first-test.

Intégration Continue (CD) Et Badges : Travis, Combinaisons, Code Climate, AppVeyor

Il est maintenant temps de configurer les outils d'intégration et de déploiement continus (CI/CD). Nous allons configurer des services communs tels que travis-ci , coveralls , AppVeyor et codeclimate et ajouter des badges à notre fichier README.

Commençons.

Travis CI

Travis CI est un outil qui exécute nos tests automatiquement chaque fois que nous transmettons un commit à GitHub (et récemment, Bitbucket) et chaque fois que nous créons une pull request. Ceci est surtout utile lors de demandes d'extraction en nous montrant si notre nouveau code a cassé l'un de nos tests.

  1. Visitez travis-ci.com ou travis-ci.org et créez un compte si vous n'en avez pas. Vous devez vous inscrire avec votre compte GitHub.
  2. Survolez la flèche déroulante à côté de votre photo de profil et cliquez sur settings .
  3. Sous l'onglet Repositories , cliquez sur Manage repositories on Github pour être redirigé vers Github.
  4. Sur la page GitHub, faites défiler jusqu'à Repository access et cochez la case à côté de Only select repositories .
  5. Cliquez sur le menu déroulant Select repositories et recherchez le référentiel express-api-template . Cliquez dessus pour l'ajouter à la liste des référentiels que vous souhaitez ajouter à travis-ci .
  6. Cliquez sur Approve and install et attendez d'être redirigé vers travis-ci .
  7. En haut de la page du référentiel, près du nom du référentiel, cliquez sur l'icône de build unknown . Dans le modal Status Image, sélectionnez markdown dans la liste déroulante des formats.
  8. Copiez le code résultant et collez-le dans votre fichier README.md .
  9. Sur la page du projet, cliquez sur More options > Settings . Sous la section Environment Variables , ajoutez la variable d'environnement TEST_ENV_VARIABLE . Lorsque vous entrez sa valeur, assurez-vous de l'avoir entre guillemets doubles comme ceci "Environment variable is coming across."
  10. Créez un fichier .travis.yml à la racine de votre projet et collez-y le code ci-dessous (nous définirons la valeur de CC_TEST_REPORTER_ID dans la section Code Climate).
 language: node_js env: global: - CC_TEST_REPORTER_ID=get-this-from-code-climate-repo-page matrix: include: - node_js: '12' cache: directories: [node_modules] install: yarn after_success: yarn coverage before_script: - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - chmod +x ./cc-test-reporter - ./cc-test-reporter before-build script: - yarn test after_script: - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESUL

Tout d'abord, nous disons à Travis d'exécuter notre test avec Node.js, puis définissons la variable d'environnement globale CC_TEST_REPORTER_ID (nous y reviendrons dans la section Code Climate). Dans la section matrix , nous disons à Travis d'exécuter nos tests avec Node.js v12. Nous voulons également mettre en cache le node_modules/ afin qu'il n'ait pas à être régénéré à chaque fois.

Nous installons nos dépendances à l'aide de la commande yarn qui est un raccourci pour yarn install . Les commandes before_script et after_script sont utilisées pour télécharger les résultats de couverture vers codeclimate . Nous configurerons codeclimate peu. Une fois le yarn test avec succès, nous souhaitons également exécuter yarn coverage qui téléchargera notre rapport de couverture sur coveralls.io.

Combinaisons

Coveralls télécharge les données de couverture de test pour une visualisation facile. Nous pouvons afficher la couverture de test sur notre machine locale à partir du dossier de couverture, mais Coveralls la rend disponible en dehors de notre machine locale.

  1. Visitez coveralls.io et connectez-vous ou inscrivez-vous avec votre compte Github.
  2. Survolez le côté gauche de l'écran pour afficher le menu de navigation. Cliquez sur ADD REPOS .
  3. Recherchez le express-api-template et activez la couverture à l'aide du bouton bascule sur le côté gauche. Si vous ne le trouvez pas, cliquez sur SYNC REPOS dans le coin supérieur droit et réessayez. Notez que votre dépôt doit être public, sauf si vous avez un compte PRO.
  4. Cliquez sur détails pour accéder à la page des détails du dépôt.
  5. Créez le fichier .coveralls.yml à la racine de votre projet et entrez le code ci-dessous. Pour obtenir le repo_token , cliquez sur les détails du repo. Vous le trouverez facilement sur cette page. Vous pouvez simplement faire une recherche de navigateur pour repo_token .
 repo_token: get-this-from-repo-settings-on-coveralls.io

Ce jeton mappe vos données de couverture à un référentiel sur Coveralls. Maintenant, ajoutez la commande de coverage à la section des scripts de votre fichier package.json :

 "coverage": "nyc report --reporter=text-lcov | coveralls"

Cette commande télécharge le rapport de couverture dans le dossier .nyc_output vers coveralls.io. Allumez votre connexion Internet et exécutez :

 yarn coverage

Cela devrait télécharger le rapport de couverture existant sur les combinaisons. Actualisez la page de dépôt sur les combinaisons pour voir le rapport complet.

Sur la page de détails, faites défiler vers le bas pour trouver la section BADGE YOUR REPO . Cliquez sur le menu déroulant EMBED et copiez le code de démarquage et collez-le dans votre fichier README .

Code Climat

Code Climate est un outil qui nous aide à mesurer la qualité du code. Il nous montre les métriques de maintenance en vérifiant notre code par rapport à certains modèles définis. Il détecte des éléments tels que les répétitions inutiles et les boucles for profondément imbriquées. Il collecte également des données de couverture de test, tout comme coveralls.io.

  1. Rendez-vous sur codeclimate.com et cliquez sur « Sign up with GitHub ». Connectez vous si vous possédez déjà un compte.
  2. Une fois dans votre tableau de bord, cliquez sur Add a repository .
  3. Recherchez le express-api-template dans la liste et cliquez sur Add Repo .
  4. Attendez que la construction soit terminée et redirigez vers le tableau de bord du référentiel.
  5. Sous Codebase Summary , cliquez sur Test Coverage . Dans le menu Test coverage , copiez l' TEST REPORTER ID et collez-le dans votre .travis.yml comme valeur de CC_TEST_REPORTER_ID .
  6. Toujours sur la même page, dans la navigation de gauche, sous EXTRAS , cliquez sur Badges. Copiez les badges de maintainability et test coverage au format Markdown et collez-les dans votre fichier README.md .

Il est important de noter qu'il existe deux manières de configurer les contrôles de maintenabilité. Certains paramètres par défaut sont appliqués à chaque référentiel, mais si vous le souhaitez, vous pouvez fournir un fichier .codeclimate.yml à la racine de votre projet. J'utiliserai les paramètres par défaut, que vous pouvez trouver sous l'onglet Maintainability de la page des paramètres du référentiel. Je vous encourage à jeter un œil au moins. Si vous souhaitez toujours configurer vos propres paramètres, ce guide vous donnera toutes les informations dont vous avez besoin.

AppVeyor

AppVeyor et Travis CI sont tous deux des testeurs automatisés. La principale différence est que travis-ci exécute des tests dans un environnement Linux tandis qu'AppVeyor exécute des tests dans un environnement Windows. Cette section est incluse pour montrer comment démarrer avec AppVeyor.

  • Visitez AppVeyor et connectez-vous ou inscrivez-vous.
  • Sur la page suivante, cliquez sur NEW PROJECT .
  • Dans la liste des référentiels, recherchez le express-api-template . Passez la souris dessus et cliquez sur ADD .
  • Cliquez sur l'onglet Settings . Cliquez sur Environment dans la navigation de gauche. Ajoutez TEST_ENV_VARIABLE et sa valeur. Cliquez sur "Enregistrer" en bas de la page.
  • Créez le fichier appveyor.yml à la racine de votre projet et collez-y le code ci-dessous.
 environment: matrix: - nodejs_version: "12" install: - yarn test_script: - yarn test build: off

Ce code demande à AppVeyor d'exécuter nos tests à l'aide de Node.js v12. Nous installons ensuite nos dépendances de projet avec la commande yarn . test_script spécifie la commande pour exécuter notre test. La dernière ligne indique à AppVeyor de ne pas créer de dossier de construction.

Cliquez sur l'onglet Settings . Dans la navigation de gauche, cliquez sur les badges. Copiez le code Markdown et collez-le dans votre fichier README.md .

Validez votre code et poussez vers GitHub. Si vous avez tout fait comme indiqué, tous les tests devraient réussir et vous devriez voir vos nouveaux badges brillants comme indiqué ci-dessous. Vérifiez à nouveau que vous avez défini les variables d'environnement sur Travis et AppVeyor.

Dépôt des badges CI/CD. ( Grand aperçu )

C'est maintenant le bon moment pour valider nos changements.

  • La branche correspondante dans mon dépôt est 05-ci.

Ajout d'un contrôleur

Actuellement, nous traitons la requête GET à l'URL racine, /v1 , à l'intérieur de src/routes/index.js . Cela fonctionne comme prévu et il n'y a rien de mal à cela. Cependant, à mesure que votre application se développe, vous souhaitez garder les choses en ordre. Vous voulez que les préoccupations soient séparées - vous voulez une séparation claire entre le code qui gère la demande et le code qui génère la réponse qui sera renvoyée au client. Pour y parvenir, nous écrivons controllers . Les contrôleurs sont simplement des fonctions qui gèrent les requêtes provenant d'une URL particulière.

Pour commencer, créez un dossier controllers/ dans le dossier src/ . Les controllers internes créent deux fichiers : index.js et home.js . Nous exporterions nos fonctions depuis index.js . Vous pouvez nommer home.js comme vous le souhaitez, mais vous souhaitez généralement nommer les contrôleurs d'après ce qu'ils contrôlent. Par exemple, vous pouvez avoir un fichier usersController.js pour contenir toutes les fonctions liées aux utilisateurs de votre application.

Ouvrez src/controllers/home.js et entrez le code ci-dessous :

 import { testEnvironmentVariable } from '../settings'; export const indexPage = (req, res) => res.status(200).json({ message: testEnvironmentVariable });

Vous remarquerez que nous n'avons déplacé que la fonction qui gère la demande de la route / .

Ouvrez src/controllers/index.js et entrez le code ci-dessous.

 // export everything from home.js export * from './home';

Nous exportons tout depuis le fichier home.js. Cela nous permet de raccourcir nos instructions d'importation pour import { indexPage } from '../controllers';

Ouvrez src/routes/index.js et remplacez le code par celui ci-dessous :

 import express from 'express'; import { indexPage } from '../controllers'; const indexRouter = express.Router(); indexRouter.get('/', indexPage); export default indexRouter;

Le seul changement ici est que nous avons fourni une fonction pour gérer la requête vers la route / .

Vous venez d'écrire avec succès votre premier contrôleur. À partir de là, il s'agit d'ajouter plus de fichiers et de fonctions selon les besoins.

Allez-y et jouez avec l'application en ajoutant quelques itinéraires et contrôleurs supplémentaires. Vous pouvez ajouter une route et un contrôleur pour la page à propos. N'oubliez pas de mettre à jour votre test, cependant.

Exécutez yarn test pour confirmer que nous n'avons rien cassé. Votre test est-il réussi ? C'est super.

C'est un bon point pour commiter nos changements.

  • La branche correspondante dans mon référentiel est 06-controllers.

Connecter la base de données PostgreSQL et écrire un modèle

Notre contrôleur renvoie actuellement des messages texte codés en dur. Dans une application du monde réel, nous avons souvent besoin de stocker et de récupérer des informations à partir d'une base de données. Dans cette section, nous allons connecter notre application à une base de données PostgreSQL.

Nous allons implémenter le stockage et la récupération de messages texte simples à l'aide d'une base de données. Nous avons deux options pour configurer une base de données : nous pouvons en approvisionner une à partir d'un serveur cloud ou nous pouvons configurer la nôtre localement.

Je vous recommande de provisionner une base de données à partir d'un serveur cloud. ElephantSQL has a free plan that gives 20MB of free storage which is sufficient for this tutorial. Visit the site and click on Get a managed database today . Create an account (if you don't have one) and follow the instructions to create a free plan. Take note of the URL on the database details page. We'll be needing it soon.

ElephantSQL turtle plan details page (Large preview)

If you would rather set up a database locally, you should visit the PostgreSQL and PgAdmin sites for further instructions.

Once we have a database set up, we need to find a way to allow our Express app to communicate with our database. Node.js by default doesn't support reading and writing to PostgreSQL database, so we'll be using an excellent library, appropriately named, node-postgres.

node-postgres executes SQL queries in node and returns the result as an object, from which we can grab items from the rows key.

Let's connect node-postgres to our application.

 # install node-postgres yarn add pg

Open settings.js and add the line below:

 export const connectionString = process.env.CONNECTION_STRING;

Open your .env file and add the CONNECTION_STRING variable. This is the connection string we'll be using to establish a connection to our database. The general form of the connection string is shown below.

 CONNECTION_STRING="postgresql://dbuser:dbpassword@localhost:5432/dbname"

If you're using elephantSQL you should copy the URL from the database details page.

Inside your /src folder, create a new folder called models/ . Inside this folder, create two files:

  • pool.js
  • model.js

Open pools.js and paste the following code:

 import { Pool } from 'pg'; import dotenv from 'dotenv'; import { connectionString } from '../settings'; dotenv.config(); export const pool = new Pool({ connectionString });

First, we import the Pool and dotenv from the pg and dotenv packages, and then import the settings we created for our postgres database before initializing dotenv . We establish a connection to our database with the Pool object. In node-postgres , every query is executed by a client. A Pool is a collection of clients for communicating with the database.

To create the connection, the pool constructor takes a config object. You can read more about all the possible configurations here. It also accepts a single connection string, which I will use here.

Open model.js and paste the following code:

 import { pool } from './pool'; class Model { constructor(table) { this.pool = pool; this.table = table; this.pool.on('error', (err, client) => `Error, ${err}, on idle client${client}`); } async select(columns, clause) { let query = `SELECT ${columns} FROM ${this.table}`; if (clause) query += clause; return this.pool.query(query); } } export default Model;

We create a model class whose constructor accepts the database table we wish to operate on. We'll be using a single pool for all our models.

We then create a select method which we will use to retrieve items from our database. This method accepts the columns we want to retrieve and a clause, such as a WHERE clause. It returns the result of the query, which is a Promise . Remember we said earlier that every query is executed by a client, but here we execute the query with pool. This is because, when we use pool.query , node-postgres executes the query using the first available idle client.

The query you write is entirely up to you, provided it is a valid SQL statement that can be executed by a Postgres engine.

The next step is to actually create an API endpoint to utilize our newly connected database. Before we do that, I'd like us to create some utility functions. The goal is for us to have a way to perform common database operations from the command line.

Create a folder, utils/ inside the src/ folder. Create three files inside this folder:

  • queries.js
  • queryFunctions.js
  • runQuery.js

We're going to create functions to create a table in our database, insert seed data in the table, and to delete the table.

Ouvrez queries.js et collez le code suivant :

 export const createMessageTable = ` DROP TABLE IF EXISTS messages; CREATE TABLE IF NOT EXISTS messages ( id SERIAL PRIMARY KEY, name VARCHAR DEFAULT '', message VARCHAR NOT NULL ) `; export const insertMessages = ` INSERT INTO messages(name, message) VALUES ('chidimo', 'first message'), ('orji', 'second message') `; export const dropMessagesTable = 'DROP TABLE messages';

Dans ce fichier, nous définissons trois chaînes de requête SQL. La première requête supprime et recrée la table des messages . La deuxième requête insère deux lignes dans la table des messages . N'hésitez pas à ajouter d'autres éléments ici. La dernière requête supprime/supprime la table des messages .

Ouvrez queryFunctions.js et collez le code suivant :

 import { pool } from '../models/pool'; import { insertMessages, dropMessagesTable, createMessageTable, } from './queries'; export const executeQueryArray = async arr => new Promise(resolve => { const stop = arr.length; arr.forEach(async (q, index) => { await pool.query(q); if (index + 1 === stop) resolve(); }); }); export const dropTables = () => executeQueryArray([ dropMessagesTable ]); export const createTables = () => executeQueryArray([ createMessageTable ]); export const insertIntoTables = () => executeQueryArray([ insertMessages ]);

Ici, nous créons des fonctions pour exécuter les requêtes que nous avons définies précédemment. Notez que la fonction executeQueryArray exécute un tableau de requêtes et attend que chacune se termine dans la boucle. (Ne faites pas une telle chose dans le code de production cependant). Ensuite, nous ne résolvons la promesse qu'une fois que nous avons exécuté la dernière requête de la liste. La raison de l'utilisation d'un tableau est que le nombre de ces requêtes augmentera à mesure que le nombre de tables dans notre base de données augmentera.

Ouvrez runQuery.js et collez le code suivant :

 import { createTables, insertIntoTables } from './queryFunctions'; (async () => { await createTables(); await insertIntoTables(); })();

C'est là que nous exécutons les fonctions pour créer la table et insérer les messages dans la table. Ajoutons une commande dans la section scripts de notre package.json pour exécuter ce fichier.

 "runQuery": "babel-node ./src/utils/runQuery"

Exécutez maintenant :

 yarn runQuery

Si vous inspectez votre base de données, vous verrez que la table des messages a été créée et que les messages ont été insérés dans la table.

Si vous utilisez ElephantSQL, sur la page des détails de la base de données, cliquez sur BROWSER dans le menu de navigation de gauche. Sélectionnez la table des messages et cliquez sur Execute . Vous devriez voir les messages du fichier queries.js .

Créons un contrôleur et une route pour afficher les messages de notre base de données.

Créez un nouveau fichier de contrôleur src/controllers/messages.js et collez le code suivant :

 import Model from '../models/model'; const messagesModel = new Model('messages'); export const messagesPage = async (req, res) => { try { const data = await messagesModel.select('name, message'); res.status(200).json({ messages: data.rows }); } catch (err) { res.status(200).json({ messages: err.stack }); } };

Nous importons notre classe Model et créons une nouvelle instance de ce modèle. Ceci représente la table des messages dans notre base de données. Nous utilisons ensuite la méthode select du modèle pour interroger notre base de données. Les données ( name et message ) que nous obtenons sont envoyées au format JSON dans la réponse.

Nous définissons le contrôleur messagesPage comme une fonction async . Puisque les requêtes node-postgres renvoient une promesse, nous await le résultat de cette requête. Si nous rencontrons une erreur lors de la requête, nous l'attrapons et affichons la pile à l'utilisateur. Vous devez décider comment choisir de gérer l'erreur.

Ajoutez le point de terminaison get messages à src/routes/index.js et mettez à jour la ligne d'importation.

 # update the import line import { indexPage, messagesPage } from '../controllers'; # add the get messages endpoint indexRouter.get('/messages', messagesPage)

Visitez https://localhost:3000/v1/messages et vous devriez voir les messages affichés comme indiqué ci-dessous.

Messages de la base de données. ( Grand aperçu )

Maintenant, mettons à jour notre fichier de test. Lorsque vous faites TDD, vous écrivez généralement vos tests avant d'implémenter le code qui fait passer le test. Je prends l'approche inverse ici parce que nous travaillons toujours sur la configuration de la base de données.

Créez un nouveau fichier, hooks.js dans le dossier test/ et entrez le code ci-dessous :

 import { dropTables, createTables, insertIntoTables, } from '../src/utils/queryFunctions'; before(async () => { await createTables(); await insertIntoTables(); }); after(async () => { await dropTables(); });

Lorsque notre test démarre, Mocha trouve ce fichier et l'exécute avant d'exécuter un fichier de test. Il exécute le crochet before pour créer la base de données et y insérer des éléments. Les fichiers de test s'exécutent ensuite. Une fois le test terminé, Mocha exécute le crochet after dans lequel nous déposons la base de données. Cela garantit que chaque fois que nous exécutons nos tests, nous le faisons avec des enregistrements propres et nouveaux dans notre base de données.

Créez un nouveau fichier de test test/messages.test.js et ajoutez le code ci-dessous :

 import { expect, server, BASE_URL } from './setup'; describe('Messages', () => { it('get messages page', done => { server .get(`${BASE_URL}/messages`) .expect(200) .end((err, res) => { expect(res.status).to.equal(200); expect(res.body.messages).to.be.instanceOf(Array); res.body.messages.forEach(m => { expect(m).to.have.property('name'); expect(m).to.have.property('message'); }); done(); }); }); });

Nous affirmons que le résultat de l'appel à /messages est un tableau. Pour chaque objet message, nous affirmons qu'il a le name et la propriété message .

La dernière étape de cette section consiste à mettre à jour les fichiers CI.

Ajoutez les sections suivantes au fichier .travis.yml :

 services: - postgresql addons: postgresql: "10" apt: packages: - postgresql-10 - postgresql-client-10 before_install: - sudo cp /etc/postgresql/{9.6,10}/main/pg_hba.conf - sudo /etc/init.d/postgresql restart

Cela demande à Travis de lancer une base de données PostgreSQL 10 avant d'exécuter nos tests.

Ajoutez la commande pour créer la base de données comme première entrée dans la section before_script :

 # add this as the first line in the before_script section - psql -c 'create database testdb;' -U postgres

Créez la variable d'environnement CONNECTION_STRING sur Travis et utilisez la valeur ci-dessous :

 CONNECTION_STRING="postgresql://postgres:postgres@localhost:5432/testdb"

Ajoutez les sections suivantes au fichier .appveyor.yml :

 before_test: - SET PGUSER=postgres - SET PGPASSWORD=Password12! - PATH=C:\Program Files\PostgreSQL\10\bin\;%PATH% - createdb testdb services: - postgresql101

Ajoutez la variable d'environnement de chaîne de connexion à appveyor. Utilisez la ligne ci-dessous :

 CONNECTION_STRING=postgresql://postgres:Password12!@localhost:5432/testdb

Validez maintenant vos modifications et envoyez-les à GitHub. Vos tests doivent réussir à la fois Travis CI et AppVeyor.

  • La branche correspondante dans mon référentiel est 07-connect-postgres.

Note : J'espère que tout fonctionne bien de votre côté, mais au cas où vous auriez des problèmes pour une raison quelconque, vous pouvez toujours vérifier mon code dans le référentiel !

Voyons maintenant comment nous pouvons ajouter un message à notre base de données. Pour cette étape, nous aurons besoin d'un moyen d'envoyer des requêtes POST à ​​notre URL. J'utiliserai Postman pour envoyer des requêtes POST .

Allons sur la route TDD et mettons à jour notre test pour refléter ce que nous espérons accomplir.

Ouvrez test/message.test.js et ajoutez le cas de test ci-dessous :

 it('posts messages', done => { const data = { name: 'some name', message: 'new message' }; server .post(`${BASE_URL}/messages`) .send(data) .expect(200) .end((err, res) => { expect(res.status).to.equal(200); expect(res.body.messages).to.be.instanceOf(Array); res.body.messages.forEach(m => { expect(m).to.have.property('id'); expect(m).to.have.property('name', data.name); expect(m).to.have.property('message', data.message); }); done(); }); });

Ce test envoie une requête POST au point de terminaison /v1/messages et nous nous attendons à ce qu'un tableau soit renvoyé. Nous vérifions également les propriétés id , name et message sur le tableau.

Exécutez vos tests pour voir que ce cas échoue. Réparons-le maintenant.

Pour envoyer des demandes de publication, nous utilisons la méthode de publication du serveur. Nous envoyons également le nom et le message que nous voulons insérer. Nous nous attendons à ce que la réponse soit un tableau, avec un id de propriété et les autres informations qui composent la requête. L' id est la preuve qu'un enregistrement a été inséré dans la base de données.

Ouvrez src/models/model.js et ajoutez la méthode insert :

 async insertWithReturn(columns, values) { const query = ` INSERT INTO ${this.table}(${columns}) VALUES (${values}) RETURNING id, ${columns} `; return this.pool.query(query); }

C'est la méthode qui nous permet d'insérer des messages dans la base de données. Après avoir inséré l'élément, il renvoie l id , le name et message .

Ouvrez src/controllers/messages.js et ajoutez le contrôleur ci-dessous :

 export const addMessage = async (req, res) => { const { name, message } = req.body; const columns = 'name, message'; const values = `'${name}', '${message}'`; try { const data = await messagesModel.insertWithReturn(columns, values); res.status(200).json({ messages: data.rows }); } catch (err) { res.status(200).json({ messages: err.stack }); } };

Nous déstructurons le corps de la requête pour obtenir le nom et le message. Ensuite, nous utilisons les valeurs pour former une chaîne de requête SQL que nous exécutons ensuite avec la méthode insertWithReturn de notre modèle.

Ajoutez le point de terminaison POST ci-dessous à /src/routes/index.js et mettez à jour votre ligne d'importation.

 import { indexPage, messagesPage, addMessage } from '../controllers'; indexRouter.post('/messages', addMessage);

Exécutez vos tests pour voir s'ils réussissent.

Ouvrez Postman et envoyez une demande POST au point de terminaison des messages . Si vous venez d'exécuter votre test, n'oubliez pas d'exécuter yarn query pour recréer la table des messages .

 yarn query 
Requête POST au point de terminaison des messages. ( Grand aperçu )
Requête GET affichant le message nouvellement ajouté. ( Grand aperçu )

Validez vos modifications et transférez vers GitHub. Vos tests doivent réussir à la fois Travis et AppVeyor. Votre couverture de test diminuera de quelques points, mais ce n'est pas grave.

  • La branche correspondante sur mon repo est 08-post-to-db.

Intergiciel

Notre discussion sur Express ne sera pas complète sans parler du middleware. La documentation Express décrit un middleware comme suit :

"[...] les fonctions qui ont accès à l'objet de requête ( req ), à l'objet de réponse ( res ) et à la prochaine fonction middleware dans le cycle requête-réponse de l'application. La prochaine fonction middleware est généralement désignée par une variable nommée next .

Un middleware peut exécuter un certain nombre de fonctions telles que l'authentification, la modification du corps de la requête, etc. Consultez la documentation Express sur l'utilisation du middleware.

Nous allons écrire un middleware simple qui modifie le corps de la requête. Notre middleware ajoutera le mot SAYS: au message entrant avant qu'il ne soit enregistré dans la base de données.

Avant de commencer, modifions notre test pour refléter ce que nous voulons réaliser.

Ouvrez test/messages.test.js et modifiez la dernière ligne d'attente dans le scénario de test du posts message :

 it('posts messages', done => { ... expect(m).to.have.property('message', `SAYS: ${data.message}`); # update this line ... });

Nous affirmons que la chaîne SAYS: a été ajoutée au message. Exécutez vos tests pour vous assurer que ce scénario de test échoue.

Maintenant, écrivons le code pour faire passer le test.

Créez un nouveau dossier middleware/ dans le dossier src/ . Créez deux fichiers dans ce dossier :

  • middleware.js
  • index.js

Entrez le code ci-dessous dans middleware.js :

 export const modifyMessage = (req, res, next) => { req.body.message = `SAYS: ${req.body.message}`; next(); };

Ici, nous ajoutons la chaîne SAYS: au message dans le corps de la requête. Après cela, nous devons appeler la fonction next() pour passer l'exécution à la fonction suivante dans la chaîne requête-réponse. Chaque middleware doit appeler la fonction next pour transmettre l'exécution au middleware suivant dans le cycle requête-réponse.

Entrez le code ci-dessous dans index.js :

 # export everything from the middleware file export * from './middleware';

Cela exporte le middleware que nous avons dans le fichier /middleware.js . Pour l'instant, nous n'avons que le middleware modifyMessage .

Ouvrez src/routes/index.js et ajoutez le middleware à la chaîne de demande-réponse du message post.

 import { modifyMessage } from '../middleware'; indexRouter.post('/messages', modifyMessage, addMessage);

Nous pouvons voir que la fonction modifyMessage vient avant la fonction addMessage . Nous invoquons la fonction addMessage en appelant next dans le middleware modifyMessage . À titre d'expérience, commentez la ligne next() au milieu de modifyMessage et regardez la demande se bloquer.

Ouvrez Postman et créez un nouveau message. Vous devriez voir la chaîne ajoutée.

Message modifié par le middleware. ( Grand aperçu )

C'est un bon point pour commiter nos changements.

  • La branche correspondante dans mon référentiel est 09-middleware.

Gestion des erreurs et middleware asynchrone

Les erreurs sont inévitables dans toute application. La tâche qui attend le développeur est de savoir comment traiter les erreurs aussi gracieusement que possible.

En express :

« La gestion des erreurs fait référence à la manière dont Express détecte et traite les erreurs qui se produisent de manière synchrone et asynchrone.

Si nous n'écrivions que des fonctions synchrones, nous n'aurions peut-être pas à nous soucier autant de la gestion des erreurs, car Express fait déjà un excellent travail pour les gérer. D'après les docs :

"Les erreurs qui se produisent dans le code synchrone à l'intérieur des gestionnaires de route et du middleware ne nécessitent aucun travail supplémentaire."

Mais une fois que nous commençons à écrire des gestionnaires de routeurs asynchrones et des intergiciels, nous devons gérer les erreurs.

Notre middleware modifyMessage est une fonction synchrone. Si une erreur se produit dans cette fonction, Express s'en chargera très bien. Voyons comment nous gérons les erreurs dans le middleware asynchrone.

Disons qu'avant de créer un message, nous voulons obtenir une image de l'API Lorem Picsum en utilisant cette URL https://picsum.photos/id/0/info . Il s'agit d'une opération asynchrone qui peut réussir ou échouer, et qui présente un cas à traiter.

Commencez par installer Axios.

 # install axios yarn add axios

Ouvrez src/middleware/middleware.js et ajoutez la fonction ci-dessous :

 export const performAsyncAction = async (req, res, next) => { try { await axios.get('https://picsum.photos/id/0/info'); next(); } catch (err) { next(err); } };

Dans cette fonction async , nous await un appel à une API (nous n'avons pas réellement besoin des données renvoyées) et appelons ensuite la fonction next dans la chaîne de requêtes. Si la requête échoue, nous interceptons l'erreur et la transmettons à next . Une fois qu'Express détecte cette erreur, il ignore tous les autres middlewares de la chaîne. Si nous n'appelons pas next(err) , la requête sera bloquée. Si nous avons seulement appelé next() sans err , la requête se déroulera comme si rien ne s'était passé et l'erreur ne serait pas interceptée.

Importez cette fonction et ajoutez-la à la chaîne middleware de la route des messages post :

 import { modifyMessage, performAsyncAction } from '../middleware'; indexRouter.post('/messages', modifyMessage, performAsyncAction, addMessage);

Ouvrez src/app.js et ajoutez le code ci-dessous juste avant la ligne export default app .

 app.use((err, req, res, next) => { res.status(400).json({ error: err.stack }); }); export default app;

Ceci est notre gestionnaire d'erreurs. Selon la documentation de gestion des erreurs Express :

"[...] les fonctions de gestion des erreurs ont quatre arguments au lieu de trois : (err, req, res, next) ."

Notez que ce gestionnaire d'erreurs doit venir en dernier, après chaque appel app.use() . Une fois que nous rencontrons une erreur, nous renvoyons la trace de la pile avec un code d'état de 400 . Vous pouvez faire ce que vous voulez avec l'erreur. Vous voudrez peut-être l'enregistrer ou l'envoyer quelque part.

C'est un bon endroit pour valider vos modifications.

  • La branche correspondante dans mon référentiel est 10-async-middleware.

Déployer sur Heroku

  1. Pour commencer, rendez-vous sur https://www.heroku.com/ et connectez-vous ou inscrivez-vous.
  2. Téléchargez et installez la CLI Heroku à partir d'ici.
  3. Ouvrez un terminal dans le dossier du projet pour exécuter la commande.
 # login to heroku on command line heroku login

Cela ouvrira une fenêtre de navigateur et vous demandera de vous connecter à votre compte Heroku.

Connectez-vous pour accorder à votre terminal l'accès à votre compte Heroku et créez une nouvelle application heroku en exécutant :

 #app name is up to you heroku create app-name

Cela créera l'application sur Heroku et renverra deux URL.

 # app production url and git url https://app-name.herokuapp.com/ | https://git.heroku.com/app-name.git

Copiez l'URL à droite et exécutez la commande ci-dessous. Notez que cette étape est facultative car vous constaterez peut-être que Heroku a déjà ajouté l'URL distante.

 # add heroku remote url git remote add heroku https://git.heroku.com/my-shiny-new-app.git

Ouvrez un terminal latéral et exécutez la commande ci-dessous. Cela vous montre le journal de l'application en temps réel, comme indiqué dans l'image.

 # see process logs heroku logs --tail
Journaux Heroku. ( Grand aperçu )

Exécutez les trois commandes suivantes pour définir les variables d'environnement requises :

 heroku config:set TEST_ENV_VARIABLE="Environment variable is coming across." heroku config:set CONNECTION_STRING=your-db-connection-string-here. heroku config:set NPM_CONFIG_PRODUCTION=false

Rappelez-vous dans nos scripts, nous définissons :

 "prestart": "babel ./src --out-dir build", "start": "node ./build/bin/www",

Pour démarrer l'application, elle doit être compilée jusqu'à ES5 en utilisant babel dans l'étape de prestart -démarrage car babel n'existe que dans nos dépendances de développement. Nous devons définir NPM_CONFIG_PRODUCTION sur false afin de pouvoir également les installer.

Pour confirmer que tout est correctement défini, exécutez la commande ci-dessous. Vous pouvez également visiter l'onglet settings sur la page de l'application et cliquer sur Reveal Config Vars les variables de configuration.

 # check configuration variables heroku config

Maintenant, lancez git push heroku .

Pour ouvrir l'application, exécutez :

 # open /v1 route heroku open /v1 # open /v1/messages route heroku open /v1/messages

Si, comme moi, vous utilisez la même base de données PostgresSQL pour le développement et la production, vous constaterez peut-être qu'à chaque fois que vous exécutez vos tests, la base de données est supprimée. Pour le recréer, vous pouvez exécuter l'une des commandes suivantes :

 # run script locally yarn runQuery # run script with heroku heroku run yarn runQuery

Déploiement continu (CD) avec Travis

Ajoutons maintenant le déploiement continu (CD) pour compléter le flux CI/CD. Nous déploierons à partir de Travis après chaque test réussi.

La première étape consiste à installer Travis CI. (Vous pouvez trouver les instructions d'installation ici.) Après avoir installé avec succès le Travis CI, connectez-vous en exécutant la commande ci-dessous. (Notez que cela doit être fait dans votre référentiel de projet.)

 # login to travis travis login --pro # use this if you're using two factor authentication travis login --pro --github-token enter-github-token-here

Si votre projet est hébergé sur travis-ci.org, supprimez le drapeau --pro . Pour obtenir un jeton GitHub, visitez la page des paramètres de développeur de votre compte et générez-en un. Cela ne s'applique que si votre compte est sécurisé avec 2FA.

Ouvrez votre .travis.yml et ajoutez une section de déploiement :

 deploy: provider: heroku app: master: app-name

Ici, nous spécifions que nous voulons déployer sur Heroku. La sous-section app spécifie que nous voulons déployer la branche master de notre référentiel sur l' app-name sur Heroku. Il est possible de déployer différentes branches sur différentes applications. Vous pouvez en savoir plus sur les options disponibles ici.

Exécutez la commande ci-dessous pour chiffrer votre clé API Heroku et l'ajouter à la section de déploiement :

 # encrypt heroku API key and add to .travis.yml travis encrypt $(heroku auth:token) --add deploy.api_key --pro

Cela ajoutera la sous-section ci-dessous à la section de déploiement.

 api_key: secure: very-long-encrypted-api-key-string

Validez maintenant vos modifications et transférez vers GitHub tout en surveillant vos journaux. Vous verrez le build se déclencher dès que le test Travis sera terminé. De cette façon, si nous avons un test qui échoue, les modifications ne seront jamais déployées. De même, si la construction échouait, l'ensemble du test échouerait. Ceci termine le flux CI/CD.

  • La branche correspondante dans mon repo est 11-cd.

Conclusion

Si vous êtes arrivé jusqu'ici, je dis : "Bravo !" Dans ce didacticiel, nous avons configuré avec succès un nouveau projet Express. Nous sommes allés de l'avant pour configurer les dépendances de développement ainsi que l'intégration continue (CI). Nous avons ensuite écrit des fonctions asynchrones pour gérer les requêtes vers nos points de terminaison API - complétées par des tests. Nous avons ensuite examiné brièvement la gestion des erreurs. Enfin, nous avons déployé notre projet sur Heroku et configuré le déploiement continu.

Vous avez maintenant un modèle pour votre prochain projet back-end. Nous en avons fait assez pour vous aider à démarrer, mais vous devriez continuer à apprendre pour continuer. Assurez-vous également de consulter la documentation Express.js. Si vous préférez utiliser MongoDB au lieu de PostgreSQL , j'ai ici un modèle qui fait exactement cela. Vous pouvez le vérifier pour la configuration. Il n'a que quelques points de différence.

Ressources

  • "Créer un backend d'API Express avec MongoDB", Orji Chidi Matthew, GitHub
  • "Un petit guide pour connecter le middleware", Stephen Sugden
  • "Modèle d'API Express", GitHub
  • "AppVeyor contre Travis CI", StackShare
  • « La CLI Heroku », Centre de développement Heroku
  • "Déploiement Heroku", Travis CI
  • "Utilisation du middleware", Express.js
  • "Gestion des erreurs", Express.js
  • "Pour commencer", Moka
  • nyc (GitHub)
  • ElephantSQL
  • Facteur
  • Express
  • Travis CI
  • Code Climat
  • PostgreSQLName
  • pgAdmin