Cum să configurați un proiect de backend API Express cu PostgreSQL
Publicat: 2022-03-10Vom adopta o abordare Test-Driven Development (TDD) și configurarea jobului de Integrare continuă (CI) pentru a rula automat testele noastre pe Travis CI și AppVeyor, complet cu raportarea calității codului și a acoperirii. Vom afla despre controlere, modele (cu PostgreSQL), gestionarea erorilor și middleware Express asincron. În cele din urmă, vom finaliza conducta CI/CD prin configurarea implementării automate pe Heroku.
Sună mult, dar acest tutorial se adresează începătorilor care sunt gata să-și încerce un proiect back-end cu un anumit nivel de complexitate și care ar putea fi încă confuzi cu privire la modul în care toate piesele se potrivesc într-un proiect real. .
Este robust, fără a fi copleșitor și este împărțit în secțiuni pe care le puteți finaliza într-un timp rezonabil.
Noțiuni de bază
Primul pas este să creați un nou director pentru proiect și să începeți un nou proiect de nod. Node este necesar pentru a continua cu acest tutorial. Dacă nu îl aveți instalat, accesați site-ul web oficial, descărcați-l și instalați-l înainte de a continua.
Voi folosi yarn ca manager de pachete pentru acest proiect. Există instrucțiuni de instalare pentru sistemul dvs. de operare specific aici. Simțiți-vă liber să utilizați npm dacă doriți.
Deschideți terminalul, creați un director nou și începeți un proiect Node.js.
# create a new directory mkdir express-api-template # change to the newly-created directory cd express-api-template # initialize a new Node.js project npm init
Răspundeți la întrebările care urmează pentru a genera un fișier package.json . Acest fișier conține informații despre proiectul dvs. Un exemplu de astfel de informații include dependențele pe care le folosește, comanda de pornire a proiectului și așa mai departe.
Acum puteți deschide folderul de proiect în editorul dorit. Folosesc codul de studio vizual. Este un IDE gratuit cu o mulțime de plugin-uri pentru a vă ușura viața și este disponibil pentru toate platformele majore. Îl puteți descărca de pe site-ul oficial.
Creați următoarele fișiere în folderul proiectului:
- README.md
- .editorconfig
Iată o descriere a ceea ce face .editorconfig de pe site-ul web EditorConfig. (Probabil că nu ai nevoie de el dacă lucrezi singur, dar nu dăunează, așa că îl voi lăsa aici.)
„EditorConfig ajută la menținerea unor stiluri de codare consistente pentru mai mulți dezvoltatori care lucrează la același proiect în diferite editori și IDE-uri.”
Deschideți .editorconfig
și inserați următorul cod:
root = true [*] indent_style = space indent_size = 2 charset = utf-8 trim_trailing_whitespace = false insert_final_newline = true
[*]
înseamnă că dorim să aplicăm regulile care intră sub incidența acestuia la fiecare fișier din proiect. Vrem o dimensiune de indentare de două spații și un set de caractere UTF-8
. De asemenea, dorim să tăiem spațiul alb final și să inserăm o linie goală finală în fișierul nostru.
Deschideți README.md și adăugați numele proiectului ca element de prim nivel.
# Express API template
Să adăugăm imediat controlul versiunilor.
# initialize the project folder as a git repository git init
Creați un fișier .gitignore și introduceți următoarele rânduri:
node_modules/ yarn-error.log .env .nyc_output coverage build/
Acestea sunt toate fișierele și folderele pe care nu dorim să le urmărim. Nu le avem încă în proiectul nostru, dar le vom vedea pe măsură ce continuăm.
În acest moment, ar trebui să aveți următoarea structură de foldere.
EXPRESS-API-TEMPLATE ├── .editorconfig ├── .gitignore ├── package.json └── README.md
Consider că acesta este un punct bun pentru a-mi comite modificările și pentru a le trimite către GitHub.
Pornirea unui nou proiect Express
Express este un cadru Node.js pentru construirea de aplicații web. Potrivit site-ului oficial, este un
Cadru web rapid, fără păreri, minimalist pentru Node.js.
Există și alte cadre grozave de aplicații web pentru Node.js, dar Express este foarte popular, cu peste 47.000 de stele GitHub la momentul scrierii acestui articol.
În acest articol, nu vom avea multe discuții despre toate părțile care compun Express. Pentru acea discuție, vă recomand să verificați seria lui Jamie. Prima parte este aici, iar a doua parte este aici.
Instalați Express și începeți un nou proiect Express. Este posibil să configurați manual un server Express de la zero, dar pentru a ne ușura viața, vom folosi generatorul expres pentru a configura scheletul aplicației.
# 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
Indicatorul -f
forțează Express să creeze proiectul în directorul curent.
Acum vom efectua câteva operațiuni de curățenie.
- Ștergeți fișierul index/users.js .
- Ștergeți folderele
public/
șiviews/
. - Redenumiți fișierul bin/www în bin/www.js .
- Dezinstalați
jade
cu comandayarn remove jade
. - Creați un nou folder numit
src/
și mutați următoarele în interiorul acestuia: 1. fișier app.js 2.bin/
folder 3.routes/
folder în interior. - Deschideți package.json și actualizați scriptul de
start
pentru a arăta ca mai jos.
"start": "node ./src/bin/www"
În acest moment, structura folderului de proiect arată ca mai jos. Puteți vedea cum VS Code evidențiază modificările de fișiere care au avut loc.
EXPRESS-API-TEMPLATE ├── node_modules ├── src | ├── bin │ │ ├── www.js │ ├── routes │ | ├── index.js │ └── app.js ├── .editorconfig ├── .gitignore ├── package.json ├── README.md └── yarn.lock
Deschideți src/app.js și înlocuiți conținutul cu codul de mai jos.
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;
După ce am solicitat niște biblioteci, îi instruim pe Express să gestioneze fiecare cerere care vine la /v1
cu indexRouter
.
Înlocuiți conținutul routes/index.js cu codul de mai jos:
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;
Luăm Express, creăm un router din acesta și servim ruta /
, care returnează un cod de stare de 200
și un mesaj JSON.
Porniți aplicația cu comanda de mai jos:
# start the app yarn start
Dacă ați configurat totul corect, ar trebui să vedeți doar $ node ./src/bin/www
în terminalul dvs.
Accesați https://localhost:3000/v1
în browser. Ar trebui să vedeți următorul mesaj:
{ "message": "Welcome to Express API template" }
Acesta este un punct bun pentru a ne angaja schimbările.
- Ramura corespunzătoare din depozitul meu este 01-install-express.
Convertirea codului nostru în ES6
Codul generat de express-generator
este în ES5
, dar în acest articol vom scrie tot codul nostru în sintaxa ES6
. Deci, să convertim codul nostru existent în ES6
.
Înlocuiți conținutul routes/index.js cu codul de mai jos:
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;
Este același cod pe care l-am văzut mai sus, dar cu instrucțiunea de import și o funcție de săgeată în gestionarea rutei /
.
Înlocuiți conținutul src/app.js cu codul de mai jos:
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;
Să aruncăm o privire acum la conținutul src/bin/www.js . Îl vom construi treptat. Ștergeți conținutul src/bin/www.js
și inserați în blocul de cod de mai jos.
#!/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
Acest cod verifică dacă este specificat un port personalizat în variabilele de mediu. Dacă nu este setată niciuna, valoarea implicită a portului de 3000
este setată pe instanța aplicației, după ce a fost normalizată fie la un șir, fie la un număr de normalizePort
. Serverul este apoi creat din modulul http
, cu app
ca funcție de apel invers.
Linia de #!/usr/bin/env node
este opțională, deoarece vom specifica nodul atunci când dorim să executăm acest fișier. Dar asigurați-vă că este pe linia 1 a fișierului src/bin/www.js sau eliminați-l complet.
Să aruncăm o privire la funcția de tratare a erorilor. Copiați și lipiți acest bloc de cod după linia în care este creat serverul.
/** * 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);
Funcția onError
ascultă erorile de pe serverul http și afișează mesajele de eroare adecvate. Funcția onListening
pur și simplu scoate la consolă portul pe care îl ascultă serverul. În cele din urmă, serverul ascultă cererile primite la adresa și portul specificate.
În acest moment, tot codul nostru existent este în sintaxa ES6
. Opriți serverul (utilizați Ctrl + C ) și rulați yarn start
. Veți primi o eroare SyntaxError: Invalid or unexpected token
. Acest lucru se întâmplă deoarece Node (la momentul scrierii) nu acceptă o parte din sintaxa pe care am folosit-o în codul nostru.
Acum vom remedia asta în secțiunea următoare.
Configurarea dependențelor de dezvoltare: babel
, nodemon
, eslint
și prettier
Este timpul să setăm majoritatea scripturilor de care vom avea nevoie în această fază a proiectului.
Instalați bibliotecile necesare cu comenzile de mai jos. Puteți doar să copiați totul și să-l lipiți în terminalul dvs. Rândurile de comentarii vor fi sărite.
# install babel scripts yarn add @babel/cli @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/register @babel/runtime @babel/node --dev
Aceasta instalează toate scripturile babel listate ca dependențe de dezvoltare. Verificați fișierul package.json și ar trebui să vedeți o secțiune devDependencies
. Toate scripturile instalate vor fi listate acolo.
Scripturile babel pe care le folosim sunt explicate mai jos:
@babel/cli | O instalare necesară pentru utilizarea babel . Permite utilizarea Babel din terminal și este disponibil ca ./node_modules/.bin/babel . |
@babel/core | Funcționalitatea de bază Babel. Aceasta este o instalare necesară. |
@babel/node | Acest lucru funcționează exact ca CLI-ul Node.js, cu avantajul suplimentar de a compila cu presetări și pluginuri babel . Acest lucru este necesar pentru utilizare cu nodemon . |
@babel/plugin-transform-runtime | Acest lucru ajută la evitarea dublării în rezultatul compilat. |
@babel/preset-env | O colecție de pluginuri care sunt responsabile pentru realizarea transformărilor de cod. |
@babel/register | Acesta compileazã fișierele din mers și este specificat ca o cerință în timpul testelor. |
@babel/runtime | Acest lucru funcționează împreună cu @babel/plugin-transform-runtime . |
Creați un fișier numit .babelrc la rădăcina proiectului și adăugați următorul cod:
{ "presets": ["@babel/preset-env"], "plugins": ["@babel/transform-runtime"] }
Să instalăm nodemon
# install nodemon yarn add nodemon --dev
nodemon
este o bibliotecă care monitorizează codul sursă al proiectului și repornește automat serverul nostru ori de câte ori observă orice modificări.
Creați un fișier numit nodemon.json la rădăcina proiectului și adăugați codul de mai jos:
{ "watch": [ "package.json", "nodemon.json", ".eslintrc.json", ".babelrc", ".prettierrc", "src/" ], "verbose": true, "ignore": ["*.test.js", "*.spec.js"] }
Tasta watch
îi spune lui nodemon
ce fișiere și foldere să urmărească pentru modificări. Deci, ori de câte ori oricare dintre aceste fișiere se modifică, nodemon repornește serverul. Tasta de ignore
îi spune fișierelor să nu urmărească modificări.
Acum actualizați secțiunea de scripts
a fișierului package.json pentru a arăta astfel:
# 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 construiește conținutul folderuluisrc/
și îl pune în folderulbuild/
. Cândyarn start
, acest script rulează mai întâi înainte de script-ul destart
. -
start
script-ul servește acum conținutul folderuluibuild/
în loc de folderulsrc/
pe care îl servim anterior. Acesta este scriptul pe care îl veți folosi atunci când difuzați fișierul în producție. De fapt, servicii precum Heroku rulează automat acest script atunci când implementați. -
yarn startdev
este folosit pentru a porni serverul în timpul dezvoltării. De acum încolo vom folosi acest script pe măsură ce dezvoltăm aplicația. Observați că acum folosimbabel-node
pentru a rula aplicația în loc denode
obișnuit. Indicatorul--exec
forțeazăbabel-node
să servească folderulsrc/
. Pentru scriptul destart
, folosimnode
, deoarece fișierele din folderulbuild/
au fost compilate în ES5.
Rulați yarn startdev
și vizitați https://localhost:3000/v1. Serverul dvs. ar trebui să funcționeze din nou.
Ultimul pas din această secțiune este să configurați ESLint
și prettier
. ESLint ajută la aplicarea regulilor de sintaxă, în timp ce mai frumos ajută la formatarea corectă a codului nostru pentru a fi lizibil.
Adăugați-le pe amândouă cu comanda de mai jos. Ar trebui să rulați acest lucru pe un terminal separat în timp ce observați terminalul pe care rulează serverul nostru. Ar trebui să vedeți repornind serverul. Acest lucru se datorează faptului că monitorizăm fișierul package.json pentru modificări.
# install elsint and prettier yarn add eslint eslint-config-airbnb-base eslint-plugin-import prettier --dev
Acum creați fișierul .eslintrc.json în root
proiectului și adăugați codul de mai jos:
{ "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] } }
Acest fișier definește în principal câteva reguli în funcție de care eslint
va verifica codul nostru. Puteți vedea că extindem regulile de stil folosite de Airbnb.
În secțiunea "rules"
, definim dacă eslint
ar trebui să arate un avertisment sau o eroare atunci când întâlnește anumite încălcări. De exemplu, afișează un mesaj de avertizare pe terminalul nostru pentru orice indentare care nu utilizează 2 spații. O valoare de [0]
dezactivează o regulă, ceea ce înseamnă că nu vom primi un avertisment sau o eroare dacă încălcăm acea regulă.
Creați un fișier numit .prettierrc și adăugați codul de mai jos:
{ "trailingComma": "es5", "tabWidth": 2, "semi": true, "singleQuote": true }
Setăm o lățime a filei de 2
și impunem utilizarea ghilimelelor simple în aplicația noastră. Verificați ghidul mai frumos pentru mai multe opțiuni de stil.
Acum adăugați următoarele scripturi la package.json dvs.:
# add these one after the other "lint": "./node_modules/.bin/eslint ./src" "pretty": "prettier --write '**/*.{js,json}' '!node_modules/**'" "postpretty": "yarn lint --fix"
Rulați yarn lint
. Ar trebui să vedeți o serie de erori și avertismente în consolă.
Comanda pretty
ne înfrumusețează codul. Comanda postpretty
este executată imediat după. Rulează comanda lint
cu --fix
atașat. Acest semnalizare îi spune ESLint
să remedieze automat problemele comune de listing. În acest fel, rulez în mare parte comanda yarn pretty
fără să mă mai chinui de comanda lint
.
Rulați yarn pretty
. Ar trebui să vedeți că avem doar două avertismente despre prezența alert
în fișierul bin/www.js .
Iată cum arată structura proiectului nostru în acest moment.
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
Este posibil să descoperiți că aveți un fișier suplimentar, yarn-error.log
în rădăcina proiectului. Adăugați-l în fișierul .gitignore
. Angajați-vă modificările.
- Ramura corespunzătoare în acest moment al depozitului meu este 02-dev-dependencies.
Setări și variabile de mediu în fișierul nostru .env
În aproape fiecare proiect, veți avea nevoie de un loc unde să stocați setările care vor fi utilizate în aplicația dvs., de exemplu, o cheie secretă AWS. Stocăm astfel de setări ca variabile de mediu. Acest lucru îi ține departe de privirile indiscrete și le putem folosi în aplicația noastră, după cum este necesar.
Îmi place să am un fișier settings.js cu care îmi citesc toate variabilele de mediu. Apoi, mă pot referi la fișierul de setări de oriunde în aplicația mea. Aveți libertatea de a numi acest fișier cum doriți, dar există un fel de consens cu privire la denumirea unor astfel de fișiere settings.js sau config.js .
Pentru variabilele noastre de mediu, le vom păstra într-un fișier .env
și le vom citi în fișierul nostru de settings
de acolo.
Creați fișierul .env la rădăcina proiectului și introduceți linia de mai jos:
TEST_ENV_VARIABLE="Environment variable is coming across"
Pentru a putea citi variabilele de mediu în proiectul nostru, există o bibliotecă frumoasă, dotenv
, care citește fișierul nostru .env
și ne oferă acces la variabilele de mediu definite în interior. Să-l instalăm.
# install dotenv yarn add dotenv
Adăugați fișierul .env la lista de fișiere urmărite de nodemon
.
Acum, creați fișierul settings.js în folderul src/
și adăugați codul de mai jos:
import dotenv from 'dotenv'; dotenv.config(); export const testEnvironmentVariable = process.env.TEST_ENV_VARIABLE;
Importăm pachetul dotenv
și apelăm metoda lui de configurare. Exportăm apoi testEnvironmentVariable
pe care l-am setat în fișierul nostru .env
.
Deschideți src/routes/index.js și înlocuiți codul cu cel de mai jos.
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;
Singura modificare pe care am făcut-o aici este că importăm testEnvironmentVariable
din fișierul nostru de settings
și o folosim ca mesaj de retur pentru o solicitare de la ruta /
.
Vizitați https://localhost:3000/v1 și ar trebui să vedeți mesajul, așa cum se arată mai jos.
{ "message": "Environment variable is coming across." }
Si asta e. De acum încolo putem adăuga câte variabile de mediu dorim și le putem exporta din fișierul nostru settings.js .
Acesta este un punct bun pentru a vă comite codul. Amintiți-vă să vă înfrumusețați și să scame codul.
- Ramura corespunzătoare din repo-ul meu este 03-env-variables.
Scriem primul nostru test
Este timpul să încorporăm testarea în aplicația noastră. Unul dintre lucrurile care oferă dezvoltatorului încredere în codul lor sunt testele. Sunt sigur că ați văzut nenumărate articole pe web care predică Dezvoltarea bazată pe teste (TDD). Nu se poate sublinia suficient că codul tău are nevoie de o anumită măsură de testare. TDD este foarte ușor de urmărit atunci când lucrați cu Express.js.
În testele noastre, vom efectua apeluri către punctele noastre finale API și vom verifica dacă ceea ce este returnat este ceea ce ne așteptăm.
Instalați dependențele necesare:
# install dependencies yarn add mocha chai nyc sinon-chai supertest coveralls --dev
Fiecare dintre aceste biblioteci are propriul rol de jucat în testele noastre.
mocha | alergător de testare |
chai | folosit pentru a face afirmații |
nyc | colectați raportul de acoperire a testului |
sinon-chai | extinde afirmațiile lui chai |
supertest | folosit pentru a efectua apeluri HTTP către punctele noastre finale API |
coveralls | pentru a încărca acoperirea testului pe coveralls.io |
Creați un nou dosar test/
la rădăcina proiectului dvs. Creați două fișiere în acest folder:
- test/setup.js
- test/index.test.js
Mocha va găsi automat test/
dosarul.
Deschideți test/setup.js și inserați codul de mai jos. Acesta este doar un fișier de ajutor care ne ajută să organizăm toate importurile de care avem nevoie în fișierele noastre de testare.
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';
Acesta este ca un fișier de setări, dar pentru testele noastre. În acest fel, nu trebuie să inițializam totul în fiecare dintre fișierele noastre de testare. Deci importăm pachetele necesare și exportăm ceea ce am inițializat - pe care apoi le putem importa în fișierele care au nevoie de ele.
Deschideți index.test.js și inserați următorul cod de 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(); }); }); });
Aici facem o solicitare pentru a obține punctul final de bază, care este /
și afirmăm că res.
obiectul body
are o cheie de message
cu o valoare a Environment variable is coming across.
Dacă nu sunteți familiarizat cu modelul describe
it
vă încurajez să aruncați o privire rapidă la documentul „Noțiuni introductive” al lui Mocha.
Adăugați comanda de testare în secțiunea de scripts
din package.json .
"test": "nyc --reporter=html --reporter=text --reporter=lcov mocha -r @babel/register"
Acest script execută testul nostru cu nyc
și generează trei tipuri de raport de acoperire: un raport HTML, trimis în folderul coverage/
; un raport text trimis către terminal și un raport lcov trimis în folderul .nyc_output/
.
Acum rulați yarn test
. Ar trebui să vedeți un raport text în terminalul dvs. la fel ca cel din fotografia de mai jos.
Observați că sunt generate două foldere suplimentare:
-
.nyc_output/
-
coverage/
Privește în interiorul .gitignore
și vei vedea că deja le ignorăm pe amândouă. Vă încurajez să deschideți coverage/index.html
într-un browser și să vedeți raportul de testare pentru fiecare fișier.
Acesta este un punct bun pentru a vă angaja modificările.
- Ramura corespunzătoare din repo-ul meu este 04-first-test.
Integrare continuă (CD) și insigne: Travis, Combine, Code Climate, AppVeyor
Acum este timpul să configurați instrumentele de integrare și implementare continuă (CI/CD). Vom configura servicii comune, cum ar fi travis-ci
, coveralls
, AppVeyor
și codeclimate
și vom adăuga insigne în fișierul nostru README.
Să începem.
Travis CI
Travis CI este un instrument care rulează testele noastre automat de fiecare dată când împingem un commit către GitHub (și recent, Bitbucket) și de fiecare dată când creăm o cerere de extragere. Acest lucru este util mai ales atunci când faceți solicitări de extragere, arătându-ne dacă noul nostru cod a eșuat vreunul dintre testele noastre.
- Vizitați travis-ci.com sau travis-ci.org și creați un cont dacă nu aveți unul. Trebuie să vă înregistrați cu contul GitHub.
- Treceți cu mouse-ul peste săgeata drop-down de lângă fotografia de profil și faceți clic pe
settings
. - Sub fila
Repositories
, faceți clic peManage repositories on Github
pentru a fi redirecționat către Github. - Pe pagina GitHub, derulați în jos la
Repository access
și faceți clic pe caseta de selectare de lângăOnly select repositories
. - Faceți clic pe meniul derulant
Select repositories
și găsiți depozitulexpress-api-template
. Faceți clic pe el pentru a-l adăuga la lista de depozite pe care doriți să le adăugați latravis-ci
. - Faceți clic pe
Approve and install
și așteptați să fiți redirecționat înapoi latravis-ci
. - În partea de sus a paginii repo, aproape de numele repo, faceți clic pe pictograma
build unknown
. Din modulul Imagine de stare, selectați markdown din meniul drop-down format. - Copiați codul rezultat și inserați-l în fișierul README.md .
- Pe pagina proiectului, faceți clic pe
More options
>Settings
. În secțiuneaEnvironment Variables
, adăugați variabila de mediuTEST_ENV_VARIABLE
. Când introduceți valoarea sa, asigurați-vă că o aveți între ghilimele duble, cum ar fi"Environment variable is coming across."
- Creați fișierul .travis.yml la rădăcina proiectului și inserați codul de mai jos (vom seta valoarea
CC_TEST_REPORTER_ID
în secțiunea 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
Mai întâi, îi spunem lui Travis să ruleze testul nostru cu Node.js, apoi setăm variabila de mediu globală CC_TEST_REPORTER_ID
(vom ajunge la aceasta în secțiunea Code Climate). În secțiunea matrix
, îi spunem lui Travis să ruleze testele noastre cu Node.js v12. De asemenea, dorim să memorăm în cache directorul node_modules/
, astfel încât să nu fie regenerat de fiecare dată.
Ne instalăm dependențele folosind comanda yarn
, care este o prescurtare pentru yarn install
. before_script
și after_script
sunt folosite pentru a încărca rezultatele acoperirii în codeclimate
. Vom configura codeclimate
curând. După ce yarn test
rulează cu succes, dorim să rulăm și yarn coverage
care va încărca raportul nostru de acoperire pe coveralls.io.
Salopete
Salopetele încarcă date de acoperire de testare pentru o vizualizare ușoară. Putem vizualiza acoperirea testului pe mașina noastră locală din folderul de acoperire, dar Coveralls o face disponibilă în afara mașinii noastre locale.
- Vizitați coveralls.io și fie conectați-vă, fie înregistrați-vă cu contul Github.
- Treceți cursorul peste partea stângă a ecranului pentru a afișa meniul de navigare. Faceți clic pe
ADD REPOS
. - Căutați repo-ul
express-api-template
și activați acoperirea folosind butonul de comutare din partea stângă. Dacă nu îl găsiți, faceți clic peSYNC REPOS
în colțul din dreapta sus și încercați din nou. Rețineți că repo-ul dvs. trebuie să fie public, cu excepția cazului în care aveți un cont PRO. - Faceți clic pe detalii pentru a accesa pagina cu detalii repo.
- Creați fișierul .coveralls.yml la rădăcina proiectului și introduceți codul de mai jos. Pentru a obține
repo_token
, faceți clic pe detaliile repo. Îl vei găsi ușor pe pagina respectivă. Puteți doar să căutați în browserrepo_token
.
repo_token: get-this-from-repo-settings-on-coveralls.io
Acest token mapează datele dvs. de acoperire într-un depozit de pe Salopete. Acum, adăugați comanda de coverage
în secțiunea de scripts
a fișierului dumneavoastră package.json :
"coverage": "nyc report --reporter=text-lcov | coveralls"
Această comandă încarcă raportul de acoperire în folderul .nyc_output
în coveralls.io. Porniți conexiunea la internet și rulați:
yarn coverage
Aceasta ar trebui să încarce raportul de acoperire existent în salopete. Reîmprospătați pagina repo pe salopete pentru a vedea raportul complet.
Pe pagina de detalii, derulați în jos pentru a găsi secțiunea BADGE YOUR REPO
. Faceți clic pe meniul derulant EMBED
și copiați codul de reducere și inserați-l în fișierul README .
Cod Clima
Code Climate este un instrument care ne ajută să măsurăm calitatea codului. Ne arată valorile de întreținere verificând codul nostru cu unele modele definite. Detectează lucruri precum repetarea inutilă și buclele for imbricate profund. De asemenea, colectează date de acoperire a testelor, la fel ca coveralls.io.
- Vizitați codeclimate.com și faceți clic pe „Înscrieți-vă cu GitHub”. Conectați-vă dacă aveți deja un cont.
- Odată ajuns în tabloul de bord, faceți clic pe
Add a repository
. - Găsiți repo-ul
express-api-template
din listă și faceți clic peAdd Repo
. - Așteptați finalizarea construcției și redirecționați către tabloul de bord repo.
- Sub
Codebase Summary
, faceți clic peTest Coverage
. În meniulTest coverage
, copiațiTEST REPORTER ID
și inserați-l în .travis.yml ca valoareCC_TEST_REPORTER_ID
. - Tot pe aceeași pagină, în navigarea din stânga, sub
EXTRAS
, faceți clic pe Insigne. Copiați insignele demaintainability
șitest coverage
în format de reducere și inserați-le în fișierul README.md .
Este important de reținut că există două moduri de a configura verificările de întreținere. Există setări implicite care sunt aplicate fiecărui depozit, dar dacă doriți, puteți furniza un fișier .codeclimate.yml la rădăcina proiectului. Voi folosi setările implicite, pe care le puteți găsi în fila Maintainability
a paginii de setări repo. Vă încurajez să aruncați o privire măcar. Dacă tot doriți să configurați propriile setări, acest ghid vă va oferi toate informațiile de care aveți nevoie.
AppVeyor
AppVeyor și Travis CI sunt ambele teste automate. Principala diferență este că travis-ci rulează teste într-un mediu Linux, în timp ce AppVeyor rulează teste într-un mediu Windows. Această secțiune este inclusă pentru a arăta cum să începeți cu AppVeyor.
- Vizitați AppVeyor și conectați-vă sau înregistrați-vă.
- Pe pagina următoare, faceți clic pe
NEW PROJECT
. - Din lista de repo, găsiți repo-ul
express-api-template
. Plasați cursorul peste el și faceți clic peADD
. - Faceți clic pe fila
Settings
. Faceți clic peEnvironment
în navigarea din stânga. AdăugațiTEST_ENV_VARIABLE
și valoarea acesteia. Faceți clic pe „Salvați” în partea de jos a paginii. - Creați fișierul appveyor.yml la rădăcina proiectului și inserați codul de mai jos.
environment: matrix: - nodejs_version: "12" install: - yarn test_script: - yarn test build: off
Acest cod indică AppVeyor să ruleze testele noastre folosind Node.js v12. Apoi instalăm dependențele de proiect cu comanda yarn
. test_script
specifică comanda pentru a rula testul nostru. Ultima linie îi spune AppVeyor să nu creeze un folder de compilare.
Faceți clic pe fila Settings
. În navigarea din stânga, faceți clic pe insigne. Copiați codul de reducere și inserați-l în fișierul README.md .
Scrieți-vă codul și trimiteți în GitHub. Dacă ați făcut totul conform instrucțiunilor, toate testele ar trebui să treacă și ar trebui să vedeți noile ecusoane strălucitoare, așa cum se arată mai jos. Verificați din nou dacă ați setat variabilele de mediu pe Travis și AppVeyor.
Acum este un moment bun pentru a ne angaja schimbările.
- Ramura corespunzătoare din repo-ul meu este 05-ci.
Adăugarea unui controler
În prezent, gestionăm solicitarea GET
către adresa URL rădăcină, /v1
, în interiorul src/routes/index.js . Acest lucru funcționează conform așteptărilor și nu este nimic în neregulă cu el. Cu toate acestea, pe măsură ce aplicația dvs. crește, doriți să păstrați lucrurile ordonate. Doriți ca preocupările să fie separate - doriți o separare clară între codul care gestionează cererea și codul care generează răspunsul care va fi trimis înapoi clientului. Pentru a realiza acest lucru, scriem controllers
. Controllerele sunt pur și simplu funcții care gestionează cererile care vin printr-o anumită adresă URL.
Pentru a începe, creați un folder controllers/
în interiorul folderului src/
. controllers
interne creează două fișiere: index.js și home.js . Ne-am exporta funcțiile din index.js . Puteți numi home.js orice doriți, dar de obicei doriți să denumiți controlorii după ceea ce controlează aceștia. De exemplu, este posibil să aveți un fișier usersController.js pentru a păstra fiecare funcție legată de utilizatorii din aplicația dvs.
Deschideți src/controllers/home.js și introduceți codul de mai jos:
import { testEnvironmentVariable } from '../settings'; export const indexPage = (req, res) => res.status(200).json({ message: testEnvironmentVariable });
Veți observa că am mutat doar funcția care se ocupă de cererea pentru ruta /
.
Deschideți src/controllers/index.js și introduceți codul de mai jos.
// export everything from home.js export * from './home';
Exportăm totul din fișierul home.js. Acest lucru ne permite să scurtăm instrucțiunile noastre de import pentru a import { indexPage } from '../controllers';
Deschideți src/routes/index.js și înlocuiți codul de acolo cu cel de mai jos:
import express from 'express'; import { indexPage } from '../controllers'; const indexRouter = express.Router(); indexRouter.get('/', indexPage); export default indexRouter;
Singura modificare aici este că am furnizat o funcție pentru a gestiona cererea către ruta /
.
Tocmai ai scris cu succes primul tău controler. De aici este o chestiune de a adăuga mai multe fișiere și funcții după cum este necesar.
Continuați și jucați-vă cu aplicația adăugând mai multe rute și controlere. Puteți adăuga o rută și un controler pentru pagina despre. Nu uitați să vă actualizați testul, totuși.
Rulați yarn test
pentru a confirma că nu am rupt nimic. Iti trece testul? Asta e tare.
Acesta este un punct bun pentru a ne angaja schimbările.
- Ramura corespunzătoare din repo-ul meu este 06-controllers.
Conectarea bazei de date PostgreSQL
și scrierea unui model
Controlerul nostru returnează în prezent mesaje text codificate. Într-o aplicație din lumea reală, adesea trebuie să stocăm și să recuperăm informații dintr-o bază de date. În această secțiune, vom conecta aplicația noastră la o bază de date PostgreSQL.
Vom implementa stocarea și preluarea mesajelor text simple folosind o bază de date. Avem două opțiuni pentru setarea unei baze de date: am putea furniza una de pe un server cloud sau ne-am putea configura propria noastră locală.
Vă recomand să furnizați o bază de date de pe 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.
Deschideți queries.js și inserați următorul cod:
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';
În acest fișier, definim trei șiruri de interogare SQL. Prima interogare șterge și recreează tabelul de messages
. A doua interogare inserează două rânduri în tabelul de messages
. Simțiți-vă liber să adăugați mai multe articole aici. Ultima interogare elimină/șterge tabelul de messages
.
Deschideți queryFunctions.js și inserați următorul cod:
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 ]);
Aici, creăm funcții pentru a executa interogările pe care le-am definit mai devreme. Rețineți că funcția executeQueryArray
execută o serie de interogări și așteaptă ca fiecare să se finalizeze în bucla. (Totuși, nu faceți așa ceva în codul de producție). Apoi, rezolvăm promisiunea doar după ce am executat ultima interogare din listă. Motivul pentru utilizarea unei matrice este că numărul de astfel de interogări va crește pe măsură ce crește numărul de tabele din baza noastră de date.
Deschideți runQuery.js și inserați următorul cod:
import { createTables, insertIntoTables } from './queryFunctions'; (async () => { await createTables(); await insertIntoTables(); })();
Aici executăm funcțiile pentru a crea tabelul și a introduce mesajele în tabel. Să adăugăm o comandă în secțiunea de scripts
a pachetului nostru.json pentru a executa acest fișier.
"runQuery": "babel-node ./src/utils/runQuery"
Acum rulați:
yarn runQuery
Dacă vă inspectați baza de date, veți vedea că tabelul de messages
a fost creat și că mesajele au fost inserate în tabel.
Dacă utilizați ElephantSQL, în pagina de detalii a bazei de date, faceți clic pe BROWSER
din meniul de navigare din stânga. Selectați tabelul de messages
și faceți clic pe Execute
. Ar trebui să vedeți mesajele din fișierul queries.js .
Să creăm un controler și să rutăm pentru a afișa mesajele din baza noastră de date.
Creați un nou fișier de controler src/controllers/messages.js și inserați următorul cod:
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 }); } };
Importăm clasa noastră Model
și creăm o nouă instanță a modelului respectiv. Acesta reprezintă tabelul de messages
din baza noastră de date. Apoi folosim metoda select
a modelului pentru a interoga baza noastră de date. Datele ( name
și message
) pe care le primim sunt trimise ca JSON în răspuns.
Definim controlerul messagesPage
ca o funcție async
. Deoarece interogările node-postgres
returnează o promisiune, await
rezultatul acelei interogări. Dacă întâlnim o eroare în timpul interogării, o prindem și afișăm stiva utilizatorului. Ar trebui să decideți cum alegeți să gestionați eroarea.
Adăugați punctul final de obținere a mesajelor la src/routes/index.js și actualizați linia de import.
# update the import line import { indexPage, messagesPage } from '../controllers'; # add the get messages endpoint indexRouter.get('/messages', messagesPage)
Vizitați https://localhost:3000/v1/messages și ar trebui să vedeți mesajele afișate așa cum se arată mai jos.
Acum, să actualizăm fișierul nostru de testare. Când faceți TDD, de obicei vă scrieți testele înainte de a implementa codul care face testul să treacă. Eu adopt o abordare opusă aici, deoarece încă lucrăm la configurarea bazei de date.
Creați un fișier nou, hooks.js în dosarul test/
și introduceți codul de mai jos:
import { dropTables, createTables, insertIntoTables, } from '../src/utils/queryFunctions'; before(async () => { await createTables(); await insertIntoTables(); }); after(async () => { await dropTables(); });
Când începe testul nostru, Mocha găsește acest fișier și îl execută înainte de a rula orice fișier de testare. Execută cârligul before
pentru a crea baza de date și pentru a insera câteva elemente în ea. Apoi fișierele de testare rulează după aceea. Odată ce testul este terminat, Mocha rulează after
hook în care aruncăm baza de date. Acest lucru ne asigură că de fiecare dată când rulăm testele, facem acest lucru cu înregistrări curate și noi în baza noastră de date.
Creați un nou fișier de test test/messages.test.js și adăugați codul de mai jos:
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(); }); }); });
Afirmăm că rezultatul apelului către /messages
este o matrice. Pentru fiecare obiect mesaj, afirmăm că are name
și proprietatea message
.
Pasul final din această secțiune este actualizarea fișierelor CI.
Adăugați următoarele secțiuni în fișierul .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
Acest lucru îi indică lui Travis să rotească o bază de date PostgreSQL 10 înainte de a rula testele noastre.
Adăugați comanda pentru a crea baza de date ca primă intrare în secțiunea before_script
:
# add this as the first line in the before_script section - psql -c 'create database testdb;' -U postgres
Creați variabila de mediu CONNECTION_STRING
pe Travis și utilizați valoarea de mai jos:
CONNECTION_STRING="postgresql://postgres:postgres@localhost:5432/testdb"
Adăugați următoarele secțiuni în fișierul .appveyor.yml :
before_test: - SET PGUSER=postgres - SET PGPASSWORD=Password12! - PATH=C:\Program Files\PostgreSQL\10\bin\;%PATH% - createdb testdb services: - postgresql101
Adăugați variabila de mediu șir de conexiune la appveyor. Utilizați linia de mai jos:
CONNECTION_STRING=postgresql://postgres:Password12!@localhost:5432/testdb
Acum confirmați modificările și trimiteți în GitHub. Testele dvs. ar trebui să treacă atât pe Travis CI, cât și pe AppVeyor.
- Ramura corespunzătoare din repo-ul meu este 07-connect-postgres.
Notă : sper că totul funcționează bine din partea dvs., dar în cazul în care ar trebui să aveți probleme dintr-un anumit motiv, puteți oricând să verificați codul meu în repo!
Acum, să vedem cum putem adăuga un mesaj în baza noastră de date. Pentru acest pas, vom avea nevoie de o modalitate de a trimite solicitări POST
la adresa noastră URL. Voi folosi Postman pentru a trimite cereri POST
.
Să mergem pe ruta TDD și să ne actualizăm testul pentru a reflecta ceea ce ne așteptăm să realizăm.
Deschideți test/message.test.js și adăugați cazul de mai jos:
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(); }); });
Acest test face o solicitare POST către punctul final /v1/messages
și ne așteptăm să fie returnat o matrice. De asemenea, verificăm proprietățile id
, name
și message
din matrice.
Rulați testele pentru a vedea că acest caz eșuează. Să reparăm acum.
Pentru a trimite cereri de postare, folosim metoda de postare a serverului. Trimitem, de asemenea, numele și mesajul pe care vrem să le introducem. Ne așteptăm ca răspunsul să fie o matrice, cu un id
de proprietate și celelalte informații care formează interogarea. id
-ul este dovada că o înregistrare a fost introdusă în baza de date.
Deschideți src/models/model.js și adăugați metoda de insert
:
async insertWithReturn(columns, values) { const query = ` INSERT INTO ${this.table}(${columns}) VALUES (${values}) RETURNING id, ${columns} `; return this.pool.query(query); }
Aceasta este metoda care ne permite să inserăm mesaje în baza de date. După introducerea articolului, acesta returnează id
-ul, name
și message
.
Deschideți src/controllers/messages.js și adăugați controlerul de mai jos:
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 }); } };
Destructuram corpul cererii pentru a obține numele și mesajul. Apoi folosim valorile pentru a forma un șir de interogare SQL pe care apoi îl executăm cu metoda insertWithReturn
a modelului nostru.
Adăugați punctul final POST
de mai jos la /src/routes/index.js și actualizați-vă linia de import.
import { indexPage, messagesPage, addMessage } from '../controllers'; indexRouter.post('/messages', addMessage);
Rulați testele pentru a vedea dacă trec.
Deschideți Postman și trimiteți o solicitare POST
către punctul final de messages
. Dacă tocmai ați rulat testul, nu uitați să rulați yarn query
pentru a recrea tabelul de messages
.
yarn query
Confirmați modificările și trimiteți în GitHub. Testele dvs. ar trebui să treacă atât pe Travis, cât și pe AppVeyor. Acoperirea testului dumneavoastră va scădea cu câteva puncte, dar este în regulă.
- Ramura corespunzătoare din repo-ul meu este 08-post-to-db.
Middleware
Discuția noastră despre Express nu va fi completă fără a vorbi despre middleware. Documentația Express descrie un middleware ca:
„[...] funcții care au acces la obiectul cerere (req
), la obiectul răspuns (res
) și la următoarea funcție middleware din ciclul cerere-răspuns al aplicației. Următoarea funcție middleware este de obicei indicată printr-o variabilă numitănext
.”
Un middleware poate îndeplini orice număr de funcții, cum ar fi autentificarea, modificarea corpului cererii și așa mai departe. Consultați documentația Express despre utilizarea middleware-ului.
Vom scrie un middleware simplu care modifică corpul cererii. Middleware-ul nostru va adăuga cuvântul SAYS:
la mesajul primit înainte ca acesta să fie salvat în baza de date.
Înainte de a începe, să ne modificăm testul pentru a reflecta ceea ce dorim să obținem.
Deschideți test/messages.test.js și modificați ultima linie de așteptare din cazul de testare a posts message
:
it('posts messages', done => { ... expect(m).to.have.property('message', `SAYS: ${data.message}`); # update this line ... });
Afirmăm că șirul SAYS:
a fost adăugat la mesaj. Rulați testele pentru a vă asigura că acest caz de testare eșuează.
Acum, să scriem codul pentru a trece testul.
Creați un nou middleware/
folder în interiorul folderului src/
. Creați două fișiere în acest folder:
- middleware.js
- index.js
Introdu codul de mai jos în middleware.js :
export const modifyMessage = (req, res, next) => { req.body.message = `SAYS: ${req.body.message}`; next(); };
Aici, atașăm șirul SAYS:
la mesajul din corpul solicitării. După ce facem asta, trebuie să apelăm funcția next()
pentru a trece execuția următoarei funcție din lanțul cerere-răspuns. Fiecare middleware trebuie să apeleze next
funcție pentru a trece execuția următorului middleware din ciclul cerere-răspuns.
Introdu codul de mai jos în index.js :
# export everything from the middleware file export * from './middleware';
Aceasta exportă middleware-ul pe care îl avem în fișierul /middleware.js . Pentru moment, avem doar middleware-ul modifyMessage
.
Deschideți src/routes/index.js și adăugați middleware-ul în lanțul de solicitare-răspuns pentru mesaje postate.
import { modifyMessage } from '../middleware'; indexRouter.post('/messages', modifyMessage, addMessage);
Putem vedea că funcția modifyMessage
vine înaintea funcției addMessage
. Invocăm funcția addMessage
prin apelarea next
în middleware-ul modifyMessage
. Ca experiment, comentați next()
din mijlocul modifyMessage
și urmăriți blocarea cererii.
Deschideți Postman și creați un mesaj nou. Ar trebui să vedeți șirul atașat.
Acesta este un punct bun pentru a ne angaja schimbările.
- Ramura corespunzătoare din depozitul meu este 09-middleware.
Gestionarea erorilor și middleware asincron
Erorile sunt inevitabile în orice aplicație. Sarcina în fața dezvoltatorului este cum să facă față erorilor cât mai grațios posibil.
În expres:
„ Gestionarea erorilor se referă la modul în care Express captează și procesează erorile care apar atât sincron, cât și asincron.
Dacă am scrie doar funcții sincrone, s-ar putea să nu ne facem griji atât de mult cu privire la gestionarea erorilor, deoarece Express face deja o treabă excelentă în gestionarea acestora. Conform documentelor:
„Erorile care apar în codul sincron din interiorul rutelor de gestionare și middleware nu necesită muncă suplimentară.”
Dar odată ce începem să scriem router-uri de gestionare asincrone și middleware, atunci trebuie să facem ceva de gestionare a erorilor.
Middleware-ul nostru modifyMessage
este o funcție sincronă. Dacă apare o eroare în acea funcție, Express se va descurca bine. Să vedem cum ne ocupăm de erorile din middleware asincron.
Să presupunem că, înainte de a crea un mesaj, dorim să obținem o imagine din API-ul Lorem Picsum folosind această adresă URL https://picsum.photos/id/0/info
. Aceasta este o operațiune asincronă care ar putea fie să reușească, fie să eșueze și care prezintă un caz de care trebuie să ne ocupăm.
Începeți prin a instala Axios.
# install axios yarn add axios
Deschideți src/middleware/middleware.js și adăugați funcția de mai jos:
export const performAsyncAction = async (req, res, next) => { try { await axios.get('https://picsum.photos/id/0/info'); next(); } catch (err) { next(err); } };
În această funcție async
, await
un apel către un API (de fapt nu avem nevoie de datele returnate) și apoi apelăm next
funcție din lanțul de cereri. Dacă solicitarea eșuează, prindem eroarea și o transmitem next
. Odată ce Express vede această eroare, omite toate celelalte middleware din lanț. Dacă nu am sunat next(err)
, cererea se va bloca. Dacă am apelat doar next()
fără err
, cererea va continua ca și cum nimic nu s-ar fi întâmplat și eroarea nu va fi prinsă.
Importați această funcție și adăugați-o la lanțul de middleware al rutei de mesaje postate:
import { modifyMessage, performAsyncAction } from '../middleware'; indexRouter.post('/messages', modifyMessage, performAsyncAction, addMessage);
Deschideți src/app.js și adăugați codul de mai jos chiar înainte de linia export default app
.
app.use((err, req, res, next) => { res.status(400).json({ error: err.stack }); }); export default app;
Acesta este handlerul nostru de erori. Conform documentului de tratare a erorilor Express:
„[...] funcțiile de tratare a erorilor au patru argumente în loc de trei: (err, req, res, next)
.”
Rețineți că acest handler de erori trebuie să fie ultimul, după fiecare apel app.use()
. Odată ce întâmpinăm o eroare, returnăm urmărirea stivei cu un cod de stare de 400
. Ai putea face ce vrei cu eroarea. Poate doriți să îl înregistrați sau să îl trimiteți undeva.
Acesta este un loc bun pentru a efectua modificările.
- Ramura corespunzătoare din depozitul meu este 10-async-middleware.
Implementează în Heroku
- Pentru a începe, accesați https://www.heroku.com/ și conectați-vă sau înregistrați-vă.
- Descărcați și instalați Heroku CLI de aici.
- Deschideți un terminal în folderul proiectului pentru a rula comanda.
# login to heroku on command line heroku login
Aceasta va deschide o fereastră de browser și vă va cere să vă conectați la contul Heroku.
Conectați-vă pentru a acorda terminalului dvs. acces la contul Heroku și creați o nouă aplicație Heroku rulând:
#app name is up to you heroku create app-name
Aceasta va crea aplicația pe Heroku și va returna două adrese URL.
# app production url and git url https://app-name.herokuapp.com/ | https://git.heroku.com/app-name.git
Copiați adresa URL din dreapta și rulați comanda de mai jos. Rețineți că acest pas este opțional, deoarece este posibil să descoperiți că Heroku a adăugat deja adresa URL la distanță.
# add heroku remote url git remote add heroku https://git.heroku.com/my-shiny-new-app.git
Deschideți un terminal lateral și executați comanda de mai jos. Aceasta vă arată conectarea aplicației în timp real, așa cum se arată în imagine.
# see process logs heroku logs --tail
Rulați următoarele trei comenzi pentru a seta variabilele de mediu necesare:
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
Amintiți-vă, în scripturile noastre, am stabilit:
"prestart": "babel ./src --out-dir build", "start": "node ./build/bin/www",
Pentru a porni aplicația, trebuie să fie compilată până la ES5 folosind babel în pasul de prestart
, deoarece babel există doar în dependențele noastre de dezvoltare. Trebuie să setăm NPM_CONFIG_PRODUCTION
la false
pentru a le putea instala și pe acestea.
Pentru a confirma că totul este setat corect, rulați comanda de mai jos. De asemenea, puteți vizita fila de settings
de pe pagina aplicației și faceți clic pe Reveal Config Vars
.
# check configuration variables heroku config
Acum rulați git push heroku
.
Pentru a deschide aplicația, rulați:
# open /v1 route heroku open /v1 # open /v1/messages route heroku open /v1/messages
Dacă, ca mine, utilizați aceeași bază de date PostgresSQL atât pentru dezvoltare, cât și pentru producție, este posibil să descoperiți că de fiecare dată când executați teste, baza de date este ștearsă. Pentru a-l recrea, puteți rula oricare dintre următoarele comenzi:
# run script locally yarn runQuery # run script with heroku heroku run yarn runQuery
Desfăşurare continuă (CD) cu Travis
Să adăugăm acum Implementarea continuă (CD) pentru a finaliza fluxul CI/CD. Ne vom implementa de la Travis după fiecare test de succes.
Primul pas este să instalați Travis CI. (Puteți găsi instrucțiunile de instalare aici.) După instalarea cu succes a Travis CI, conectați-vă rulând comanda de mai jos. (Rețineți că acest lucru ar trebui făcut în depozitul dvs. de proiect.)
# login to travis travis login --pro # use this if you're using two factor authentication travis login --pro --github-token enter-github-token-here
Dacă proiectul dvs. este găzduit pe travis-ci.org, eliminați --pro
. Pentru a obține un token GitHub, accesați pagina de setări pentru dezvoltatori a contului dvs. și generați unul. Acest lucru se aplică numai dacă contul dvs. este securizat cu 2FA.
Deschideți .travis.yml și adăugați o secțiune de implementare:
deploy: provider: heroku app: master: app-name
Aici, precizăm că dorim să o implementăm pe Heroku. Sub-secțiunea aplicației specifică faptul că dorim să implementăm ramura master
a depozitului nostru în app-name
aplicație de pe Heroku. Este posibil să implementați diferite ramuri în diferite aplicații. Puteți citi mai multe despre opțiunile disponibile aici.
Rulați comanda de mai jos pentru a cripta cheia API Heroku și adăugați-o la secțiunea de implementare:
# encrypt heroku API key and add to .travis.yml travis encrypt $(heroku auth:token) --add deploy.api_key --pro
Aceasta va adăuga subsecțiunea de mai jos la secțiunea de implementare.
api_key: secure: very-long-encrypted-api-key-string
Acum comite modificările și împinge în GitHub în timp ce îți monitorizezi jurnalele. Veți vedea construcția declanșată de îndată ce testul Travis este finalizat. În acest fel, dacă avem un test eșuat, modificările nu vor fi implementate niciodată. De asemenea, dacă construcția a eșuat, întreaga rulare de testare ar eșua. Aceasta completează fluxul CI/CD.
- Ramura corespunzătoare din repo-ul meu este 11-cd.
Concluzie
Dacă ați ajuns până aici, vă spun: „Dumneze în sus!” În acest tutorial, am configurat cu succes un nou proiect Express. Am continuat să configuram dependențele de dezvoltare, precum și Integrarea continuă (CI). Apoi am scris funcții asincrone pentru a gestiona solicitările către punctele noastre finale API - completate cu teste. Apoi ne-am uitat pe scurt la gestionarea erorilor. În cele din urmă, am implementat proiectul nostru în Heroku și am configurat Implementarea continuă.
Acum aveți un șablon pentru următorul proiect back-end. Am făcut destule doar pentru a vă ajuta să începeți, dar ar trebui să continuați să învățați să continuați. Asigurați-vă că consultați și documentele Express.js. Dacă preferați să utilizați MongoDB
în loc de PostgreSQL
, am aici un șablon care face exact asta. Îl puteți verifica pentru configurare. Are doar câteva puncte de diferență.
Resurse
- „Creați un backend API Express cu MongoDB”, Orji Chidi Matthew, GitHub
- „Un scurt ghid pentru conectarea middleware-ului”, Stephen Sugden
- „Șablon API expres”, GitHub
- „AppVeyor vs Travis CI”, StackShare
- „The Heroku CLI”, Heroku Dev Center
- „Desfăşurarea Heroku”, Travis CI
- „Utilizarea middleware”, Express.js
- „Gestionarea erorilor”, Express.js
- „Începe”, Mocha
-
nyc
(GitHub) - ElephantSQL
- Poştaş
- Expres
- Travis CI
- Cod Clima
- PostgreSQL
- pgAdmin