Come impostare un progetto di backend API Express con PostgreSQL

Pubblicato: 2022-03-10
Riepilogo rapido ↬ In questo articolo creeremo una serie di endpoint API utilizzando Express da zero nella sintassi ES6 e tratteremo alcune best practice di sviluppo. Scopri come funzionano tutti i pezzi mentre crei un piccolo progetto utilizzando l'integrazione continua e lo sviluppo basato su test prima di implementarlo su Heroku.

Adotteremo un approccio di sviluppo basato su test (TDD) e il lavoro di integrazione continua (CI) per eseguire automaticamente i nostri test su Travis CI e AppVeyor, completi di qualità del codice e report sulla copertura. Impareremo su controller, modelli (con PostgreSQL), gestione degli errori e middleware asincrono Express. Infine, completeremo la pipeline CI/CD configurando la distribuzione automatica su Heroku.

Sembra molto, ma questo tutorial è rivolto a principianti che sono pronti a cimentarsi in un progetto back-end con un certo livello di complessità e che potrebbero essere ancora confusi su come tutti i pezzi si incastrano in un progetto reale .

È robusto senza essere opprimente ed è suddiviso in sezioni che puoi completare in un ragionevole lasso di tempo.

Iniziare

Il primo passaggio consiste nel creare una nuova directory per il progetto e avviare un nuovo progetto di nodo. Il nodo è necessario per continuare con questo tutorial. Se non lo hai installato, vai al sito Web ufficiale, scaricalo e installalo prima di continuare.

Userò il filato come gestore di pacchetti per questo progetto. Ci sono istruzioni di installazione per il tuo sistema operativo specifico qui. Sentiti libero di usare npm se lo desideri.

Altro dopo il salto! Continua a leggere sotto ↓

Apri il tuo terminale, crea una nuova directory e avvia un progetto 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

Rispondi alle domande che seguono per generare un file package.json . Questo file contiene informazioni sul tuo progetto. Un esempio di tali informazioni include quali dipendenze utilizza, il comando per avviare il progetto e così via.

Ora puoi aprire la cartella del progetto nel tuo editor di scelta. Uso il codice di Visual Studio. È un IDE gratuito con tonnellate di plugin per semplificarti la vita ed è disponibile per tutte le principali piattaforme. Puoi scaricarlo dal sito ufficiale.

Crea i seguenti file nella cartella del progetto:

  • LEGGIMI.md
  • .editorconfig

Ecco una descrizione di ciò che fa .editorconfig dal sito Web EditorConfig. (Probabilmente non ne hai bisogno se lavori da solo, ma non fa male, quindi lo lascio qui.)

"EditorConfig aiuta a mantenere stili di codifica coerenti per più sviluppatori che lavorano allo stesso progetto su vari editor e IDE".

Apri .editorconfig e incolla il codice seguente:

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

Il [*] significa che vogliamo applicare le regole che ne derivano a ogni file del progetto. Vogliamo una dimensione del rientro di due spazi e un set di caratteri UTF-8 . Vogliamo anche tagliare lo spazio bianco finale e inserire una riga vuota finale nel nostro file.

Apri README.md e aggiungi il nome del progetto come elemento di primo livello.

 # Express API template

Aggiungiamo subito il controllo della versione.

 # initialize the project folder as a git repository git init

Crea un file .gitignore e inserisci le seguenti righe:

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

Questi sono tutti i file e le cartelle che non vogliamo tracciare. Non li abbiamo ancora nel nostro progetto, ma li vedremo man mano che procediamo.

A questo punto, dovresti avere la seguente struttura di cartelle.

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

Considero questo un buon punto per confermare le mie modifiche e inviarle a GitHub.

Avvio di un nuovo progetto Express

Express è un framework Node.js per la creazione di applicazioni web. Secondo il sito ufficiale, si tratta di un

Framework web veloce, semplice e minimalista per Node.js.

Esistono altri fantastici framework di applicazioni Web per Node.js, ma Express è molto popolare, con oltre 47.000 stelle GitHub al momento della stesura di questo articolo.

In questo articolo, non avremo molte discussioni su tutte le parti che compongono Express. Per quella discussione, ti consiglio di dare un'occhiata alla serie di Jamie. La prima parte è qui e la seconda parte è qui.

Installa Express e avvia un nuovo progetto Express. È possibile configurare manualmente un server Express da zero, ma per semplificarci la vita utilizzeremo il generatore di Express per configurare lo scheletro dell'app.

 # 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

Il flag -f obbliga Express a creare il progetto nella directory corrente.

Ora eseguiremo alcune operazioni di pulizia della casa.

  1. Elimina il file index/users.js .
  2. Elimina le cartelle public/ e views/ .
  3. Rinomina il file bin/www in bin/www.js .
  4. Disinstalla jade con il comando yarn remove jade .
  5. Crea una nuova cartella denominata src/ e sposta al suo interno quanto segue: 1. file app.js 2. bin/ cartella 3. routes/ cartella all'interno.
  6. Apri package.json e aggiorna lo script di start in modo che assomigli di seguito.
 "start": "node ./src/bin/www"

A questo punto, la struttura della cartella del tuo progetto appare come di seguito. Puoi vedere come VS Code evidenzia le modifiche ai file che hanno avuto luogo.

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

Apri src/app.js e sostituisci il contenuto con il codice seguente.

 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;

Dopo aver richiesto alcune librerie, indichiamo a Express di gestire ogni richiesta che arriva a /v1 con indexRouter .

Sostituisci il contenuto di route/index.js con il codice seguente:

 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;

Prendiamo Express, creiamo un router da esso e serviamo la / route, che restituisce un codice di stato di 200 e un messaggio JSON.

Avvia l'app con il comando seguente:

 # start the app yarn start

Se hai impostato tutto correttamente dovresti vedere solo $ node ./src/bin/www nel tuo terminale.

Visita https://localhost:3000/v1 nel tuo browser. Dovresti vedere il seguente messaggio:

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

