Comment configurer un projet backend d'API express avec PostgreSQL
Publié: 2022-03-10Nous 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.
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.
- Supprimez le fichier index/users.js .
- Supprimez les dossiers
public/
etviews/
. - Renommez le fichier bin/www en bin/www.js .
- Désinstallez
jade
avec la commandeyarn remove jade
. - 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. - 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"
-
prestart
scripts de pré-démarrage construisent le contenu du dossiersrc/
et le placent dans le dossierbuild/
. Lorsque vous lancez la commandeyarn start
, ce script s'exécute avant le script destart
. - Le script de
start
sert maintenant le contenu du dossierbuild/
au lieu du dossiersrc/
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. -
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 maintenantbabel-node
pour exécuter l'application au lieu denode
normal. Le drapeau--exec
forcebabel-node
à servir le dossiersrc/
. Pour le script destart
, nous utilisonsnode
car les fichiers du dossierbuild/
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.
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.
- 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.
- Survolez la flèche déroulante à côté de votre photo de profil et cliquez sur
settings
. - Sous l'onglet
Repositories
, cliquez surManage repositories on Github
pour être redirigé vers Github. - Sur la page GitHub, faites défiler jusqu'à
Repository access
et cochez la case à côté deOnly select repositories
. - Cliquez sur le menu déroulant
Select repositories
et recherchez le référentielexpress-api-template
. Cliquez dessus pour l'ajouter à la liste des référentiels que vous souhaitez ajouter àtravis-ci
. - Cliquez sur
Approve and install
et attendez d'être redirigé verstravis-ci
. - 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. - Copiez le code résultant et collez-le dans votre fichier README.md .
- Sur la page du projet, cliquez sur
More options
>Settings
. Sous la sectionEnvironment Variables
, ajoutez la variable d'environnementTEST_ENV_VARIABLE
. Lorsque vous entrez sa valeur, assurez-vous de l'avoir entre guillemets doubles comme ceci"Environment variable is coming across."
- 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.
- Visitez coveralls.io et connectez-vous ou inscrivez-vous avec votre compte Github.
- Survolez le côté gauche de l'écran pour afficher le menu de navigation. Cliquez sur
ADD REPOS
. - 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 surSYNC 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. - Cliquez sur détails pour accéder à la page des détails du dépôt.
- 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 pourrepo_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.
- Rendez-vous sur codeclimate.com et cliquez sur « Sign up with GitHub ». Connectez vous si vous possédez déjà un compte.
- Une fois dans votre tableau de bord, cliquez sur
Add a repository
. - Recherchez le
express-api-template
dans la liste et cliquez surAdd Repo
. - Attendez que la construction soit terminée et redirigez vers le tableau de bord du référentiel.
- Sous
Codebase Summary
, cliquez surTest Coverage
. Dans le menuTest coverage
, copiez l'TEST REPORTER ID
et collez-le dans votre .travis.yml comme valeur deCC_TEST_REPORTER_ID
. - Toujours sur la même page, dans la navigation de gauche, sous
EXTRAS
, cliquez sur Badges. Copiez les badges demaintainability
ettest 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 surADD
. - Cliquez sur l'onglet
Settings
. Cliquez surEnvironment
dans la navigation de gauche. AjoutezTEST_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.
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.
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.
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
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éenext
.
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.
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
- Pour commencer, rendez-vous sur https://www.heroku.com/ et connectez-vous ou inscrivez-vous.
- Téléchargez et installez la CLI Heroku à partir d'ici.
- 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
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