Cum să configurați un proiect de backend API Express cu PostgreSQL

Publicat: 2022-03-10
Rezumat rapid ↬ În acest articol, vom crea un set de puncte finale API folosind Express de la zero în sintaxa ES6 și vom acoperi câteva dintre cele mai bune practici de dezvoltare. Aflați cum funcționează toate piesele împreună în timp ce creați un proiect mic folosind integrarea continuă și dezvoltarea bazată pe teste înainte de implementarea în Heroku.

Vom 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.

Mai multe după săritură! Continuați să citiți mai jos ↓

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.

  1. Ștergeți fișierul index/users.js .
  2. Ștergeți folderele public/ și views/ .
  3. Redenumiți fișierul bin/www în bin/www.js .
  4. Dezinstalați jade cu comanda yarn remove jade .
  5. 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.
  6. 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"
  1. prestart scripts construiește conținutul folderului src/ și îl pune în folderul build/ . Când yarn start , acest script rulează mai întâi înainte de script-ul de start .
  2. start script-ul servește acum conținutul folderului build/ în loc de folderul src/ 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.
  3. 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 folosim babel-node pentru a rula aplicația în loc de node obișnuit. Indicatorul --exec forțează babel-node să servească folderul src/ . Pentru scriptul de start , folosim node , deoarece fișierele din folderul build/ 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.

Raport de acoperire a testului (previzualizare mare)

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.

  1. 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.
  2. Treceți cu mouse-ul peste săgeata drop-down de lângă fotografia de profil și faceți clic pe settings .
  3. Sub fila Repositories , faceți clic pe Manage repositories on Github pentru a fi redirecționat către Github.
  4. Pe pagina GitHub, derulați în jos la Repository access și faceți clic pe caseta de selectare de lângă Only select repositories .
  5. Faceți clic pe meniul derulant Select repositories și găsiți depozitul express-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 la travis-ci .
  6. Faceți clic pe Approve and install și așteptați să fiți redirecționat înapoi la travis-ci .
  7. Î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.
  8. Copiați codul rezultat și inserați-l în fișierul README.md .
  9. Pe pagina proiectului, faceți clic pe More options > Settings . În secțiunea Environment Variables , adăugați variabila de mediu TEST_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."
  10. 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.

  1. Vizitați coveralls.io și fie conectați-vă, fie înregistrați-vă cu contul Github.
  2. Treceți cursorul peste partea stângă a ecranului pentru a afișa meniul de navigare. Faceți clic pe ADD REPOS .
  3. 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 pe SYNC 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.
  4. Faceți clic pe detalii pentru a accesa pagina cu detalii repo.
  5. 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 browser repo_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.

  1. Vizitați codeclimate.com și faceți clic pe „Înscrieți-vă cu GitHub”. Conectați-vă dacă aveți deja un cont.
  2. Odată ajuns în tabloul de bord, faceți clic pe Add a repository .
  3. Găsiți repo-ul express-api-template din listă și faceți clic pe Add Repo .
  4. Așteptați finalizarea construcției și redirecționați către tabloul de bord repo.
  5. Sub Codebase Summary , faceți clic pe Test Coverage . În meniul Test coverage , copiați TEST REPORTER ID și inserați-l în .travis.yml ca valoare CC_TEST_REPORTER_ID .
  6. Tot pe aceeași pagină, în navigarea din stânga, sub EXTRAS , faceți clic pe Insigne. Copiați insignele de maintainability și test 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 pe ADD .
  • Faceți clic pe fila Settings . Faceți clic pe Environment în navigarea din stânga. Adăugați TEST_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.

Ecusoane Repo CI/CD. (Previzualizare mare)

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.

ElephantSQL turtle plan details page (Large preview)

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

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

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

Let's connect node-postgres to our application.

 # install node-postgres yarn add pg

Open settings.js and add the line below:

 export const connectionString = process.env.CONNECTION_STRING;

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

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

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

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

  • pool.js
  • model.js

Open pools.js and paste the following code:

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

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

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

Open model.js and paste the following code:

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

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

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

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

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

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

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

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

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.

Mesaje din baza de date. (Previzualizare mare)

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 
Solicitare POST către punctul final al mesajelor. (Previzualizare mare)
Solicitarea GET afișează mesajul nou adăugat. (Previzualizare mare)

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.

Mesaj modificat de middleware. (Previzualizare mare)

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

  1. Pentru a începe, accesați https://www.heroku.com/ și conectați-vă sau înregistrați-vă.
  2. Descărcați și instalați Heroku CLI de aici.
  3. 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
Jurnalele Heroku. (Previzualizare mare)

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