Questo è un buon punto per confermare le nostre modifiche.

  • Il ramo corrispondente nel mio repository è 01-install-express.

Conversione del nostro codice ES6

Il codice generato da express-generator è in ES5 , ma in questo articolo scriveremo tutto il nostro codice nella sintassi ES6 . Quindi, convertiamo il nostro codice esistente in ES6 .

Sostituisci il contenuto di route/index.js con il codice seguente:

 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;

È lo stesso codice che abbiamo visto sopra, ma con l'istruzione import e una funzione freccia nel gestore del percorso / .

Sostituisci il contenuto di src/app.js con il codice seguente:

 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;

Diamo ora un'occhiata al contenuto di src/bin/www.js . Lo costruiremo in modo incrementale. Elimina il contenuto di src/bin/www.js e incollalo nel blocco di codice sottostante.

 #!/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

Questo codice verifica se nelle variabili di ambiente è specificata una porta personalizzata. Se non è impostato nessuno, il valore della porta predefinito di 3000 viene impostato nell'istanza dell'app, dopo essere stato normalizzato su una stringa o un numero da normalizePort . Il server viene quindi creato dal modulo http , con l' app come funzione di callback.

La riga del #!/usr/bin/env node è facoltativa poiché si specifica il nodo quando si desidera eseguire questo file. Ma assicurati che sia sulla riga 1 del file src/bin/www.js o rimuovilo completamente.

