Come impostare un progetto di backend API Express con PostgreSQL
Pubblicato: 2022-03-10Adotteremo 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.
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.
- Elimina il file index/users.js .
- Elimina le cartelle
public/
eviews/
. - Rinomina il file bin/www in bin/www.js .
- Disinstalla
jade
con il comandoyarn remove jade
. - 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. - 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"
-
prestart
scripts compila il contenuto della cartellasrc/
e lo inserisce nella cartellabuild/
. Quando si esegue il comandoyarn start
, questo script viene eseguito prima dello script distart
. - lo script
start
ora serve il contenuto della cartellabuild/
invece della cartellasrc/
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. -
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 usandobabel-node
per eseguire l'app invece del normalenode
. Il flag--exec
forzababel-node
a servire la cartellasrc/
. Per lo script distart
, utilizziamonode
poiché i file nella cartellabuild/
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.
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.
- Visita travis-ci.com o travis-ci.org e crea un account se non ne hai uno. Devi registrarti con il tuo account GitHub.
- Passa il mouse sopra la freccia del menu a discesa accanto all'immagine del tuo profilo e fai clic su
settings
. - Nella scheda
Repositories
fai clic suManage repositories on Github
per essere reindirizzato a Github. - Nella pagina GitHub, scorri verso il basso fino a
Repository access
e fai clic sulla casella di controllo accanto aOnly select repositories
. - Fai clic sul menu a discesa
Select repositories
e trova il repositoryexpress-api-template
. Fare clic per aggiungerlo all'elenco dei repository che si desidera aggiungere atravis-ci
. - Fai clic su
Approve and install
e attendi di essere reindirizzato atravis-ci
. - 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. - Copia il codice risultante e incollalo nel tuo file README.md .
- Nella pagina del progetto, fai clic su
More options
>Settings
. Nella sezioneEnvironment Variables
, aggiungi la variabile envTEST_ENV_VARIABLE
. Quando inserisci il suo valore, assicurati di averlo tra virgolette come questa"Environment variable is coming across."
- 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.
- Visita coveralls.io e accedi o registrati con il tuo account Github.
- Passa il mouse sul lato sinistro dello schermo per visualizzare il menu di navigazione. Fare clic su
ADD REPOS
. - 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 suSYNC 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. - Fare clic su dettagli per andare alla pagina dei dettagli del repository.
- 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 perrepo_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.
- Visita codeclimate.com e fai clic su "Registrati con GitHub". Accedi se hai già un account.
- Una volta nella dashboard, fai clic su
Add a repository
. - Trova il repository
express-api-template
dall'elenco e fai clic suAdd Repo
repository . - Attendi il completamento della build e reindirizza alla dashboard del repository.
- In
Codebase Summary
, fare clic suTest Coverage
. Nel menuTest coverage
, copia l'TEST REPORTER ID
e incollalo nel tuo .travis.yml come valore diCC_TEST_REPORTER_ID
. - Sempre nella stessa pagina, nella navigazione di sinistra, sotto
EXTRAS
, cliccare su Badge. Copia i badge dimaintainability
etest 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 suADD
. - Fare clic sulla scheda
Settings
. Fare clic suEnvironment
nella barra di navigazione a sinistra. AggiungiTEST_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.
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.
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.
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
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 denominatanext
.”
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.
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
- Per iniziare, vai su https://www.heroku.com/ e accedi o registrati.
- Scarica e installa Heroku CLI da qui.
- 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
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