So richten Sie ein Express-API-Backend-Projekt mit PostgreSQL ein
Veröffentlicht: 2022-03-10Wir werden einen Test-Driven Development (TDD)-Ansatz und den eingerichteten Continuous Integration (CI)-Job verwenden, um unsere Tests automatisch auf Travis CI und AppVeyor auszuführen, komplett mit Berichten zur Codequalität und -abdeckung. Wir lernen Controller, Modelle (mit PostgreSQL), Fehlerbehandlung und asynchrone Express-Middleware kennen. Abschließend vervollständigen wir die CI/CD-Pipeline, indem wir die automatische Bereitstellung auf Heroku konfigurieren.
Es hört sich nach viel an, aber dieses Tutorial richtet sich an Anfänger, die bereit sind, sich an einem Backend-Projekt mit einem gewissen Grad an Komplexität zu versuchen, und die vielleicht immer noch verwirrt sind, wie alle Teile in einem echten Projekt zusammenpassen .
Es ist robust, ohne überwältigend zu sein, und ist in Abschnitte unterteilt, die Sie in angemessener Zeit absolvieren können.
Einstieg
Der erste Schritt besteht darin, ein neues Verzeichnis für das Projekt zu erstellen und ein neues Knotenprojekt zu starten. Node ist erforderlich, um mit diesem Tutorial fortzufahren. Wenn Sie es nicht installiert haben, gehen Sie zur offiziellen Website, laden Sie es herunter und installieren Sie es, bevor Sie fortfahren.
Ich werde Garn als meinen Paketmanager für dieses Projekt verwenden. Hier finden Sie Installationsanweisungen für Ihr spezifisches Betriebssystem. Fühlen Sie sich frei, npm zu verwenden, wenn Sie möchten.
Öffnen Sie Ihr Terminal, erstellen Sie ein neues Verzeichnis und starten Sie ein Node.js-Projekt.
# 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
Beantworten Sie die folgenden Fragen, um eine package.json -Datei zu generieren. Diese Datei enthält Informationen zu Ihrem Projekt. Beispiele für solche Informationen sind die verwendeten Abhängigkeiten, der Befehl zum Starten des Projekts usw.
Sie können nun den Projektordner in einem Editor Ihrer Wahl öffnen. Ich verwende Visual Studio Code. Es ist eine kostenlose IDE mit unzähligen Plugins, die Ihnen das Leben erleichtern, und es ist für alle wichtigen Plattformen verfügbar. Sie können es von der offiziellen Website herunterladen.
Erstellen Sie die folgenden Dateien im Projektordner:
- README.md
- .editorconfig
Hier ist eine Beschreibung dessen, was .editorconfig auf der EditorConfig-Website tut. (Sie brauchen es wahrscheinlich nicht, wenn Sie alleine arbeiten, aber es schadet nicht, also lasse ich es hier.)
„EditorConfig hilft dabei, konsistente Codierungsstile für mehrere Entwickler aufrechtzuerhalten, die über verschiedene Editoren und IDEs hinweg an demselben Projekt arbeiten.“
Öffnen .editorconfig
und fügen Sie den folgenden Code ein:
root = true [*] indent_style = space indent_size = 2 charset = utf-8 trim_trailing_whitespace = false insert_final_newline = true
Das [*]
bedeutet, dass wir die Regeln, die darunter fallen, auf jede Datei im Projekt anwenden möchten. Wir wollen eine Einzugsgröße von zwei Leerzeichen und einen UTF-8
Zeichensatz. Wir wollen auch nachlaufende Leerzeichen kürzen und eine letzte leere Zeile in unsere Datei einfügen.
Öffnen Sie README.md und fügen Sie den Projektnamen als Element der ersten Ebene hinzu.
# Express API template
Lassen Sie uns gleich die Versionskontrolle hinzufügen.
# initialize the project folder as a git repository git init
Erstellen Sie eine .gitignore -Datei und geben Sie die folgenden Zeilen ein:
node_modules/ yarn-error.log .env .nyc_output coverage build/
Dies sind alle Dateien und Ordner, die wir nicht verfolgen möchten. Wir haben sie noch nicht in unserem Projekt, aber wir werden sie sehen, wenn wir fortfahren.
Zu diesem Zeitpunkt sollten Sie die folgende Ordnerstruktur haben.
EXPRESS-API-TEMPLATE ├── .editorconfig ├── .gitignore ├── package.json └── README.md
Ich halte dies für einen guten Punkt, um meine Änderungen zu committen und sie auf GitHub zu pushen.
Starten eines neuen Express-Projekts
Express ist ein Node.js-Framework zum Erstellen von Webanwendungen. Laut offizieller Website handelt es sich um eine
Schnelles, unparteiisches, minimalistisches Web-Framework für Node.js.
Es gibt andere großartige Webanwendungs-Frameworks für Node.js, aber Express ist mit über 47.000 GitHub-Sternen zum Zeitpunkt des Schreibens dieses Artikels sehr beliebt.
In diesem Artikel werden wir nicht viele Diskussionen über alle Teile führen, aus denen Express besteht. Für diese Diskussion empfehle ich Ihnen, sich Jamies Serie anzusehen. Den ersten Teil gibt es hier, den zweiten Teil gibt es hier.
Installieren Sie Express und starten Sie ein neues Express-Projekt. Es ist möglich, einen Express-Server von Grund auf manuell einzurichten, aber um uns das Leben zu erleichtern, verwenden wir den Express-Generator, um das App-Skelett einzurichten.
# 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
Das Flag -f
zwingt Express, das Projekt im aktuellen Verzeichnis zu erstellen.
Wir werden jetzt einige Hausputzarbeiten durchführen.
- Löschen Sie die Datei index/users.js .
- Löschen Sie die Ordner
public/
undviews/
. - Benennen Sie die Datei bin/www in bin/www.js um .
- Deinstallieren Sie
jade
mit dem Befehlyarn remove jade
. - Erstellen Sie einen neuen Ordner mit dem Namen
src/
und verschieben Sie Folgendes hinein: 1. app.js -Datei 2.bin/
-Ordner 3.routes/
-Ordner darin. - Öffnen Sie package.json und aktualisieren Sie das
start
so, dass es wie unten aussieht.
"start": "node ./src/bin/www"
Zu diesem Zeitpunkt sieht Ihre Projektordnerstruktur wie folgt aus. Sie können sehen, wie VS Code die vorgenommenen Dateiänderungen hervorhebt.
EXPRESS-API-TEMPLATE ├── node_modules ├── src | ├── bin │ │ ├── www.js │ ├── routes │ | ├── index.js │ └── app.js ├── .editorconfig ├── .gitignore ├── package.json ├── README.md └── yarn.lock
Öffnen Sie src/app.js und ersetzen Sie den Inhalt durch den folgenden Code.
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;
Nachdem wir einige Bibliotheken angefordert haben, weisen wir Express an, jede an /v1
eingehende Anfrage mit indexRouter
zu verarbeiten.
Ersetzen Sie den Inhalt von routes/index.js durch den folgenden Code:
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;
Wir schnappen uns Express, erstellen daraus einen Router und bedienen die /
-Route, die einen Statuscode von 200
und eine JSON-Nachricht zurückgibt.
Starten Sie die App mit dem folgenden Befehl:
# start the app yarn start
Wenn Sie alles richtig eingerichtet haben, sollten Sie in Ihrem Terminal nur $ node ./src/bin/www
sehen.
Besuchen Sie https://localhost:3000/v1
in Ihrem Browser. Sie sollten die folgende Meldung sehen:
{ "message": "Welcome to Express API template" }
Dies ist ein guter Punkt, um unsere Änderungen zu übernehmen.
- Der entsprechende Branch in meinem Repo ist 01-install-express.
Konvertieren unseres Codes in ES6
Der von express-generator
generierte Code ist in ES5
, aber in diesem Artikel werden wir unseren gesamten Code in ES6
-Syntax schreiben. Konvertieren wir also unseren vorhandenen Code in ES6
.
Ersetzen Sie den Inhalt von routes/index.js durch den folgenden Code:
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;
Es ist derselbe Code wie oben, aber mit der import-Anweisung und einer Pfeilfunktion im /
route-Handler.
Ersetzen Sie den Inhalt von src/app.js durch den folgenden Code:
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;
Schauen wir uns nun den Inhalt von src/bin/www.js an . Wir werden es schrittweise aufbauen. Löschen Sie den Inhalt von src/bin/www.js
und fügen Sie den folgenden Codeblock ein.
#!/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
Dieser Code prüft, ob in den Umgebungsvariablen ein benutzerdefinierter Port angegeben ist. Wenn keiner festgelegt ist, wird der Standardportwert von 3000
auf der App-Instanz festgelegt, nachdem er von normalizePort entweder auf eine Zeichenfolge oder eine Zahl normalizePort
wurde. Aus dem http
-Modul wird dann der Server erstellt, mit app
als Callback-Funktion.
Die #!/usr/bin/env node
ist optional, da wir node angeben würden, wenn wir diese Datei ausführen möchten. Stellen Sie jedoch sicher, dass es sich in Zeile 1 der Datei src/bin/www.js befindet , oder entfernen Sie es vollständig.
Werfen wir einen Blick auf die Fehlerbehandlungsfunktion. Kopieren Sie diesen Codeblock und fügen Sie ihn nach der Zeile ein, in der der Server erstellt wird.
/** * 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);
Die Funktion onError
lauscht auf Fehler im http-Server und zeigt entsprechende Fehlermeldungen an. Die Funktion onListening
gibt einfach den Port aus, auf dem der Server auf der Konsole lauscht. Schließlich wartet der Server auf eingehende Anfragen an der angegebenen Adresse und dem angegebenen Port.
An diesem Punkt ist unser gesamter vorhandener Code in ES6
-Syntax. Stoppen Sie Ihren Server (verwenden Sie Ctrl + C ) und führen Sie yarn start
aus. Sie erhalten einen Fehler SyntaxError: Invalid or unexpected token
. Dies geschieht, weil Node (zum Zeitpunkt des Schreibens) einen Teil der Syntax, die wir in unserem Code verwendet haben, nicht unterstützt.
Das beheben wir nun im folgenden Abschnitt.
Entwicklungsabhängigkeiten konfigurieren: babel
, nodemon
, eslint
, And prettier
Es ist an der Zeit, die meisten Skripte einzurichten, die wir in dieser Phase des Projekts benötigen.
Installieren Sie die erforderlichen Bibliotheken mit den folgenden Befehlen. Sie können einfach alles kopieren und in Ihr Terminal einfügen. Die Kommentarzeilen werden übersprungen.
# install babel scripts yarn add @babel/cli @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/register @babel/runtime @babel/node --dev
Dadurch werden alle aufgelisteten babel-Skripte als Entwicklungsabhängigkeiten installiert. Überprüfen Sie Ihre Datei package.json und Sie sollten einen Abschnitt devDependencies
sehen. Dort werden alle installierten Skripte aufgelistet.
Die von uns verwendeten babel-Skripte werden im Folgenden erklärt:
@babel/cli | Eine erforderliche Installation für die Verwendung von babel . Es ermöglicht die Verwendung von Babel vom Terminal aus und ist als ./node_modules/.bin/babel verfügbar. |
@babel/core | Core-Babel-Funktionalität. Dies ist eine erforderliche Installation. |
@babel/node | Dies funktioniert genau wie die Node.js-CLI, mit dem zusätzlichen Vorteil, dass es mit babel -Voreinstellungen und Plugins kompiliert werden kann. Dies ist für die Verwendung mit nodemon erforderlich. |
@babel/plugin-transform-runtime | Dies hilft, Duplikate in der kompilierten Ausgabe zu vermeiden. |
@babel/preset-env | Eine Sammlung von Plugins, die für die Durchführung von Codetransformationen verantwortlich sind. |
@babel/register | Dies kompiliert Dateien im laufenden Betrieb und wird während der Tests als Anforderung angegeben. |
@babel/runtime | Dies funktioniert in Verbindung mit @babel/plugin-transform-runtime . |
Erstellen Sie eine Datei namens .babelrc im Stammverzeichnis Ihres Projekts und fügen Sie den folgenden Code hinzu:
{ "presets": ["@babel/preset-env"], "plugins": ["@babel/transform-runtime"] }
Lassen Sie uns nodemon
installieren
# install nodemon yarn add nodemon --dev
nodemon
ist eine Bibliothek, die unseren Projektquellcode überwacht und unseren Server automatisch neu startet, wenn er Änderungen feststellt.
Erstellen Sie eine Datei namens nodemon.json im Stammverzeichnis Ihres Projekts und fügen Sie den folgenden Code hinzu:
{ "watch": [ "package.json", "nodemon.json", ".eslintrc.json", ".babelrc", ".prettierrc", "src/" ], "verbose": true, "ignore": ["*.test.js", "*.spec.js"] }
Der nodemon
watch
welche Dateien und Ordner auf Änderungen überwacht werden sollen. Wenn sich also eine dieser Dateien ändert, startet nodemon den Server neu. Die ignore
-Taste weist die Dateien an, nicht auf Änderungen zu achten.
Aktualisieren Sie nun den Abschnitt scripts
Ihrer Datei package.json so, dass er wie folgt aussieht:
# 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 erstellt den Inhalt des Ordnerssrc/
und legt ihn im Ordnerbuild/
ab. Wenn Sie den Befehl „yarn start
“ ausgeben, wird dieses Skript zuerst vor demstart
ausgeführt. - Das
start
stellt jetzt den Inhalt desbuild/
-Ordners statt des zuvorsrc/
-Ordners bereit. Dies ist das Skript, das Sie verwenden, wenn Sie die Datei in der Produktion bereitstellen. Tatsächlich führen Dienste wie Heroku dieses Skript automatisch aus, wenn Sie es bereitstellen. -
yarn startdev
wird verwendet, um den Server während der Entwicklung zu starten. Von nun an werden wir dieses Skript bei der Entwicklung der App verwenden. Beachten Sie, dass wir jetztbabel-node
verwenden, um die App anstelle des regulärennode
auszuführen. Das Flag--exec
zwingtbabel-node
, den Ordnersrc/
zu bedienen. Für dasstart
verwenden wirnode
, da die Dateien imbuild/
-Ordner zu ES5 kompiliert wurden.
Führen yarn startdev
und besuchen Sie https://localhost:3000/v1. Ihr Server sollte wieder betriebsbereit sein.
Der letzte Schritt in diesem Abschnitt ist die Konfiguration von ESLint
und prettier
. ESLint hilft bei der Durchsetzung von Syntaxregeln, während hübschere hilft, unseren Code für die Lesbarkeit richtig zu formatieren.
Fügen Sie beide mit dem folgenden Befehl hinzu. Sie sollten dies auf einem separaten Terminal ausführen und dabei das Terminal beobachten, auf dem unser Server läuft. Sie sollten sehen, dass der Server neu gestartet wird. Dies liegt daran, dass wir die Datei „package.json “ auf Änderungen überwachen.
# install elsint and prettier yarn add eslint eslint-config-airbnb-base eslint-plugin-import prettier --dev
Erstellen Sie nun die Datei .eslintrc.json im root
und fügen Sie den folgenden Code hinzu:
{ "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] } }
Diese Datei definiert hauptsächlich einige Regeln, anhand derer eslint
unseren Code überprüft. Sie können sehen, dass wir die von Airbnb verwendeten Stilregeln erweitern.
Im Abschnitt "rules"
definieren wir, ob eslint
eine Warnung oder einen Fehler anzeigen soll, wenn es auf bestimmte Verstöße stößt. Zum Beispiel zeigt es eine Warnmeldung auf unserem Terminal für jeden Einzug, der keine 2 Leerzeichen verwendet. Ein Wert von [0]
deaktiviert eine Regel, was bedeutet, dass wir keine Warnung oder Fehlermeldung erhalten, wenn wir gegen diese Regel verstoßen.
Erstellen Sie eine Datei namens .prettierrc und fügen Sie den folgenden Code hinzu:
{ "trailingComma": "es5", "tabWidth": 2, "semi": true, "singleQuote": true }
Wir legen eine Tabulatorbreite von 2
fest und erzwingen die Verwendung von einfachen Anführungszeichen in unserer gesamten Anwendung. Sehen Sie sich die hübschere Anleitung für weitere Styling-Optionen an.
Fügen Sie nun Ihrer package.json die folgenden Skripte hinzu:
# add these one after the other "lint": "./node_modules/.bin/eslint ./src" "pretty": "prettier --write '**/*.{js,json}' '!node_modules/**'" "postpretty": "yarn lint --fix"
Führen Sie yarn lint
. Sie sollten eine Reihe von Fehlern und Warnungen in der Konsole sehen.
Der Befehl pretty
verschönert unseren Code. Der postpretty
Befehl wird unmittelbar danach ausgeführt. Es führt den lint
-Befehl mit angehängtem --fix
Flag aus. Dieses Flag weist ESLint
an, häufig auftretende Linting-Probleme automatisch zu beheben. Auf diese Weise führe ich meistens den Befehl wool yarn pretty
aus, ohne mich um den Befehl lint
zu kümmern.
yarn pretty
laufen lassen. Sie sollten sehen, dass wir nur zwei Warnungen über das Vorhandensein von Warnungen in der Datei bin/www.js haben alert
So sieht unsere Projektstruktur zu diesem Zeitpunkt aus.
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
Sie werden möglicherweise feststellen, dass Sie eine zusätzliche Datei, yarn-error.log
, in Ihrem Projektstammverzeichnis haben. Fügen Sie es zur .gitignore
-Datei hinzu. Bestätigen Sie Ihre Änderungen.
- Der entsprechende Zweig an dieser Stelle in meinem Repo ist 02-dev-dependencies.
Einstellungen und Umgebungsvariablen in unserer .env- Datei
In fast jedem Projekt benötigen Sie einen Ort zum Speichern von Einstellungen, die in Ihrer gesamten App verwendet werden, z. B. einen geheimen AWS-Schlüssel. Wir speichern solche Einstellungen als Umgebungsvariablen. Das schützt sie vor neugierigen Blicken und wir können sie bei Bedarf innerhalb unserer Anwendung verwenden.
Ich mag es, eine settings.js -Datei zu haben, mit der ich alle meine Umgebungsvariablen auslesen kann. Dann kann ich von überall in meiner App auf die Einstellungsdatei verweisen. Es steht Ihnen frei, diese Datei nach Belieben zu benennen, aber es gibt eine Art Konsens darüber, solche Dateien settings.js oder config.js zu benennen.
Unsere Umgebungsvariablen speichern wir in einer .env
-Datei und lesen sie von dort in unsere settings
ein.
Erstellen Sie die .env -Datei im Stammverzeichnis Ihres Projekts und geben Sie die folgende Zeile ein:
TEST_ENV_VARIABLE="Environment variable is coming across"
Um Umgebungsvariablen in unser Projekt einlesen zu können, gibt es eine nette Bibliothek, dotenv
, die unsere .env
-Datei liest und uns Zugriff auf die darin definierten Umgebungsvariablen gibt. Lassen Sie es uns installieren.
# install dotenv yarn add dotenv
Fügen Sie die .env -Datei der Liste der Dateien hinzu, die von nodemon
überwacht werden.
Erstellen Sie nun die Datei settings.js im Ordner src/
und fügen Sie den folgenden Code hinzu:
import dotenv from 'dotenv'; dotenv.config(); export const testEnvironmentVariable = process.env.TEST_ENV_VARIABLE;
Wir importieren das dotenv
-Paket und rufen seine Konfigurationsmethode auf. Wir exportieren dann die testEnvironmentVariable
, die wir in unserer .env
-Datei festgelegt haben.
Öffnen Sie src/routes/index.js und ersetzen Sie den Code durch den untenstehenden.
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;
Die einzige Änderung, die wir hier vorgenommen haben, besteht darin, dass wir testEnvironmentVariable
aus unserer settings
importieren und als Antwortnachricht für eine Anfrage von der /
-Route verwenden.
Besuchen Sie https://localhost:3000/v1 und Sie sollten die Nachricht sehen, wie unten gezeigt.
{ "message": "Environment variable is coming across." }
Und das ist es. Von nun an können wir so viele Umgebungsvariablen hinzufügen, wie wir wollen, und wir können sie aus unserer settings.js -Datei exportieren.
Dies ist ein guter Punkt, um Ihren Code festzuschreiben. Denken Sie daran, Ihren Code zu verschönern und zu fusseln.
- Der entsprechende Zweig in meinem Repo ist 03-env-variables.
Unseren ersten Test schreiben
Es ist an der Zeit, Tests in unsere App zu integrieren. Eines der Dinge, die dem Entwickler Vertrauen in seinen Code geben, sind Tests. Ich bin sicher, Sie haben unzählige Artikel im Internet gesehen, die Test-Driven Development (TDD) predigen. Es kann nicht genug betont werden, dass Ihr Code ein gewisses Maß an Tests erfordert. TDD ist sehr einfach zu befolgen, wenn Sie mit Express.js arbeiten.
In unseren Tests führen wir Aufrufe an unsere API-Endpunkte durch und prüfen, ob das zurückgegebene Ergebnis unseren Erwartungen entspricht.
Installieren Sie die erforderlichen Abhängigkeiten:
# install dependencies yarn add mocha chai nyc sinon-chai supertest coveralls --dev
Jede dieser Bibliotheken spielt in unseren Tests eine eigene Rolle.
mocha | Testläufer |
chai | verwendet, um Behauptungen aufzustellen |
nyc | Testabdeckungsbericht sammeln |
sinon-chai | erweitert Chais Behauptungen |
supertest | Wird verwendet, um HTTP-Aufrufe an unsere API-Endpunkte zu tätigen |
coveralls | zum Hochladen der Testabdeckung auf overalls.io |
Erstellen Sie einen neuen Ordner test/
im Stammverzeichnis Ihres Projekts. Erstellen Sie zwei Dateien in diesem Ordner:
- test/setup.js
- test/index.test.js
Mocha findet den Ordner test/
automatisch.
Öffnen Sie test/setup.js und fügen Sie den folgenden Code ein. Dies ist nur eine Hilfsdatei, die uns hilft, alle Importe zu organisieren, die wir in unseren Testdateien benötigen.
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';
Dies ist wie eine Einstellungsdatei, aber für unsere Tests. Auf diese Weise müssen wir nicht alles in jeder unserer Testdateien initialisieren. Also importieren wir die erforderlichen Pakete und exportieren das, was wir initialisiert haben – das wir dann in die Dateien importieren können, die sie benötigen.
Öffnen Sie index.test.js und fügen Sie den folgenden Testcode ein.
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(); }); }); });
Hier stellen wir eine Anfrage, um den Basisendpunkt zu erhalten, der /
ist, und behaupten, dass die res.
body
-Objekt hat einen message
mit einem Wert der Environment variable is coming across.
Wenn Sie mit dem describe
-Muster nicht vertraut sind it
empfehle ich Ihnen, einen kurzen Blick auf Mochas „Getting Started“-Dokument zu werfen.
Fügen Sie den Testbefehl zum Abschnitt scripts
von package.json hinzu .
"test": "nyc --reporter=html --reporter=text --reporter=lcov mocha -r @babel/register"
Dieses Skript führt unseren Test mit nyc
und generiert drei Arten von Abdeckungsberichten: einen HTML-Bericht, der an den Ordner „ coverage/
“ ausgegeben wird; ein Textbericht, der an das Terminal ausgegeben wird, und ein lcov-Bericht, der an den Ordner .nyc_output/
ausgegeben wird.
Führen Sie nun den yarn test
. Sie sollten einen Textbericht in Ihrem Terminal sehen, genau wie auf dem Foto unten.

Beachten Sie, dass zwei zusätzliche Ordner generiert werden:
-
.nyc_output/
-
coverage/
Schauen Sie in .gitignore
und Sie werden sehen, dass wir bereits beide ignorieren. Ich ermutige Sie, coverage/index.html
in einem Browser zu öffnen und den Testbericht für jede Datei anzuzeigen.
Dies ist ein guter Punkt, um Ihre Änderungen zu übernehmen.
- Der entsprechende Branch in meinem Repo ist 04-first-test.
Kontinuierliche Integration (CD) und Abzeichen: Travis, Overalls, Code Climate, AppVeyor
Jetzt ist es an der Zeit, Tools für Continuous Integration und Deployment (CI/CD) zu konfigurieren. Wir werden gängige Dienste wie travis-ci
, coveralls
, AppVeyor
und codeclimate
und Badges zu unserer README-Datei hinzufügen.
Lass uns anfangen.
Travis C.I
Travis CI ist ein Tool, das unsere Tests jedes Mal automatisch ausführt, wenn wir ein Commit an GitHub (und seit kurzem Bitbucket) senden und jedes Mal, wenn wir eine Pull-Anfrage erstellen. Dies ist vor allem bei Pull-Requests nützlich, indem es uns anzeigt, ob unser neuer Code einen unserer Tests gebrochen hat.
- Besuchen Sie travis-ci.com oder travis-ci.org und erstellen Sie ein Konto, falls Sie noch keines haben. Sie müssen sich mit Ihrem GitHub-Konto anmelden.
- Bewegen Sie den Mauszeiger über den Dropdown-Pfeil neben Ihrem Profilbild und klicken Sie auf
settings
. - Klicken Sie auf der Registerkarte
Repositories
Manage repositories on Github
, um zu Github umgeleitet zu werden. - Scrollen Sie auf der GitHub-Seite nach unten zu
Repository access
und aktivieren Sie das Kontrollkästchen nebenOnly select repositories
. - Klicken Sie auf das Dropdown-Menü
Select repositories
auswählen und suchen Sie das Repositoryexpress-api-template
. Klicken Sie darauf, um es der Liste der Repositories hinzuzufügen, die Sie zutravis-ci
hinzufügen möchten. - Klicken Sie auf
Approve and install
und warten Sie, bis Sie zutravis-ci
zurückgeleitet werden. - Klicken Sie oben auf der Repo-Seite in der Nähe des Repo-Namens auf das Symbol für den
build unknown
. Wählen Sie im Statusbild-Modal Markdown aus der Format-Dropdown-Liste aus. - Kopieren Sie den resultierenden Code und fügen Sie ihn in Ihre README.md -Datei ein.
- Klicken Sie auf der Projektseite auf
More options
>Settings
. Fügen Sie im AbschnittEnvironment Variables
dieTEST_ENV_VARIABLE
hinzu. Stellen Sie bei der Eingabe des Werts sicher, dass Sie ihn in doppelte Anführungszeichen setzen, wie z. B."Environment variable is coming across."
- Erstellen Sie die Datei .travis.yml im Stammverzeichnis Ihres Projekts und fügen Sie den folgenden Code ein (wir legen den Wert von
CC_TEST_REPORTER_ID
im Abschnitt Code Climate fest).
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
Zuerst weisen wir Travis an, unseren Test mit Node.js auszuführen, und setzen dann die globale Umgebungsvariable CC_TEST_REPORTER_ID
(dazu kommen wir im Abschnitt „Code-Klima“). Im matrix
weisen wir Travis an, unsere Tests mit Node.js v12 auszuführen. Wir möchten auch das node_modules/
, damit es nicht jedes Mal neu generiert werden muss.
Wir installieren unsere Abhängigkeiten mit dem yarn
, der eine Abkürzung für yarn install
ist. Die Befehle before_script
und after_script
werden verwendet, um Coverage-Ergebnisse in codeclimate
hochzuladen. Wir werden codeclimate
Kürze konfigurieren. Nach erfolgreichen yarn test
möchten wir auch eine yarn coverage
durchführen, die unseren Abdeckungsbericht auf overalls.io hochlädt.
Overalls
Overalls lädt Testabdeckungsdaten zur einfachen Visualisierung hoch. Wir können die Testabdeckung auf unserem lokalen Computer aus dem Abdeckungsordner anzeigen, aber Coveralls stellt sie außerhalb unseres lokalen Computers zur Verfügung.
- Besuchen Sie overalls.io und melden Sie sich entweder an oder registrieren Sie sich mit Ihrem Github-Konto.
- Bewegen Sie den Mauszeiger über die linke Seite des Bildschirms, um das Navigationsmenü anzuzeigen. Klicken Sie auf
ADD REPOS
. - Suchen Sie nach dem
express-api-template
Repo und schalten Sie die Abdeckung mit der Umschaltfläche auf der linken Seite ein. Wenn Sie es nicht finden können, klicken Sie oben rechts aufSYNC REPOS
und versuchen Sie es erneut. Beachten Sie, dass Ihr Repo öffentlich sein muss, es sei denn, Sie haben ein PRO-Konto. - Klicken Sie auf Details, um zur Seite mit den Repo-Details zu gelangen.
- Erstellen Sie die Datei .coveralls.yml im Stammverzeichnis Ihres Projekts und geben Sie den folgenden Code ein. Um das
repo_token
zu erhalten, klicken Sie auf die Repo-Details. Sie finden es leicht auf dieser Seite. Sie könnten einfach eine Browsersuche nachrepo_token
.
repo_token: get-this-from-repo-settings-on-coveralls.io
Dieses Token ordnet Ihre Abdeckungsdaten einem Repo auf Overalls zu. Fügen Sie nun den coverage
-Befehl zum scripts
-Abschnitt Ihrer package.json -Datei hinzu:
"coverage": "nyc report --reporter=text-lcov | coveralls"
Dieser Befehl lädt den Abdeckungsbericht im Ordner „ .nyc_output
“ auf „overalls.io“ hoch. Schalten Sie Ihre Internetverbindung ein und führen Sie Folgendes aus:
yarn coverage
Dadurch sollte der vorhandene Abdeckungsbericht auf Overalls hochgeladen werden. Aktualisieren Sie die Repo-Seite zu Overalls, um den vollständigen Bericht anzuzeigen.
Scrollen Sie auf der Detailseite nach unten, um den Abschnitt BADGE YOUR REPO
zu finden. Klicken Sie auf das Dropdown-Menü EMBED
, kopieren Sie den Markdown-Code und fügen Sie ihn in Ihre README -Datei ein.
Code Klima
Code Climate ist ein Tool, das uns hilft, die Codequalität zu messen. Es zeigt uns Wartungsmetriken, indem es unseren Code anhand einiger definierter Muster überprüft. Es erkennt Dinge wie unnötige Wiederholungen und tief verschachtelte for-Schleifen. Es sammelt auch Daten zur Testabdeckung, genau wie overalls.io.
- Besuchen Sie codeclimate.com und klicken Sie auf „Sign up with GitHub“. Melden Sie sich an, wenn Sie bereits ein Konto haben.
- Klicken Sie in Ihrem Dashboard auf
Add a repository
. - Suchen Sie das
express-api-template
Repo aus der Liste und klicken Sie aufAdd Repo
. - Warten Sie, bis der Build abgeschlossen ist, und leiten Sie ihn zum Repo-Dashboard weiter.
- Klicken Sie unter
Codebase Summary
aufTest Coverage
. Kopieren Sie im MenüTest coverage
dieTEST REPORTER ID
und fügen Sie sie in Ihre .travis.yml als Wert vonCC_TEST_REPORTER_ID
. - Klicken Sie auf derselben Seite in der linken Navigation unter
EXTRAS
auf Badges. Kopieren Sie die Badges fürmaintainability
undtest coverage
im Markdown-Format und fügen Sie sie in Ihre README.md -Datei ein.
Es ist wichtig zu beachten, dass es zwei Möglichkeiten gibt, Wartbarkeitsprüfungen zu konfigurieren. Es gibt die Standardeinstellungen, die auf jedes Repo angewendet werden, aber wenn Sie möchten, können Sie eine .codeclimate.yml -Datei im Stammverzeichnis Ihres Projekts bereitstellen. Ich werde die Standardeinstellungen verwenden, die Sie auf der Registerkarte Maintainability
der Seite mit den Repo-Einstellungen finden. Ich ermutige Sie, zumindest einen Blick darauf zu werfen. Wenn Sie dennoch Ihre eigenen Einstellungen konfigurieren möchten, finden Sie in dieser Anleitung alle Informationen, die Sie benötigen.

AppVeyor
AppVeyor und Travis CI sind beide automatisierte Testrunner. Der Hauptunterschied besteht darin, dass travis-ci Tests in einer Linux-Umgebung ausführt, während AppVeyor Tests in einer Windows-Umgebung ausführt. Dieser Abschnitt ist enthalten, um die ersten Schritte mit AppVeyor zu zeigen.
- Besuchen Sie AppVeyor und melden Sie sich an oder registrieren Sie sich.
- Klicken Sie auf der nächsten Seite auf
NEW PROJECT
. - Suchen Sie in der Repo-Liste nach dem
express-api-template
Repo. Bewegen Sie den Mauszeiger darüber und klicken Sie aufADD
. - Klicken Sie auf die Registerkarte
Settings
. Klicken Sie in der linken Navigation aufEnvironment
. Fügen SieTEST_ENV_VARIABLE
und seinen Wert hinzu. Klicken Sie unten auf der Seite auf „Speichern“. - Erstellen Sie die Datei appveyor.yml im Stammverzeichnis Ihres Projekts und fügen Sie den folgenden Code ein.
environment: matrix: - nodejs_version: "12" install: - yarn test_script: - yarn test build: off
Dieser Code weist AppVeyor an, unsere Tests mit Node.js v12 auszuführen. Anschließend installieren wir unsere Projektabhängigkeiten mit dem yarn
. test_script
gibt den Befehl zum Ausführen unseres Tests an. Die letzte Zeile weist AppVeyor an, keinen Build-Ordner zu erstellen.
Klicken Sie auf die Registerkarte Settings
. Klicken Sie in der linken Navigation auf Abzeichen. Kopieren Sie den Markdown-Code und fügen Sie ihn in Ihre README.md -Datei ein.
Übertragen Sie Ihren Code und übertragen Sie ihn auf GitHub. Wenn Sie alles wie angewiesen durchgeführt haben, sollten alle Tests bestanden sein und Sie sollten Ihre glänzenden neuen Abzeichen sehen, wie unten gezeigt. Überprüfen Sie erneut, ob Sie die Umgebungsvariablen auf Travis und AppVeyor festgelegt haben.

Jetzt ist ein guter Zeitpunkt, um unsere Änderungen zu übernehmen.
- Der entsprechende Zweig in meinem Repo ist 05-ci.
Controller hinzufügen
Derzeit verarbeiten wir die GET
-Anforderung an die Stamm-URL /v1
in src/routes/index.js . Dies funktioniert wie erwartet und es ist nichts falsch daran. Wenn Ihre Anwendung jedoch wächst, möchten Sie Ordnung halten. Sie möchten, dass Bedenken getrennt werden – Sie möchten eine klare Trennung zwischen dem Code, der die Anfrage verarbeitet, und dem Code, der die Antwort generiert, die an den Client zurückgesendet wird. Um dies zu erreichen, schreiben wir controllers
. Controller sind einfach Funktionen, die Anfragen verarbeiten, die über eine bestimmte URL kommen.
Erstellen Sie zunächst einen Ordner controllers/
innerhalb des Ordners src/
. Innerhalb controllers
zwei Dateien erstellt: index.js und home.js . Wir würden unsere Funktionen aus index.js heraus exportieren . Sie können home.js einen beliebigen Namen geben, aber normalerweise möchten Sie Controller nach dem benennen, was sie steuern. Beispielsweise könnten Sie eine Datei usersController.js haben, die alle Funktionen enthält, die sich auf Benutzer in Ihrer App beziehen.
Öffnen Sie src/controllers/home.js und geben Sie den folgenden Code ein:
import { testEnvironmentVariable } from '../settings'; export const indexPage = (req, res) => res.status(200).json({ message: testEnvironmentVariable });
Sie werden feststellen, dass wir nur die Funktion verschoben haben, die die Anfrage für die /
-Route verarbeitet.
Öffnen Sie src/controllers/index.js und geben Sie den folgenden Code ein.
// export everything from home.js export * from './home';
Wir exportieren alles aus der Datei home.js. Dadurch können wir unsere Importanweisungen verkürzen, import { indexPage } from '../controllers';
Öffnen Sie src/routes/index.js und ersetzen Sie den Code dort durch den folgenden:
import express from 'express'; import { indexPage } from '../controllers'; const indexRouter = express.Router(); indexRouter.get('/', indexPage); export default indexRouter;
Die einzige Änderung hier ist, dass wir eine Funktion bereitgestellt haben, um die Anfrage an die /
-Route zu verarbeiten.
Sie haben gerade erfolgreich Ihren ersten Controller geschrieben. Von hier aus müssen Sie nach Bedarf weitere Dateien und Funktionen hinzufügen.
Spielen Sie mit der App, indem Sie ein paar weitere Routen und Controller hinzufügen. Sie könnten eine Route und einen Controller für die About-Seite hinzufügen. Denken Sie jedoch daran, Ihren Test zu aktualisieren.
Führen Sie einen yarn test
zu bestätigen, dass wir nichts kaputt gemacht haben. Hat Ihr Test bestanden? Das ist cool.
Dies ist ein guter Punkt, um unsere Änderungen zu übernehmen.
- Der entsprechende Zweig in meinem Repo ist 06-Controller.
Verbinden der PostgreSQL
-Datenbank und Schreiben eines Modells
Unser Controller gibt derzeit hartcodierte Textnachrichten zurück. In einer realen App müssen wir oft Informationen aus einer Datenbank speichern und abrufen. In diesem Abschnitt verbinden wir unsere App mit einer PostgreSQL-Datenbank.
Wir werden das Speichern und Abrufen von einfachen Textnachrichten mithilfe einer Datenbank implementieren. Wir haben zwei Möglichkeiten, eine Datenbank einzurichten: Wir könnten eine von einem Cloud-Server bereitstellen oder unsere eigene lokal einrichten.
Ich würde Ihnen empfehlen, eine Datenbank von einem Cloud-Server bereitzustellen. 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.
Öffnen Sie querys.js und fügen Sie den folgenden Code ein:
export const createMessageTable = ` DROP TABLE IF EXISTS messages; CREATE TABLE IF NOT EXISTS messages ( id SERIAL PRIMARY KEY, name VARCHAR DEFAULT '', message VARCHAR NOT NULL ) `; export const insertMessages = ` INSERT INTO messages(name, message) VALUES ('chidimo', 'first message'), ('orji', 'second message') `; export const dropMessagesTable = 'DROP TABLE messages';
In dieser Datei definieren wir drei SQL-Abfragezeichenfolgen. Die erste Abfrage löscht die messages
und erstellt sie neu. Die zweite Abfrage fügt zwei Zeilen in die messages
ein. Hier können Sie gerne weitere Artikel hinzufügen. Die letzte Abfrage verwirft/löscht die messages
.
Öffnen Sie queryFunctions.js und fügen Sie den folgenden Code ein:
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 ]);
Hier erstellen wir Funktionen, um die zuvor definierten Abfragen auszuführen. Beachten Sie, dass die Funktion executeQueryArray
ein Array von Abfragen ausführt und darauf wartet, dass jede innerhalb der Schleife abgeschlossen wird. (Machen Sie so etwas jedoch nicht im Produktionscode). Dann lösen wir das Promise erst auf, wenn wir die letzte Abfrage in der Liste ausgeführt haben. Der Grund für die Verwendung eines Arrays ist, dass die Anzahl solcher Abfragen wächst, wenn die Anzahl der Tabellen in unserer Datenbank wächst.
Öffnen Sie runQuery.js und fügen Sie den folgenden Code ein:
import { createTables, insertIntoTables } from './queryFunctions'; (async () => { await createTables(); await insertIntoTables(); })();
Hier führen wir die Funktionen zum Erstellen der Tabelle aus und fügen die Nachrichten in die Tabelle ein. Lassen Sie uns einen Befehl im scripts
-Abschnitt unserer package.json hinzufügen , um diese Datei auszuführen.
"runQuery": "babel-node ./src/utils/runQuery"
Jetzt ausführen:
yarn runQuery
Wenn Sie Ihre Datenbank überprüfen, sehen Sie, dass die messages
erstellt wurde und dass die Nachrichten in die Tabelle eingefügt wurden.
Wenn Sie ElephantSQL verwenden, klicken Sie auf der Detailseite der Datenbank im linken Navigationsmenü auf BROWSER
. Wählen Sie die messages
aus und klicken Sie auf Execute
. Sie sollten die Nachrichten aus der Datei querys.js sehen.
Lassen Sie uns einen Controller und eine Route erstellen, um die Nachrichten aus unserer Datenbank anzuzeigen.
Erstellen Sie eine neue Controller-Datei src/controllers/messages.js und fügen Sie den folgenden Code ein:
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 }); } };
Wir importieren unsere Modellklasse und erstellen eine neue Instanz dieses Model
. Dies repräsentiert die messages
in unserer Datenbank. Wir verwenden dann die select
-Methode des Modells, um unsere Datenbank abzufragen. Die Daten ( name
und message
), die wir erhalten, werden als JSON in der Antwort gesendet.
Wir definieren den messagesPage
-Controller als async
Funktion. Da node-postgres
Abfragen ein Versprechen zurückgeben, await
wir auf das Ergebnis dieser Abfrage. Wenn wir während der Abfrage auf einen Fehler stoßen, fangen wir ihn ab und zeigen dem Benutzer den Stack an. Sie sollten entscheiden, wie Sie mit dem Fehler umgehen möchten.
Fügen Sie den Endpunkt „Get Messages“ zu „src/routes/index.js“ hinzu und aktualisieren Sie die Importzeile.
# update the import line import { indexPage, messagesPage } from '../controllers'; # add the get messages endpoint indexRouter.get('/messages', messagesPage)
Besuchen Sie https://localhost:3000/v1/messages und Sie sollten die unten gezeigten Nachrichten sehen.

Lassen Sie uns nun unsere Testdatei aktualisieren. Wenn Sie TDD durchführen, schreiben Sie normalerweise Ihre Tests, bevor Sie den Code implementieren, der den Test erfolgreich macht. Ich gehe hier den umgekehrten Weg, weil wir noch am Aufbau der Datenbank arbeiten.
Erstellen Sie eine neue Datei, hooks.js im Ordner test/
und geben Sie den folgenden Code ein:
import { dropTables, createTables, insertIntoTables, } from '../src/utils/queryFunctions'; before(async () => { await createTables(); await insertIntoTables(); }); after(async () => { await dropTables(); });
Wenn unser Test beginnt, findet Mocha diese Datei und führt sie aus, bevor eine Testdatei ausgeführt wird. Es führt den before
-Hook aus, um die Datenbank zu erstellen und einige Elemente darin einzufügen. Danach laufen die Testdateien. Sobald der Test abgeschlossen ist, führt Mocha den after
-Hook aus, in dem wir die Datenbank löschen. Dadurch wird sichergestellt, dass wir jedes Mal, wenn wir unsere Tests durchführen, dies mit sauberen und neuen Datensätzen in unserer Datenbank tun.
Erstellen Sie eine neue Testdatei test/messages.test.js und fügen Sie den folgenden Code hinzu:
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(); }); }); });
Wir behaupten, dass das Ergebnis des Aufrufs von /messages
ein Array ist. Für jedes Nachrichtenobjekt behaupten wir, dass es die Eigenschaft name
und message
hat.
Der letzte Schritt in diesem Abschnitt besteht darin, die CI-Dateien zu aktualisieren.
Fügen Sie der .travis.yml -Datei die folgenden Abschnitte hinzu:
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
Dadurch wird Travis angewiesen, eine PostgreSQL 10-Datenbank hochzufahren, bevor unsere Tests ausgeführt werden.
Fügen Sie den Befehl zum Erstellen der Datenbank als ersten Eintrag im Abschnitt before_script
:
# add this as the first line in the before_script section - psql -c 'create database testdb;' -U postgres
Erstellen Sie die Umgebungsvariable CONNECTION_STRING
auf Travis und verwenden Sie den folgenden Wert:
CONNECTION_STRING="postgresql://postgres:postgres@localhost:5432/testdb"
Fügen Sie der .appveyor.yml -Datei die folgenden Abschnitte hinzu:
before_test: - SET PGUSER=postgres - SET PGPASSWORD=Password12! - PATH=C:\Program Files\PostgreSQL\10\bin\;%PATH% - createdb testdb services: - postgresql101
Fügen Sie appveyor die Umgebungsvariable der Verbindungszeichenfolge hinzu. Verwenden Sie die folgende Zeile:
CONNECTION_STRING=postgresql://postgres:Password12!@localhost:5432/testdb
Übertragen Sie jetzt Ihre Änderungen und übertragen Sie sie auf GitHub. Ihre Tests sollten sowohl Travis CI als auch AppVeyor bestehen.
- Der entsprechende Branch in meinem Repo ist 07-connect-postgres.
Hinweis : Ich hoffe, dass bei Ihnen alles gut funktioniert, aber falls Sie aus irgendeinem Grund Probleme haben sollten, können Sie jederzeit meinen Code im Repo überprüfen!
Sehen wir uns nun an, wie wir unserer Datenbank eine Nachricht hinzufügen können. Für diesen Schritt benötigen wir eine Möglichkeit, POST
Anforderungen an unsere URL zu senden. Ich werde Postman verwenden, um POST
Anfragen zu senden.
Lassen Sie uns den TDD-Weg gehen und unseren Test aktualisieren, um widerzuspiegeln, was wir zu erreichen erwarten.
Öffnen Sie test/message.test.js und fügen Sie den folgenden Testfall hinzu:
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(); }); });
Dieser Test stellt eine POST-Anfrage an den Endpunkt /v1/messages
und wir erwarten, dass ein Array zurückgegeben wird. Wir prüfen auch die Eigenschaften id
, name
und message
im Array.
Führen Sie Ihre Tests durch, um festzustellen, ob dieser Fall fehlschlägt. Lass es uns jetzt reparieren.
Um Post-Requests zu versenden, verwenden wir die Post-Methode des Servers. Wir senden auch den Namen und die Nachricht, die wir einfügen möchten. Wir erwarten, dass die Antwort ein Array mit einer Eigenschafts- id
und den anderen Informationen ist, aus denen die Abfrage besteht. Die id
ist ein Beweis dafür, dass ein Datensatz in die Datenbank eingefügt wurde.
Öffnen Sie src/models/model.js und fügen Sie die insert
-Methode hinzu:
async insertWithReturn(columns, values) { const query = ` INSERT INTO ${this.table}(${columns}) VALUES (${values}) RETURNING id, ${columns} `; return this.pool.query(query); }
Dies ist die Methode, mit der wir Nachrichten in die Datenbank einfügen können. Nach dem Einfügen des Elements gibt es die id
, den name
und die message
zurück.
Öffnen Sie src/controllers/messages.js und fügen Sie den folgenden Controller hinzu:
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 }); } };
Wir destrukturieren den Anforderungstext, um den Namen und die Nachricht zu erhalten. Dann verwenden wir die Werte, um einen SQL-Abfragestring zu bilden, den wir dann mit der insertWithReturn
Methode unseres Modells ausführen.
Fügen Sie den folgenden POST
-Endpunkt zu /src/routes/index.js hinzu und aktualisieren Sie Ihre Importzeile.
import { indexPage, messagesPage, addMessage } from '../controllers'; indexRouter.post('/messages', addMessage);
Führen Sie Ihre Tests durch, um zu sehen, ob sie bestehen.
Öffnen Sie Postman und senden Sie eine POST
Anforderung an den messages
. Wenn Sie gerade Ihren Test ausgeführt haben, denken Sie daran, eine yarn query
auszuführen, um die messages
neu zu erstellen.
yarn query


Übernehmen Sie Ihre Änderungen und übertragen Sie sie auf GitHub. Ihre Tests sollten sowohl Travis als auch AppVeyor bestehen. Ihre Testabdeckung wird um ein paar Punkte sinken, aber das ist in Ordnung.
- Der entsprechende Zweig in meinem Repo ist 08-post-to-db.
Middleware
Unsere Erörterung von Express ist nicht vollständig, ohne über Middleware zu sprechen. Die Express-Dokumentation beschreibt eine Middleware wie folgt:
„[...] Funktionen, die Zugriff auf das Request-Objekt (req
), das Response-Objekt (res
) und die nächste Middleware-Funktion im Request-Response-Zyklus der Anwendung haben. Die next-Middleware-Funktion wird üblicherweise durch eine Variable namensnext
bezeichnet.“
Eine Middleware kann eine beliebige Anzahl von Funktionen wie Authentifizierung, Änderung des Anforderungstexts usw. ausführen. Informationen zur Verwendung von Middleware finden Sie in der Express-Dokumentation.
Wir werden eine einfache Middleware schreiben, die den Anforderungstext ändert. Unsere Middleware hängt das Wort SAYS:
an die eingehende Nachricht an, bevor sie in der Datenbank gespeichert wird.
Bevor wir beginnen, ändern wir unseren Test so, dass er widerspiegelt, was wir erreichen wollen.
Öffnen Sie test/messages.test.js und ändern Sie die letzte Expect-Zeile im posts message
-Testfall:
it('posts messages', done => { ... expect(m).to.have.property('message', `SAYS: ${data.message}`); # update this line ... });
Wir behaupten, dass die Zeichenfolge SAYS:
an die Nachricht angehängt wurde. Führen Sie Ihre Tests durch, um sicherzustellen, dass dieser Testfall fehlschlägt.
Lassen Sie uns nun den Code schreiben, damit der Test bestanden wird.
Erstellen Sie einen neuen Ordner „middleware middleware/
“ im Ordner „ src/
“. Erstellen Sie zwei Dateien in diesem Ordner:
- middleware.js
- index.js
Geben Sie den folgenden Code in middleware.js ein:
export const modifyMessage = (req, res, next) => { req.body.message = `SAYS: ${req.body.message}`; next(); };
Hier hängen wir die Zeichenfolge SAYS:
an die Nachricht im Anforderungstext an. Danach müssen wir die Funktion next()
aufrufen, um die Ausführung an die nächste Funktion in der Anfrage-Antwort-Kette zu übergeben. Jede Middleware muss die next
Funktion aufrufen, um die Ausführung an die nächste Middleware im Request-Response-Zyklus weiterzuleiten.
Geben Sie den folgenden Code in index.js ein :
# export everything from the middleware file export * from './middleware';
Dadurch wird die Middleware exportiert, die wir in der Datei /middleware.js haben. Im Moment haben wir nur die modifyMessage
Middleware.
Öffnen Sie src/routes/index.js und fügen Sie die Middleware der Post-Message-Request-Response-Kette hinzu.
import { modifyMessage } from '../middleware'; indexRouter.post('/messages', modifyMessage, addMessage);
Wir können sehen, dass die modifyMessage
Funktion vor der addMessage
-Funktion kommt. Wir rufen die addMessage
Funktion auf, indem wir next
in der modifyMessage
Middleware aufrufen. Kommentieren Sie als Experiment die next()
Zeile in der modifyMessage
Middle aus und beobachten Sie, wie die Anfrage hängt.
Öffnen Sie Postman und erstellen Sie eine neue Nachricht. Sie sollten die angehängte Zeichenfolge sehen.

Dies ist ein guter Punkt, um unsere Änderungen zu übernehmen.
- Der entsprechende Zweig in meinem Repo ist 09-Middleware.
Fehlerbehandlung und asynchrone Middleware
Fehler sind in jeder Anwendung unvermeidlich. Die Aufgabe des Entwicklers besteht darin, mit Fehlern so elegant wie möglich umzugehen.
Im Express:
„ Fehlerbehandlung bezieht sich darauf, wie Express Fehler abfängt und verarbeitet, die sowohl synchron als auch asynchron auftreten.
Wenn wir nur synchrone Funktionen schreiben würden, müssten wir uns vielleicht nicht so viele Gedanken über die Fehlerbehandlung machen, da Express diese bereits hervorragend behandelt. Laut den Dokumenten:
„Fehler, die im synchronen Code innerhalb von Route-Handlern und Middleware auftreten, erfordern keine zusätzliche Arbeit.“
Aber sobald wir damit beginnen, asynchrone Router-Handler und Middleware zu schreiben, müssen wir uns um die Fehlerbehandlung kümmern.
Unsere modifyMessage
Middleware ist eine synchrone Funktion. Wenn in dieser Funktion ein Fehler auftritt, wird Express damit problemlos umgehen. Sehen wir uns an, wie wir mit Fehlern in asynchroner Middleware umgehen.
Angenommen, wir möchten vor dem Erstellen einer Nachricht ein Bild von der Lorem Picsum-API mithilfe dieser URL https://picsum.photos/id/0/info
abrufen. Dies ist eine asynchrone Operation, die entweder erfolgreich sein oder fehlschlagen kann, und die einen Fall darstellt, mit dem wir uns befassen müssen.
Beginnen Sie mit der Installation von Axios.
# install axios yarn add axios
Öffnen Sie src/middleware/middleware.js und fügen Sie die folgende Funktion hinzu:
export const performAsyncAction = async (req, res, next) => { try { await axios.get('https://picsum.photos/id/0/info'); next(); } catch (err) { next(err); } };
In dieser async
Funktion await
wir auf einen Aufruf an eine API (wir benötigen die zurückgegebenen Daten nicht wirklich) und rufen anschließend die next
Funktion in der Anforderungskette auf. Wenn die Anfrage fehlschlägt, fangen wir den Fehler ab und leiten ihn an next
. Sobald Express diesen Fehler sieht, überspringt es alle andere Middleware in der Kette. Wenn wir next(err)
nicht aufgerufen haben, bleibt die Anfrage hängen. Wenn wir nur next()
ohne err
aufgerufen haben, wird die Anfrage fortgesetzt, als ob nichts passiert wäre, und der Fehler wird nicht abgefangen.
Importieren Sie diese Funktion und fügen Sie sie der Middleware-Kette der Post-Messages-Route hinzu:
import { modifyMessage, performAsyncAction } from '../middleware'; indexRouter.post('/messages', modifyMessage, performAsyncAction, addMessage);
Öffnen Sie src/app.js und fügen Sie den folgenden Code direkt vor der export default app
-Zeile hinzu.
app.use((err, req, res, next) => { res.status(400).json({ error: err.stack }); }); export default app;
Dies ist unser Fehlerbehandler. Laut dem Express-Fehlerbehandlungsdokument:
„[...] Fehlerbehandlungsfunktionen haben vier statt drei Argumente: (err, req, res, next)
.“
Beachten Sie, dass diese Fehlerbehandlungsroutine nach jedem Aufruf von app.use()
an letzter Stelle stehen muss. Sobald wir auf einen Fehler stoßen, geben wir den Stack-Trace mit dem Statuscode 400
zurück. Du kannst mit dem Fehler machen was du willst. Vielleicht möchten Sie es protokollieren oder irgendwohin schicken.
Dies ist ein guter Ort, um Ihre Änderungen zu übernehmen.
- Der entsprechende Zweig in meinem Repo ist 10-async-Middleware.
Bereitstellen auf Heroku
- Gehen Sie zunächst zu https://www.heroku.com/ und melden Sie sich an oder registrieren Sie sich.
- Laden Sie die Heroku-CLI von hier herunter und installieren Sie sie.
- Öffnen Sie ein Terminal im Projektordner, um den Befehl auszuführen.
# login to heroku on command line heroku login
Dies öffnet ein Browserfenster und fordert Sie auf, sich bei Ihrem Heroku-Konto anzumelden.
Melden Sie sich an, um Ihrem Terminal Zugriff auf Ihr Heroku-Konto zu gewähren, und erstellen Sie eine neue Heroku-App, indem Sie Folgendes ausführen:
#app name is up to you heroku create app-name
Dadurch wird die App auf Heroku erstellt und zwei URLs zurückgegeben.
# app production url and git url https://app-name.herokuapp.com/ | https://git.heroku.com/app-name.git
Kopieren Sie die URL auf der rechten Seite und führen Sie den folgenden Befehl aus. Beachten Sie, dass dieser Schritt optional ist, da Sie möglicherweise feststellen, dass Heroku die Remote-URL bereits hinzugefügt hat.
# add heroku remote url git remote add heroku https://git.heroku.com/my-shiny-new-app.git
Öffnen Sie ein Nebenterminal und führen Sie den folgenden Befehl aus. Dies zeigt Ihnen das App-Protokoll in Echtzeit, wie im Bild gezeigt.
# see process logs heroku logs --tail

Führen Sie die folgenden drei Befehle aus, um die erforderlichen Umgebungsvariablen festzulegen:
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
Denken Sie daran, dass wir in unseren Skripten Folgendes festgelegt haben:
"prestart": "babel ./src --out-dir build", "start": "node ./build/bin/www",
Um die App zu starten, muss sie im prestart
Schritt mit babel auf ES5 herunterkompiliert werden, da babel nur in unseren Entwicklungsabhängigkeiten existiert. Wir müssen NPM_CONFIG_PRODUCTION
auf false
setzen, um diese ebenfalls installieren zu können.
Um zu bestätigen, dass alles richtig eingestellt ist, führen Sie den folgenden Befehl aus. Sie können auch die Registerkarte settings
auf der App-Seite besuchen und auf Reveal Config Vars
klicken.
# check configuration variables heroku config
Führen Sie nun git push heroku
.
Führen Sie zum Öffnen der App Folgendes aus:
# open /v1 route heroku open /v1 # open /v1/messages route heroku open /v1/messages
Wenn Sie wie ich dieselbe PostgresSQL-Datenbank sowohl für die Entwicklung als auch für die Produktion verwenden, stellen Sie möglicherweise fest, dass die Datenbank jedes Mal gelöscht wird, wenn Sie Ihre Tests ausführen. Um es neu zu erstellen, können Sie einen der folgenden Befehle ausführen:
# run script locally yarn runQuery # run script with heroku heroku run yarn runQuery
Kontinuierliche Bereitstellung (CD) mit Travis
Lassen Sie uns nun Continuous Deployment (CD) hinzufügen, um den CI/CD-Fluss zu vervollständigen. Wir werden nach jedem erfolgreichen Testlauf von Travis bereitstellen.
Der erste Schritt ist die Installation von Travis CI. (Sie finden die Installationsanweisungen hier.) Melden Sie sich nach erfolgreicher Installation von Travis CI an, indem Sie den folgenden Befehl ausführen. (Beachten Sie, dass dies in Ihrem Projekt-Repository erfolgen sollte.)
# login to travis travis login --pro # use this if you're using two factor authentication travis login --pro --github-token enter-github-token-here
Wenn Ihr Projekt auf travis-ci.org gehostet wird, entfernen Sie das Flag --pro
. Um ein GitHub-Token zu erhalten, besuchen Sie die Seite mit den Entwicklereinstellungen Ihres Kontos und generieren Sie eines. Dies gilt nur, wenn Ihr Konto mit 2FA gesichert ist.
Öffnen Sie Ihre .travis.yml und fügen Sie einen Bereitstellungsabschnitt hinzu:
deploy: provider: heroku app: master: app-name
Hier geben wir an, dass wir Heroku bereitstellen möchten. Der App-Unterabschnitt gibt an, dass wir den master
-Branch unseres Repositorys für die app-name
App auf Heroku bereitstellen möchten. Es ist möglich, verschiedene Zweige für verschiedene Apps bereitzustellen. Hier können Sie mehr über die verfügbaren Optionen lesen.
Führen Sie den folgenden Befehl aus, um Ihren Heroku-API-Schlüssel zu verschlüsseln und ihn dem Bereitstellungsabschnitt hinzuzufügen:
# encrypt heroku API key and add to .travis.yml travis encrypt $(heroku auth:token) --add deploy.api_key --pro
Dadurch wird der folgende Unterabschnitt zum Bereitstellungsabschnitt hinzugefügt.
api_key: secure: very-long-encrypted-api-key-string
Übertragen Sie jetzt Ihre Änderungen und übertragen Sie sie auf GitHub, während Sie Ihre Protokolle überwachen. Sie sehen, dass der Build ausgelöst wird, sobald der Travis-Test abgeschlossen ist. Auf diese Weise würden die Änderungen bei einem fehlgeschlagenen Test niemals bereitgestellt. Ebenso würde der gesamte Testlauf fehlschlagen, wenn der Build fehlschlägt. Damit ist der CI/CD-Fluss abgeschlossen.
- Der entsprechende Zweig in meinem Repo ist 11-cd.
Fazit
Wenn Sie es bis hierher geschafft haben, sage ich: „Daumen hoch!“ In diesem Tutorial haben wir erfolgreich ein neues Express-Projekt eingerichtet. Wir haben Entwicklungsabhängigkeiten sowie Continuous Integration (CI) konfiguriert. Anschließend haben wir asynchrone Funktionen geschrieben, um Anfragen an unsere API-Endpunkte zu verarbeiten – abgeschlossen mit Tests. Anschließend haben wir uns kurz die Fehlerbehandlung angeschaut. Schließlich haben wir unser Projekt in Heroku bereitgestellt und Continuous Deployment konfiguriert.
Sie haben jetzt eine Vorlage für Ihr nächstes Backend-Projekt. Wir haben gerade genug getan, um Ihnen den Einstieg zu erleichtern, aber Sie sollten weiter lernen, um weiterzumachen. Sehen Sie sich unbedingt auch die Express.js-Dokumentation an. Wenn Sie lieber MongoDB
anstelle von PostgreSQL
verwenden möchten, habe ich hier eine Vorlage, die genau das tut. Sie können es für die Einrichtung überprüfen. Es hat nur wenige Unterschiede.
Ressourcen
- „Erstellen Sie ein Express-API-Backend mit MongoDB“, Orji Chidi Matthew, GitHub
- „Eine kurze Anleitung zum Verbinden von Middleware“, Stephen Sugden
- „Express-API-Vorlage“, GitHub
- „AppVeyor vs. Travis CI“, StackShare
- „Die Heroku-CLI“, Heroku Dev Center
- „Heroku-Bereitstellung“, Travis CI
- „Mit Middleware“, Express.js
- „Fehlerbehandlung“, Express.js
- „Erste Schritte“, Mocha
-
nyc
(GitHub) - ElephantSQL
- Postbote
- ausdrücken
- Travis C.I
- Code Klima
- PostgreSQL
- pgAdmin