Diamo un'occhiata alla funzione di gestione degli errori. Copia e incolla questo blocco di codice dopo la riga in cui è stato creato il server.

 /** * 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 funzione onError ascolta gli errori nel server http e visualizza i messaggi di errore appropriati. La funzione onListening emette semplicemente la porta su cui il server è in ascolto sulla console. Infine, il server ascolta le richieste in arrivo all'indirizzo e alla porta specificati.

A questo punto, tutto il nostro codice esistente è nella sintassi ES6 . Arresta il tuo server (usa Ctrl + C ) ed esegui yarn start . Verrà visualizzato un errore SyntaxError: Invalid or unexpected token . Ciò accade perché Node (al momento della scrittura) non supporta parte della sintassi che abbiamo utilizzato nel nostro codice.

Ora lo risolveremo nella sezione seguente.

Configurazione delle dipendenze di sviluppo: babel , nodemon , eslint e prettier

È ora di impostare la maggior parte degli script di cui avremo bisogno in questa fase del progetto.

Installa le librerie richieste con i comandi seguenti. Puoi semplicemente copiare tutto e incollarlo nel tuo terminale. Le righe di commento verranno saltate.

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

Questo installa tutti gli script babel elencati come dipendenze di sviluppo. Controlla il tuo file package.json e dovresti vedere una sezione devDependencies . Tutti gli script installati verranno elencati lì.

Gli script babel che stiamo utilizzando sono spiegati di seguito:

@babel/cli Un'installazione richiesta per l'utilizzo di babel . Consente l'uso di Babel dal terminale ed è disponibile come ./node_modules/.bin/babel .
@babel/core Funzionalità di base di Babel. Questa è un'installazione richiesta.
@babel/node Funziona esattamente come la CLI di Node.js, con l'ulteriore vantaggio di compilare con i preset e i plug-in babel . Questo è necessario per l'uso con nodemon .
@babel/plugin-transform-runtime Questo aiuta a evitare la duplicazione nell'output compilato.
@babel/preset-env Una raccolta di plug-in responsabili dell'esecuzione delle trasformazioni del codice.
@babel/register Questo compila i file al volo ed è specificato come requisito durante i test.
@babel/runtime Funziona in combinazione con @babel/plugin-transform-runtime .

Crea un file chiamato .babelrc nella radice del tuo progetto e aggiungi il seguente codice:

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

Installiamo nodemon

 # install nodemon yarn add nodemon --dev

nodemon è una libreria che monitora il codice sorgente del nostro progetto e riavvia automaticamente il nostro server ogni volta che osserva eventuali modifiche.

Crea un file chiamato nodemon.json nella radice del tuo progetto e aggiungi il codice di seguito:

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

La chiave watch dice a nodemon quali file e cartelle controllare per le modifiche. Quindi, ogni volta che uno di questi file cambia, nodemon riavvia il server. Il tasto ignore indica ai file di non controllare le modifiche.

Ora aggiorna la sezione degli scripts del tuo file package.json in modo che assomigli al seguente:

 # 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 compila il contenuto della cartella src/ e lo inserisce nella cartella build/ . Quando si esegue il comando yarn start , questo script viene eseguito prima dello script di start .
  2. lo script start ora serve il contenuto della cartella build/ invece della cartella src/ che stavamo servendo in precedenza. Questo è lo script che utilizzerai per servire il file in produzione. In effetti, servizi come Heroku eseguono automaticamente questo script durante la distribuzione.
  3. yarn startdev viene utilizzato per avviare il server durante lo sviluppo. D'ora in poi utilizzeremo questo script durante lo sviluppo dell'app. Si noti che ora stiamo usando babel-node per eseguire l'app invece del normale node . Il flag --exec forza babel-node a servire la cartella src/ . Per lo script di start , utilizziamo node poiché i file nella cartella build/ sono stati compilati in ES5.

Esegui yarn startdev e visita https://localhost:3000/v1. Il tuo server dovrebbe essere di nuovo attivo e funzionante.

Il passaggio finale in questa sezione consiste nel configurare ESLint e prettier . ESLint aiuta a far rispettare le regole di sintassi mentre più carino aiuta a formattare correttamente il nostro codice per la leggibilità.

Aggiungili entrambi con il comando seguente. Dovresti eseguirlo su un terminale separato osservando il terminale su cui è in esecuzione il nostro server. Dovresti vedere il riavvio del server. Questo perché stiamo monitorando il file package.json per le modifiche.

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

Ora crea il file .eslintrc.json nella root del progetto e aggiungi il codice seguente:

 { "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] } }

Questo file definisce principalmente alcune regole rispetto alle quali eslint verificherà il nostro codice. Puoi vedere che stiamo estendendo le regole di stile utilizzate da Airbnb.

Nella sezione "rules" , definiamo se eslint deve mostrare un avviso o un errore quando incontra determinate violazioni. Ad esempio, mostra un messaggio di avviso sul nostro terminale per qualsiasi rientro che non utilizza 2 spazi. Un valore di [0] disattiva una regola, il che significa che non riceveremo un avviso o un errore se violiamo quella regola.

Crea un file chiamato .prettierrc e aggiungi il codice qui sotto:

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

Stiamo impostando una larghezza di tabulazione di 2 e imponiamo l'uso di virgolette singole in tutta la nostra applicazione. Controlla la guida più carina per ulteriori opzioni di stile.

Ora aggiungi i seguenti script al tuo 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"

Esegui yarn lint . Dovresti vedere una serie di errori e avvisi nella console.

Il pretty comando abbellisce il nostro codice. Il comando postpretty viene eseguito subito dopo. Esegue il comando lint con il flag --fix aggiunto. Questo flag dice a ESLint di risolvere automaticamente i problemi comuni di linting. In questo modo, eseguo principalmente il comando yarn pretty senza preoccuparmi del comando lint .

Esegui yarn pretty . Dovresti vedere che abbiamo solo due avvisi sulla presenza di alert nel file bin/www.js .

Ecco come appare la nostra struttura del progetto a questo punto.

 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

Potresti scoprire di avere un file aggiuntivo, yarn-error.log nella radice del tuo progetto. Aggiungilo al file .gitignore . Conferma le tue modifiche.

  • Il ramo corrispondente a questo punto nel mio repository è 02-dev-dependencies.

Impostazioni e variabili d'ambiente nel nostro file .env

In quasi tutti i progetti, avrai bisogno di un posto dove archiviare le impostazioni che verranno utilizzate nell'app, ad esempio una chiave segreta AWS. Memorizziamo tali impostazioni come variabili di ambiente. Questo li tiene lontani da occhi indiscreti e possiamo usarli all'interno della nostra applicazione secondo necessità.

Mi piace avere un file settings.js con il quale leggo tutte le mie variabili d'ambiente. Quindi, posso fare riferimento al file delle impostazioni da qualsiasi punto all'interno della mia app. Sei libero di nominare questo file come vuoi, ma c'è una sorta di consenso sulla denominazione di tali file settings.js o config.js .

Per le nostre variabili di ambiente, le terremo in un file .env e da lì le leggeremo nel nostro file delle settings .

Crea il file .env nella radice del tuo progetto e inserisci la riga seguente:

 TEST_ENV_VARIABLE="Environment variable is coming across"

Per poter leggere le variabili d'ambiente nel nostro progetto, c'è una bella libreria, dotenv che legge il nostro file .env e ci dà accesso alle variabili d'ambiente definite all'interno. Installiamolo.

 # install dotenv yarn add dotenv

Aggiungi il file .env all'elenco dei file controllati da nodemon .

Ora, crea il file settings.js all'interno della cartella src/ e aggiungi il codice seguente:

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

Importiamo il pacchetto dotenv e chiamiamo il suo metodo di configurazione. Quindi esportiamo la testEnvironmentVariable che impostiamo nel nostro file .env .

Apri src/routes/index.js e sostituisci il codice con quello qui sotto.

 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;

L'unica modifica che abbiamo apportato qui è che importiamo testEnvironmentVariable dal nostro file delle settings e lo usiamo come messaggio di ritorno per una richiesta dalla / route.

Visita https://localhost:3000/v1 e dovresti vedere il messaggio, come mostrato di seguito.

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

E questo è tutto. D'ora in poi possiamo aggiungere tutte le variabili di ambiente che vogliamo ed esportarle dal nostro file settings.js .

Questo è un buon punto per eseguire il commit del codice. Ricordati di abbellire e lint il tuo codice.

  • Il ramo corrispondente sul mio repository è 03-env-variables.

Scrivere il nostro primo test

È ora di incorporare i test nella nostra app. Una delle cose che danno allo sviluppatore fiducia nel proprio codice sono i test. Sono sicuro che hai visto innumerevoli articoli sul web che predicano il Test-Driven Development (TDD). Non si può sottolineare abbastanza che il tuo codice necessita di una certa misura di test. TDD è molto facile da seguire quando si lavora con Express.js.

Nei nostri test, effettueremo chiamate ai nostri endpoint API e verificheremo se ciò che viene restituito è ciò che ci aspettiamo.

Installa le dipendenze richieste:

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

Ognuna di queste librerie ha il proprio ruolo da svolgere nei nostri test.

mocha corridore di prova
chai usato per fare affermazioni
nyc raccogliere il rapporto di copertura del test
sinon-chai estende le affermazioni di chai
supertest utilizzato per effettuare chiamate HTTP ai nostri endpoint API
coveralls per caricare la copertura del test su coveralls.io

Crea una nuova cartella test/ nella radice del tuo progetto. Crea due file all'interno di questa cartella:

  • test/setup.js
  • test/indice.test.js

Mocha troverà automaticamente la cartella test/ .

Apri test/setup.js e incolla il codice seguente. Questo è solo un file di supporto che ci aiuta a organizzare tutte le importazioni di cui abbiamo bisogno nei nostri file di prova.

 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';

Questo è come un file di impostazioni, ma per i nostri test. In questo modo non dobbiamo inizializzare tutto all'interno di ciascuno dei nostri file di test. Quindi importiamo i pacchetti necessari ed esportiamo ciò che abbiamo inizializzato, che possiamo quindi importare nei file che ne hanno bisogno.

Apri index.test.js e incolla il seguente codice di test.

 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(); }); }); });

Qui facciamo una richiesta per ottenere l'endpoint di base, che è / e affermiamo che la res. l'oggetto body ha una chiave del message con un valore di Environment variable is coming across.

Se non hai familiarità con il modello di describe , it consiglio di dare una rapida occhiata al documento "Guida introduttiva" di Mocha.

Aggiungi il comando test alla sezione scripts di package.json .

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

Questo script esegue il nostro test con nyc e genera tre tipi di report di copertura: un report HTML, emesso nella cartella coverage/ ; un rapporto di testo inviato al terminale e un rapporto lcov inviato alla cartella .nyc_output/ .

Ora esegui yarn test . Dovresti vedere un rapporto di testo nel tuo terminale proprio come quello nella foto qui sotto.

Rapporto sulla copertura del test (anteprima grande)

Si noti che vengono generate due cartelle aggiuntive:

  • .nyc_output/
  • coverage/

Guarda dentro .gitignore e vedrai che stiamo già ignorando entrambi. Ti incoraggio ad aprire coverage/index.html in un browser e visualizzare il rapporto di prova per ogni file.

Questo è un buon punto per confermare le modifiche.

  • Il ramo corrispondente nel mio repository è 04-first-test.

Integrazione Continua (CD) E Badge: Travis, Tute, Code Climate, AppVeyor

È giunto il momento di configurare gli strumenti di integrazione e distribuzione continua (CI/CD). Configureremo servizi comuni come travis-ci , coveralls , AppVeyor e codeclimate e aggiungeremo badge al nostro file README.

Iniziamo.

Travis CI

Travis CI è uno strumento che esegue i nostri test automaticamente ogni volta che inviamo un commit su GitHub (e recentemente, Bitbucket) e ogni volta che creiamo una richiesta pull. Questo è utile soprattutto quando si effettuano richieste pull mostrandoci se il nostro nuovo codice ha violato uno qualsiasi dei nostri test.

  1. Visita travis-ci.com o travis-ci.org e crea un account se non ne hai uno. Devi registrarti con il tuo account GitHub.
  2. Passa il mouse sopra la freccia del menu a discesa accanto all'immagine del tuo profilo e fai clic su settings .
  3. Nella scheda Repositories fai clic su Manage repositories on Github per essere reindirizzato a Github.
  4. Nella pagina GitHub, scorri verso il basso fino a Repository access e fai clic sulla casella di controllo accanto a Only select repositories .
  5. Fai clic sul menu a discesa Select repositories e trova il repository express-api-template . Fare clic per aggiungerlo all'elenco dei repository che si desidera aggiungere a travis-ci .
  6. Fai clic su Approve and install e attendi di essere reindirizzato a travis-ci .
  7. Nella parte superiore della pagina del repository, vicino al nome del repository, fai clic sull'icona build unknown . Dal modale Immagine di stato, seleziona markdown dal menu a discesa del formato.
  8. Copia il codice risultante e incollalo nel tuo file README.md .
  9. Nella pagina del progetto, fai clic su More options > Settings . Nella sezione Environment Variables , aggiungi la variabile env TEST_ENV_VARIABLE . Quando inserisci il suo valore, assicurati di averlo tra virgolette come questa "Environment variable is coming across."
  10. Crea il file .travis.yml nella radice del tuo progetto e incolla il codice sottostante (imposteremo il valore di CC_TEST_REPORTER_ID nella sezione 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

Innanzitutto, diciamo a Travis di eseguire il nostro test con Node.js, quindi impostare la variabile di ambiente globale CC_TEST_REPORTER_ID (ci arriveremo nella sezione Code Climate). Nella sezione della matrix , diciamo a Travis di eseguire i nostri test con Node.js v12. Vogliamo anche memorizzare nella cache la directory node_modules/ in modo che non debba essere rigenerata ogni volta.

Installiamo le nostre dipendenze usando il comando yarn che è una scorciatoia per l' yarn install . I comandi before_script e after_script vengono utilizzati per caricare i risultati della copertura in codeclimate . A breve configureremo codeclimate . Dopo yarn test stato eseguito correttamente, vogliamo anche eseguire yarn coverage che caricherà il nostro rapporto di copertura su coveralls.io.

Tute

Tute carica i dati sulla copertura del test per una facile visualizzazione. Possiamo visualizzare la copertura del test sul nostro computer locale dalla cartella di copertura, ma Coveralls lo rende disponibile al di fuori del nostro computer locale.

  1. Visita coveralls.io e accedi o registrati con il tuo account Github.
  2. Passa il mouse sul lato sinistro dello schermo per visualizzare il menu di navigazione. Fare clic su ADD REPOS .
  3. Cerca il repository express-api-template e attiva la copertura utilizzando il pulsante di attivazione/disattivazione sul lato sinistro. Se non riesci a trovarlo, fai clic su SYNC REPOS nell'angolo in alto a destra e riprova. Nota che il tuo repository deve essere pubblico, a meno che tu non abbia un account PRO.
  4. Fare clic su dettagli per andare alla pagina dei dettagli del repository.
  5. Crea il file .coveralls.yml nella radice del tuo progetto e inserisci il codice seguente. Per ottenere il repo_token , fai clic sui dettagli del repository. Lo troverai facilmente in quella pagina. Potresti semplicemente fare una ricerca nel browser per repo_token .
 repo_token: get-this-from-repo-settings-on-coveralls.io

Questo token associa i tuoi dati di copertura a un repository su Coveralls. Ora aggiungi il comando di coverage alla sezione degli scripts del tuo file package.json :

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

Questo comando carica il rapporto di copertura nella cartella .nyc_output su coveralls.io. Attiva la connessione a Internet ed esegui:

 yarn coverage

Questo dovrebbe caricare il rapporto sulla copertura esistente nelle tute. Aggiorna la pagina repo sulle tute per vedere il rapporto completo.

Nella pagina dei dettagli, scorri verso il basso per trovare la sezione BADGE YOUR REPO . Fai clic sul menu a discesa EMBED e copia il codice markdown e incollalo nel tuo file README .

Codice Clima

Code Climate è uno strumento che ci aiuta a misurare la qualità del codice. Ci mostra le metriche di manutenzione controllando il nostro codice rispetto ad alcuni schemi definiti. Rileva cose come ripetizioni non necessarie e cicli for nidificati in modo profondo. Raccoglie anche i dati sulla copertura dei test, proprio come coveralls.io.

  1. Visita codeclimate.com e fai clic su "Registrati con GitHub". Accedi se hai già un account.
  2. Una volta nella dashboard, fai clic su Add a repository .
  3. Trova il repository express-api-template dall'elenco e fai clic su Add Repo repository .
  4. Attendi il completamento della build e reindirizza alla dashboard del repository.
  5. In Codebase Summary , fare clic su Test Coverage . Nel menu Test coverage , copia l' TEST REPORTER ID e incollalo nel tuo .travis.yml come valore di CC_TEST_REPORTER_ID .
  6. Sempre nella stessa pagina, nella navigazione di sinistra, sotto EXTRAS , cliccare su Badge. Copia i badge di maintainability e test coverage in formato markdown e incollali nel file README.md .

È importante notare che esistono due modi per configurare i controlli di manutenibilità. Ci sono le impostazioni predefinite che vengono applicate a ogni repository, ma se lo desideri, puoi fornire un file .codeclimate.yml nella radice del tuo progetto. Userò le impostazioni predefinite, che puoi trovare nella scheda Maintainability della pagina delle impostazioni del repository. Ti incoraggio a dare almeno un'occhiata. Se desideri comunque configurare le tue impostazioni, questa guida ti fornirà tutte le informazioni di cui hai bisogno.

AppVeyor

AppVeyor e Travis CI sono entrambi corridori di test automatizzati. La differenza principale è che travis-ci esegue i test in un ambiente Linux mentre AppVeyor esegue i test in un ambiente Windows. Questa sezione è inclusa per mostrare come iniziare con AppVeyor.

  • Visita AppVeyor e accedi o registrati.
  • Nella pagina successiva, clicca su NEW PROJECT .
  • Dall'elenco dei repository, trova il repository express-api-template . Passa il mouse su di esso e fai clic su ADD .
  • Fare clic sulla scheda Settings . Fare clic su Environment nella barra di navigazione a sinistra. Aggiungi TEST_ENV_VARIABLE e il suo valore. Fai clic su "Salva" in fondo alla pagina.
  • Crea il file appveyor.yml nella radice del tuo progetto e incolla il codice seguente.
 environment: matrix: - nodejs_version: "12" install: - yarn test_script: - yarn test build: off

Questo codice indica ad AppVeyor di eseguire i nostri test utilizzando Node.js v12. Installiamo quindi le dipendenze del nostro progetto con il comando yarn . test_script specifica il comando per eseguire il nostro test. L'ultima riga dice ad AppVeyor di non creare una cartella di build.

Fare clic sulla scheda Settings . Nella navigazione a sinistra, fai clic sui badge. Copia il codice markdown e incollalo nel tuo file README.md .

Conferma il tuo codice e invialo a GitHub. Se hai fatto tutto come indicato, tutti i test dovrebbero essere superati e dovresti vedere i tuoi nuovi badge scintillanti come mostrato di seguito. Controlla di nuovo di aver impostato le variabili d'ambiente su Travis e AppVeyor.

Badge CI/CD Repo. (Grande anteprima)

Ora è un buon momento per impegnare i nostri cambiamenti.

  • Il ramo corrispondente nel mio repository è 05-ci.

Aggiunta di un controller

Attualmente, stiamo gestendo la richiesta GET all'URL radice, /v1 , all'interno di src/routes/index.js . Funziona come previsto e non c'è niente di sbagliato in questo. Tuttavia, man mano che la tua applicazione cresce, vuoi mantenere le cose in ordine. Vuoi che le preoccupazioni siano separate: vuoi una chiara separazione tra il codice che gestisce la richiesta e il codice che genera la risposta che verrà rispedita al cliente. Per raggiungere questo obiettivo, scriviamo controllers . I controller sono semplicemente funzioni che gestiscono le richieste provenienti da un determinato URL.

Per iniziare, crea una cartella controllers/ all'interno della cartella src/ . All'interno controllers vengono creati due file: index.js e home.js . Vorremmo esportare le nostre funzioni da index.js . Puoi nominare home.js qualsiasi cosa tu voglia, ma in genere vuoi nominare i controller in base a ciò che controllano. Ad esempio, potresti avere un file usersController.js per contenere tutte le funzioni relative agli utenti nella tua app.

Apri src/controllers/home.js e inserisci il codice qui sotto:

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

Noterai che abbiamo spostato solo la funzione che gestisce la richiesta per il percorso / .

Apri src/controllers/index.js e inserisci il codice seguente.

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

Esportiamo tutto dal file home.js. Questo ci consente di abbreviare le nostre istruzioni di importazione per import { indexPage } from '../controllers';

Apri src/routes/index.js e sostituisci il codice lì con quello qui sotto:

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

L'unico cambiamento qui è che abbiamo fornito una funzione per gestire la richiesta alla / route.

Hai appena scritto con successo il tuo primo controller. Da qui si tratta di aggiungere più file e funzioni secondo necessità.

Vai avanti e gioca con l'app aggiungendo altri percorsi e controller. Puoi aggiungere un percorso e un controller per la pagina Informazioni. Ricordati di aggiornare il tuo test, però.

Esegui il yarn test per confermare che non abbiamo rotto nulla. La tua prova passa? Questo è figo.

Questo è un buon punto per confermare le nostre modifiche.

  • Il ramo corrispondente nel mio repository è 06-controllers.

Connettere il database PostgreSQL e scrivere un modello

Il nostro controller attualmente restituisce messaggi di testo codificati. In un'app del mondo reale, spesso abbiamo bisogno di archiviare e recuperare informazioni da un database. In questa sezione, collegheremo la nostra app a un database PostgreSQL.

Implementeremo la memorizzazione e il recupero di semplici messaggi di testo utilizzando un database. Abbiamo due opzioni per impostare un database: potremmo fornirne uno da un server cloud o potremmo configurarne uno locale.

Ti consiglierei di eseguire il provisioning di un database da un server 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.

Apri query.js e incolla il codice seguente:

 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';

In questo file, definiamo tre stringhe di query SQL. La prima query elimina e ricrea la tabella dei messages . La seconda query inserisce due righe nella tabella dei messages . Sentiti libero di aggiungere altri articoli qui. L'ultima query elimina/elimina la tabella dei messages .

Apri queryFunctions.js e incolla il codice seguente:

 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 ]);

Qui creiamo funzioni per eseguire le query che abbiamo definito in precedenza. Si noti che la funzione executeQueryArray esegue una matrice di query e attende il completamento di ciascuna all'interno del ciclo. (Non fare una cosa del genere nel codice di produzione però). Quindi, risolviamo la promessa solo dopo aver eseguito l'ultima query nell'elenco. Il motivo per l'utilizzo di un array è che il numero di tali query aumenterà all'aumentare del numero di tabelle nel nostro database.

Apri runQuery.js e incolla il codice seguente:

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

Qui è dove eseguiamo le funzioni per creare la tabella e inserire i messaggi nella tabella. Aggiungiamo un comando nella sezione scripts del nostro package.json per eseguire questo file.

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

Ora esegui:

 yarn runQuery

Se ispezioni il tuo database, vedrai che la tabella dei messages è stata creata e che i messaggi sono stati inseriti nella tabella.

Se stai utilizzando ElephantSQL, nella pagina dei dettagli del database, fai clic su BROWSER dal menu di navigazione a sinistra. Selezionare la tabella dei messages e fare clic su Execute . Dovresti vedere i messaggi dal file query.js .

Creiamo un controller e un percorso per visualizzare i messaggi dal nostro database.

Crea un nuovo file controller src/controllers/messages.js e incolla il codice seguente:

 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 }); } };

Importiamo la nostra classe Model e creiamo una nuova istanza di quel modello. Questo rappresenta la tabella dei messages nel nostro database. Usiamo quindi il metodo select del modello per interrogare il nostro database. I dati ( name e message ) che otteniamo vengono inviati come JSON nella risposta.

Definiamo il controller messagesPage come una funzione async . Poiché le query node-postgres restituiscono una promessa, await il risultato di quella query. Se incontriamo un errore durante la query, lo catturiamo e mostriamo lo stack all'utente. Dovresti decidere come scegliere di gestire l'errore.

Aggiungi l'endpoint get messaggi a src/routes/index.js e aggiorna la riga di importazione.

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

Visita https://localhost:3000/v1/messages e dovresti vedere i messaggi visualizzati come mostrato di seguito.

Messaggi dal database. (Grande anteprima)

Ora aggiorniamo il nostro file di prova. Quando esegui TDD, di solito scrivi i tuoi test prima di implementare il codice che fa passare il test. Sto adottando l'approccio opposto qui perché stiamo ancora lavorando alla configurazione del database.

Crea un nuovo file, hooks.js nella cartella test/ e inserisci il codice seguente:

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

Quando il nostro test inizia, Mocha trova questo file e lo esegue prima di eseguire qualsiasi file di test. Esegue l'hook before per creare il database e inserire alcuni elementi in esso. I file di test vengono quindi eseguiti dopo. Una volta terminato il test, Mocha esegue l' after hook in cui rilasciamo il database. Ciò garantisce che ogni volta che eseguiamo i nostri test, lo facciamo con record nuovi e puliti nel nostro database.

Crea un nuovo file di test test/messages.test.js e aggiungi il codice seguente:

 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(); }); }); });

Affermiamo che il risultato della chiamata a /messages è un array. Per ogni oggetto messaggio, affermiamo che ha la proprietà name e message .

Il passaggio finale in questa sezione consiste nell'aggiornare i file CI.

Aggiungi le seguenti sezioni al file .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

Questo indica a Travis di avviare un database PostgreSQL 10 prima di eseguire i nostri test.

Aggiungi il comando per creare il database come prima voce nella sezione before_script :

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

Crea la variabile di ambiente CONNECTION_STRING su Travis e utilizza il valore seguente:

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

Aggiungi le seguenti sezioni al file .appveyor.yml :

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

Aggiungi la variabile di ambiente della stringa di connessione all'appveyor. Usa la riga seguente:

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

Ora salva le modifiche ed esegui il push su GitHub. I tuoi test dovrebbero superare sia Travis CI che AppVeyor.

  • Il ramo corrispondente nel mio repository è 07-connect-postgres.

Nota : spero che tutto funzioni bene da parte tua, ma nel caso in cui dovessi avere problemi per qualche motivo, puoi sempre controllare il mio codice nel repository!

Ora, vediamo come possiamo aggiungere un messaggio al nostro database. Per questo passaggio, avremo bisogno di un modo per inviare richieste POST al nostro URL. Userò Postman per inviare richieste POST .

Seguiamo il percorso TDD e aggiorniamo il nostro test per riflettere ciò che ci aspettiamo di ottenere.

Apri test/message.test.js e aggiungi il test case seguente:

 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(); }); });

Questo test effettua una richiesta POST all'endpoint /v1/messages e ci aspettiamo che venga restituito un array. Verifichiamo anche le proprietà id , name e message sull'array.

Esegui i tuoi test per vedere che questo caso non riesce. Ora sistemiamolo.

Per inviare richieste di posta, utilizziamo il metodo di posta del server. Inviamo anche il nome e il messaggio che vogliamo inserire. Ci aspettiamo che la risposta sia un array, con un id proprietà e le altre informazioni che compongono la query. L' id è la prova che un record è stato inserito nel database.

Apri src/models/model.js e aggiungi il metodo di insert :

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

Questo è il metodo che ci permette di inserire i messaggi nel database. Dopo aver inserito l'oggetto, restituisce l' id , il name e message .

Apri src/controllers/messages.js e aggiungi il controller seguente:

 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 }); } };

Destrutturiamo il corpo della richiesta per ottenere il nome e il messaggio. Quindi usiamo i valori per formare una stringa di query SQL che poi eseguiamo con il metodo insertWithReturn del nostro modello.

Aggiungi l'endpoint POST sottostante a /src/routes/index.js e aggiorna la tua riga di importazione.

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

Esegui i tuoi test per vedere se passano.

Apri Postman e invia una richiesta POST all'endpoint dei messages . Se hai appena eseguito il test, ricorda di eseguire la yarn query per ricreare la tabella dei messages .

 yarn query 
Richiesta POST all'endpoint dei messaggi. (Grande anteprima)
GET richiesta che mostra il messaggio appena aggiunto. (Grande anteprima)

Conferma le modifiche ed esegui il push su GitHub. I tuoi test dovrebbero superare sia Travis che AppVeyor. La copertura del test diminuirà di alcuni punti, ma va bene.

  • Il ramo corrispondente sul mio repository è 08-post-to-db.

Middleware

La nostra discussione su Express non sarà completa senza parlare di middleware. La documentazione Express descrive un middleware come:

“[...] funzioni che hanno accesso all'oggetto richiesta ( req ), all'oggetto risposta ( res ) e alla successiva funzione middleware nel ciclo richiesta-risposta dell'applicazione. La funzione middleware successiva è comunemente indicata da una variabile denominata next .”

Un middleware può eseguire un numero qualsiasi di funzioni come l'autenticazione, la modifica del corpo della richiesta e così via. Consulta la documentazione di Express sull'uso del middleware.

Scriveremo un semplice middleware che modifichi il corpo della richiesta. Il nostro middleware aggiungerà la parola SAYS: al messaggio in arrivo prima che venga salvato nel database.

Prima di iniziare, modifichiamo il nostro test per riflettere ciò che vogliamo ottenere.

Apri test/messages.test.js e modifica l'ultima riga attesa nel test case dei posts message :

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

Stiamo affermando che la stringa SAYS: è stata aggiunta al messaggio. Esegui i tuoi test per assicurarti che questo test case abbia esito negativo.

Ora, scriviamo il codice per far passare il test.

Crea una nuova cartella middleware/ all'interno della cartella src/ . Crea due file all'interno di questa cartella:

  • middleware.js
  • index.js

Inserisci il codice seguente in middleware.js :

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

Qui aggiungiamo la stringa SAYS: al messaggio nel corpo della richiesta. Dopo averlo fatto, dobbiamo chiamare la funzione next() per passare l'esecuzione alla funzione successiva nella catena richiesta-risposta. Ogni middleware deve chiamare la funzione next per passare l'esecuzione al middleware successivo nel ciclo richiesta-risposta.

Inserisci il codice seguente in index.js :

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

Questo esporta il middleware che abbiamo nel file /middleware.js . Per ora, abbiamo solo il middleware modifyMessage .

Apri src/routes/index.js e aggiungi il middleware alla catena di richiesta-risposta del messaggio di posta.

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

Possiamo vedere che la funzione modifyMessage viene prima della funzione addMessage . Invochiamo la funzione addMessage chiamando next nel middleware modifyMessage . Come esperimento, commenta la riga next() nel mezzo di modifyMessage e osserva la richiesta bloccarsi.

Apri Postman e crea un nuovo messaggio. Dovresti vedere la stringa aggiunta.

Messaggio modificato dal middleware. (Grande anteprima)

Questo è un buon punto per confermare le nostre modifiche.

  • Il ramo corrispondente nel mio repository è 09-middleware.

Gestione degli errori e middleware asincrono

Gli errori sono inevitabili in qualsiasi applicazione. Il compito prima dello sviluppatore è come gestire gli errori nel modo più elegante possibile.

In espresso:

La gestione degli errori si riferisce al modo in cui Express rileva ed elabora gli errori che si verificano sia in modo sincrono che asincrono.

Se stessimo scrivendo solo funzioni sincrone, potremmo non doverci preoccupare così tanto della gestione degli errori poiché Express fa già un ottimo lavoro nel gestirli. Secondo i documenti:

"Gli errori che si verificano nel codice sincrono all'interno di gestori di route e middleware non richiedono lavoro aggiuntivo".

Ma una volta che iniziamo a scrivere gestori di router e middleware asincroni, dobbiamo eseguire un po' di gestione degli errori.

Il nostro middleware modifyMessage è una funzione sincrona. Se si verifica un errore in quella funzione, Express lo gestirà correttamente. Vediamo come gestiamo gli errori nel middleware asincrono.

Diciamo che, prima di creare un messaggio, vogliamo ottenere un'immagine dall'API Lorem Picsum utilizzando questo URL https://picsum.photos/id/0/info . Questa è un'operazione asincrona che potrebbe avere successo o fallire e che rappresenta un caso da affrontare.

Inizia installando Axios.

 # install axios yarn add axios

Apri src/middleware/middleware.js e aggiungi la seguente funzione:

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

In questa funzione async , await una chiamata a un'API (in realtà non abbiamo bisogno dei dati restituiti) e successivamente chiamiamo la funzione next nella catena di richieste. Se la richiesta non riesce, catturiamo l'errore e lo passiamo a next . Una volta che Express vede questo errore, salta tutti gli altri middleware nella catena. Se non abbiamo chiamato next(err) , la richiesta si bloccherà. Se abbiamo chiamato solo next() senza err , la richiesta procederà come se non fosse successo nulla e l'errore non verrà rilevato.

Importa questa funzione e aggiungila alla catena del middleware del percorso dei messaggi di posta:

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

Apri src/app.js e aggiungi il codice seguente appena prima della riga export default app .

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

Questo è il nostro gestore degli errori. Secondo il documento di gestione degli errori Express:

"[...] le funzioni di gestione degli errori hanno quattro argomenti invece di tre: (err, req, res, next) ."

Nota che questo gestore di errori deve essere l'ultimo, dopo ogni chiamata app.use() . Una volta riscontrato un errore, restituiamo la traccia dello stack con un codice di stato di 400 . Potresti fare quello che vuoi con l'errore. Potresti registrarlo o inviarlo da qualche parte.

Questo è un buon posto per confermare le modifiche.

  • Il ramo corrispondente nel mio repository è 10-async-middleware.

Distribuisci su Heroku

  1. Per iniziare, vai su https://www.heroku.com/ e accedi o registrati.
  2. Scarica e installa Heroku CLI da qui.
  3. Aprire un terminale nella cartella del progetto per eseguire il comando.
 # login to heroku on command line heroku login

Si aprirà una finestra del browser e ti chiederà di accedere al tuo account Heroku.

Accedi per concedere al tuo terminale l'accesso al tuo account Heroku e crea una nuova app heroku eseguendo:

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

Questo creerà l'app su Heroku e restituirà due URL.

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

Copia l'URL a destra ed esegui il comando seguente. Nota che questo passaggio è facoltativo poiché potresti scoprire che Heroku ha già aggiunto l'URL remoto.

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

Apri un terminale laterale ed esegui il comando seguente. Questo ti mostra l'accesso dell'app in tempo reale come mostrato nell'immagine.

 # see process logs heroku logs --tail
Registri di Heroku. (Grande anteprima)

Esegui i tre comandi seguenti per impostare le variabili di ambiente richieste:

 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

Ricorda che nei nostri script abbiamo impostato:

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

Per avviare l'app, è necessario compilarla fino a ES5 utilizzando babel nella fase di prestart perché babel esiste solo nelle nostre dipendenze di sviluppo. Dobbiamo impostare NPM_CONFIG_PRODUCTION su false per poter installare anche quelli.

Per confermare che tutto sia impostato correttamente, esegui il comando seguente. Puoi anche visitare la scheda delle settings nella pagina dell'app e fare clic su Reveal Config Vars .

 # check configuration variables heroku config

Ora esegui git push heroku .

Per aprire l'app, eseguire:

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

Se come me stai utilizzando lo stesso database PostgresSQL sia per lo sviluppo che per la produzione, potresti scoprire che ogni volta che esegui i test, il database viene eliminato. Per ricrearlo, puoi eseguire uno dei seguenti comandi:

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

Distribuzione continua (CD) con Travis

Aggiungiamo ora la distribuzione continua (CD) per completare il flusso CI/CD. Verrà distribuito da Travis dopo ogni test eseguito con successo.

Il primo passo è installare Travis CI. (Puoi trovare le istruzioni di installazione qui.) Dopo aver installato correttamente Travis CI, accedi eseguendo il comando seguente. (Nota che questo dovrebbe essere fatto nel repository del tuo progetto.)

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

Se il tuo progetto è ospitato su travis-ci.org, rimuovi il flag --pro . Per ottenere un token GitHub, visita la pagina delle impostazioni dello sviluppatore del tuo account e generane uno. Questo vale solo se il tuo account è protetto con 2FA.

Apri il tuo .travis.yml e aggiungi una sezione deploy:

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

Qui specifichiamo che vogliamo schierarci su Heroku. La sottosezione dell'app specifica che vogliamo distribuire il ramo master del nostro repository app-name dell'app su Heroku. È possibile distribuire rami diversi in app diverse. Puoi leggere di più sulle opzioni disponibili qui.

Esegui il comando seguente per crittografare la tua chiave API Heroku e aggiungerla alla sezione di distribuzione:

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

Ciò aggiungerà la sottosezione seguente alla sezione di distribuzione.

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

Ora salva le modifiche ed esegui il push su GitHub mentre monitori i tuoi log. Vedrai la build attivata non appena il test di Travis è terminato. In questo modo, se abbiamo un test fallito, le modifiche non verrebbero mai implementate. Allo stesso modo, se la compilazione fallisce, l'intera esecuzione del test fallirà. Questo completa il flusso CI/CD.

  • Il ramo corrispondente nel mio repository è 11-cd.

Conclusione

Se sei arrivato così lontano, dico "Police in su!" In questo tutorial, abbiamo impostato correttamente un nuovo progetto Express. Siamo andati avanti per configurare le dipendenze di sviluppo e l'integrazione continua (CI). Abbiamo quindi scritto funzioni asincrone per gestire le richieste ai nostri endpoint API, completate con i test. Abbiamo quindi esaminato brevemente la gestione degli errori. Infine, abbiamo distribuito il nostro progetto su Heroku e configurato la distribuzione continua.

Ora hai un modello per il tuo prossimo progetto di back-end. Abbiamo fatto solo abbastanza per farti iniziare, ma dovresti continuare a imparare per andare avanti. Assicurati di controllare anche i documenti di Express.js. Se preferisci usare MongoDB invece di PostgreSQL , ho un modello qui che fa esattamente questo. Puoi verificarlo per l'installazione. Ha solo pochi punti di differenza.

Risorse

  • "Crea backend API Express con MongoDB ", Orji Chidi Matthew, GitHub
  • "Una breve guida per collegare il middleware", Stephen Sugden
  • "Modello API Express", GitHub
  • "AppVeyor vs Travis CI", StackShare
  • "The Heroku CLI", Heroku Dev Center
  • "Distribuzione Heroku", Travis CI
  • "Utilizzo del middleware", Express.js
  • "Gestione degli errori", Express.js
  • "Per iniziare", Mocha
  • nyc (GitHub)
  • ElephantSQL
  • Postino
  • Esprimere
  • Travis CI
  • Codice Clima
  • PostgreSQL
  • pgAdmin