如何使用 PostgreSQL 設置 Express API 後端項目
已發表: 2022-03-10我們將採用測試驅動開發 (TDD) 方法和設置持續集成 (CI) 作業來自動在 Travis CI 和 AppVeyor 上運行我們的測試,並提供代碼質量和覆蓋率報告。 我們將學習控制器、模型(使用 PostgreSQL)、錯誤處理和異步 Express 中間件。 最後,我們將通過在 Heroku 上配置自動部署來完成 CI/CD 管道。
聽起來很多,但是本教程針對的是準備好嘗試具有一定複雜性的後端項目的初學者,並且他們可能仍然對所有部分如何在實際項目中組合在一起感到困惑.
它既健壯又不會讓人不知所措,並且可以分解為您可以在合理的時間內完成的部分。
入門
第一步是為項目創建一個新目錄並啟動一個新的節點項目。 需要節點才能繼續本教程。 如果您沒有安裝它,請前往官方網站,下載並安裝它,然後再繼續。
我將使用 yarn 作為這個項目的包管理器。 此處有適用於您的特定操作系統的安裝說明。 如果您願意,請隨意使用 npm。
打開您的終端,創建一個新目錄,然後啟動一個 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
回答以下問題以生成package.json文件。 此文件包含有關您的項目的信息。 此類信息的示例包括它使用的依賴項、啟動項目的命令等。
您現在可以在您選擇的編輯器中打開項目文件夾。 我使用視覺工作室代碼。 這是一個免費的 IDE,帶有大量插件,讓您的生活更輕鬆,並且適用於所有主要平台。 您可以從官方網站下載它。
在項目文件夾中創建以下文件:
- 自述文件.md
- .editorconfig
以下是 EditorConfig 網站上 .editorconfig 功能的描述。 (如果你是單獨工作,你可能不需要它,但它沒有害處,所以我把它留在這裡。)
“EditorConfig 有助於為跨各種編輯器和 IDE 處理同一項目的多個開發人員保持一致的編碼風格。”
打開.editorconfig
並粘貼以下代碼:
root = true [*] indent_style = space indent_size = 2 charset = utf-8 trim_trailing_whitespace = false insert_final_newline = true
[*]
表示我們要將其下的規則應用於項目中的每個文件。 我們想要兩個空格的縮進大小和UTF-8
字符集。 我們還想修剪尾隨空白並在我們的文件中插入最後一個空行。
打開README.md並將項目名稱添加為第一級元素。
# Express API template
讓我們立即添加版本控制。
# initialize the project folder as a git repository git init
創建一個.gitignore文件並輸入以下行:
node_modules/ yarn-error.log .env .nyc_output coverage build/
這些是我們不想跟踪的所有文件和文件夾。 我們的項目中還沒有它們,但我們會在繼續進行時看到它們。
此時,您應該具有以下文件夾結構。
EXPRESS-API-TEMPLATE ├── .editorconfig ├── .gitignore ├── package.json └── README.md
我認為這是提交更改並將其推送到 GitHub 的好點。
開始一個新的快遞項目
Express 是一個用於構建 Web 應用程序的 Node.js 框架。 據官網介紹,這是一個
用於 Node.js 的快速、獨立、簡約的 Web 框架。
Node.js 還有其他很棒的 Web 應用程序框架,但 Express 非常受歡迎,在撰寫本文時,它的 GitHub 星數已超過 47k。
在本文中,我們不會對構成 Express 的所有部分進行大量討論。 對於該討論,我建議您查看 Jamie 的系列。 第一部分在這裡,第二部分在這裡。
安裝 Express 並啟動一個新的 Express 項目。 可以從頭開始手動設置 Express 服務器,但為了讓我們的生活更輕鬆,我們將使用 express-generator 設置應用程序框架。
# 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
-f
標誌強制 Express 在當前目錄中創建項目。
我們現在將執行一些房屋清潔操作。
- 刪除文件index/users.js 。
- 刪除文件夾
public/
和views/
。 - 將文件bin/www重命名為bin/www.js 。
- 使用命令
yarn remove jade
jade
- 創建一個名為
src/
的新文件夾並在其中移動以下內容: 1. app.js文件 2.bin/
文件夾 3.routes/
文件夾在裡面。 - 打開package.json並更新
start
腳本,如下所示。
"start": "node ./src/bin/www"
此時,您的項目文件夾結構如下所示。 您可以看到 VS Code 如何突出顯示已發生的文件更改。
EXPRESS-API-TEMPLATE ├── node_modules ├── src | ├── bin │ │ ├── www.js │ ├── routes │ | ├── index.js │ └── app.js ├── .editorconfig ├── .gitignore ├── package.json ├── README.md └── yarn.lock
打開src/app.js並將內容替換為以下代碼。
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;
在需要一些庫之後,我們指示 Express 使用indexRouter
處理到達/v1
的每個請求。
將routes/index.js的內容替換為以下代碼:
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;
我們抓取 Express,從中創建一個路由器並提供/
路由,該路由返回狀態碼200
和 JSON 消息。
使用以下命令啟動應用程序:
# start the app yarn start
如果您已正確設置所有內容,您應該只會在終端中看到$ node ./src/bin/www
。
在瀏覽器中訪問https://localhost:3000/v1
。 您應該看到以下消息:
{ "message": "Welcome to Express API template" }
這是提交更改的好點。
- 我的倉庫中對應的分支是 01-install-express。
將我們的代碼轉換為ES6
express-generator
生成的代碼在ES5
中,但在本文中,我們將使用ES6
語法編寫所有代碼。 因此,讓我們將現有代碼轉換為ES6
。
將routes/index.js的內容替換為以下代碼:
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;
它與我們在上面看到的代碼相同,但在/
路由處理程序中帶有 import 語句和箭頭函數。
將src/app.js的內容替換為以下代碼:
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;
現在讓我們看一下src/bin/www.js的內容。 我們將逐步構建它。 刪除src/bin/www.js
的內容並粘貼到下面的代碼塊中。
#!/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
此代碼檢查是否在環境變量中指定了自定義端口。 如果沒有設置默認端口值3000
在應用實例上設置,在通過normalizePort
規範化為字符串或數字之後。 然後從http
模塊創建服務器, app
作為回調函數。
#!/usr/bin/env node
行是可選的,因為我們會在要執行此文件時指定節點。 但請確保它位於src/bin/www.js文件的第 1 行或將其完全刪除。
我們來看看錯誤處理函數。 在創建服務器的行之後復制並粘貼此代碼塊。
/** * 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);
onError
函數偵聽 http 服務器中的錯誤並顯示相應的錯誤消息。 onListening
函數只是將服務器正在偵聽的端口輸出到控制台。 最後,服務器在指定的地址和端口監聽傳入的請求。
此時,我們現有的所有代碼都是ES6
語法。 停止您的服務器(使用Ctrl + C )並運行yarn start
。 你會得到一個錯誤SyntaxError: Invalid or unexpected token
。 發生這種情況是因為 Node(在撰寫本文時)不支持我們在代碼中使用的某些語法。
我們現在將在下一節中解決這個問題。
配置開發依賴: babel
、 nodemon
、 eslint
和prettier
是時候設置我們在項目的這個階段需要的大部分腳本了。
使用以下命令安裝所需的庫。 您可以復制所有內容並將其粘貼到終端中。 註釋行將被跳過。
# install babel scripts yarn add @babel/cli @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/register @babel/runtime @babel/node --dev
這會將所有列出的 babel 腳本安裝為開發依賴項。 檢查您的package.json文件,您應該會看到devDependencies
部分。 所有已安裝的腳本都將在此處列出。
我們使用的 babel 腳本解釋如下:
@babel/cli | 使用babel 的必需安裝。 它允許從終端使用 Babel,並且可以作為./node_modules/.bin/babel 。 |
@babel/core | 核心 Babel 功能。 這是必需的安裝。 |
@babel/node | 它的工作方式與 Node.js CLI 完全一樣,還具有使用babel 預設和插件進行編譯的額外好處。 這是與nodemon 一起使用所必需的。 |
@babel/plugin-transform-runtime | 這有助於避免編譯輸出中的重複。 |
@babel/preset-env | 負責執行代碼轉換的插件集合。 |
@babel/register | 這會即時編譯文件,並在測試期間指定為要求。 |
@babel/runtime | 這與@babel/plugin-transform-runtime 結合使用。 |
在項目的根目錄下創建一個名為.babelrc的文件並添加以下代碼:
{ "presets": ["@babel/preset-env"], "plugins": ["@babel/transform-runtime"] }
讓我們安裝nodemon
# install nodemon yarn add nodemon --dev
nodemon
是一個庫,它監視我們的項目源代碼,並在觀察到任何更改時自動重新啟動我們的服務器。
在項目的根目錄下創建一個名為nodemon.json的文件並添加以下代碼:
{ "watch": [ "package.json", "nodemon.json", ".eslintrc.json", ".babelrc", ".prettierrc", "src/" ], "verbose": true, "ignore": ["*.test.js", "*.spec.js"] }
watch
鍵告訴nodemon
哪些文件和文件夾要監視更改。 因此,每當這些文件中的任何一個發生更改時,nodemon 都會重新啟動服務器。 ignore
鍵告訴它文件不要監視更改。
現在更新package.json文件的scripts
部分,如下所示:
# 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
腳本構建src/
文件夾的內容並將其放入build/
文件夾中。 當您發出yarn start
命令時,此腳本在start
腳本之前首先運行。 -
start
腳本現在提供build/
文件夾的內容,而不是我們之前提供的src/
文件夾。 這是您在生產中提供文件時將使用的腳本。 事實上,像 Heroku 這樣的服務會在你部署時自動運行這個腳本。 -
yarn startdev
用於在開發過程中啟動服務器。 從現在開始,我們將在開發應用程序時使用此腳本。 請注意,我們現在使用babel-node
來運行應用程序而不是常規node
。--exec
標誌強制babel-node
服務於src/
文件夾。 對於start
腳本,我們使用node
,因為build/
文件夾中的文件已編譯為 ES5。
運行yarn startdev
並訪問 https://localhost:3000/v1。 您的服務器應該重新啟動並運行。
本節的最後一步是配置ESLint
和prettier
。 ESLint 有助於強制執行語法規則,而 prettier 有助於正確格式化我們的代碼以提高可讀性。
使用以下命令添加它們。 您應該在單獨的終端上運行它,同時觀察我們的服務器正在運行的終端。 您應該看到服務器重新啟動。 這是因為我們正在監控package.json文件的變化。
# install elsint and prettier yarn add eslint eslint-config-airbnb-base eslint-plugin-import prettier --dev
現在在項目root
中創建.eslintrc.json文件並添加以下代碼:
{ "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] } }
該文件主要定義了一些規則, eslint
將根據這些規則檢查我們的代碼。 您可以看到我們正在擴展 Airbnb 使用的樣式規則。
在"rules"
部分,我們定義了eslint
在遇到某些違規時是否應該顯示警告或錯誤。 例如,它會在我們的終端上顯示任何不使用 2 個空格的縮進的警告消息。 值[0]
關閉規則,這意味著如果我們違反該規則,我們將不會收到警告或錯誤。
創建一個名為.prettierrc的文件並添加以下代碼:
{ "trailingComma": "es5", "tabWidth": 2, "semi": true, "singleQuote": true }
我們將選項卡寬度設置為2
,並在整個應用程序中強制使用單引號。 請查看更漂亮的指南以獲取更多樣式選項。
現在將以下腳本添加到您的package.json :
# add these one after the other "lint": "./node_modules/.bin/eslint ./src" "pretty": "prettier --write '**/*.{js,json}' '!node_modules/**'" "postpretty": "yarn lint --fix"
運行yarn lint
。 您應該會在控制台中看到許多錯誤和警告。
pretty
命令美化了我們的代碼。 postpretty
命令在之後立即運行。 它運行帶有--fix
標誌的lint
命令。 這個標誌告訴ESLint
自動修復常見的 linting 問題。 通過這種方式,我主要運行yarn pretty
命令,而不用關心lint
命令。
跑yarn pretty
。 您應該看到我們只有兩個關於bin/www.js文件中存在alert
的警告。
這是我們的項目結構此時的樣子。
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
您可能會發現您的項目根目錄中有一個附加文件yarn-error.log
。 將其添加到.gitignore
文件中。 提交您的更改。
- 我的倉庫中此時對應的分支是 02-dev-dependencies。
.env文件中的設置和環境變量
在幾乎每個項目中,您都需要在某個地方存儲將在整個應用程序中使用的設置,例如 AWS 密鑰。 我們將這些設置存儲為環境變量。 這使它們遠離窺探,我們可以根據需要在應用程序中使用它們。
我喜歡有一個settings.js文件,我可以用它來讀取我的所有環境變量。 然後,我可以在我的應用程序中的任何位置引用設置文件。 您可以隨意命名此文件,但對於命名此類文件settings.js或config.js存在某種共識。
對於我們的環境變量,我們將它們保存在一個.env
文件中,並從那裡將它們讀入我們的settings
文件。
在項目的根目錄下創建.env文件並輸入以下行:
TEST_ENV_VARIABLE="Environment variable is coming across"
為了能夠將環境變量讀取到我們的項目中,有一個不錯的庫dotenv
可以讀取我們的.env
文件並允許我們訪問其中定義的環境變量。 讓我們安裝它。
# install dotenv yarn add dotenv
將.env文件添加到nodemon
正在監視的文件列表中。
現在,在src/
文件夾中創建settings.js文件並添加以下代碼:
import dotenv from 'dotenv'; dotenv.config(); export const testEnvironmentVariable = process.env.TEST_ENV_VARIABLE;
我們導入dotenv
包並調用它的 config 方法。 然後我們導出我們在testEnvironmentVariable
文件中設置的.env
。
打開src/routes/index.js並將代碼替換為下面的代碼。
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;
我們在這裡所做的唯一更改是我們從settings
文件中導入testEnvironmentVariable
並用作來自/
路由的請求的返回消息。
訪問 https://localhost:3000/v1 ,您應該會看到消息,如下所示。
{ "message": "Environment variable is coming across." }
就是這樣。 從現在開始,我們可以根據需要添加任意數量的環境變量,並且可以從我們的settings.js文件中導出它們。
這是提交代碼的好方法。 記得美化和整理你的代碼。
- 我的倉庫中對應的分支是 03-env-variables。
編寫我們的第一個測試
是時候將測試納入我們的應用程序了。 讓開發人員對他們的代碼充滿信心的一件事是測試。 我敢肯定,您已經在網絡上看到了無數宣揚測試驅動開發 (TDD) 的文章。 您的代碼需要進行一些測試,這一點再怎麼強調也不為過。 使用 Express.js 時,TDD 非常容易遵循。
在我們的測試中,我們將調用我們的 API 端點並檢查返回的內容是否是我們所期望的。
安裝所需的依賴項:
# install dependencies yarn add mocha chai nyc sinon-chai supertest coveralls --dev
這些庫中的每一個在我們的測試中都有自己的角色。
mocha | 測試賽跑者 |
chai | 用於斷言 |
nyc | 收集測試覆蓋率報告 |
sinon-chai | 擴展柴的斷言 |
supertest | 用於對我們的 API 端點進行 HTTP 調用 |
coveralls | 用於將測試覆蓋率上傳到 coveralls.io |
在項目的根目錄下創建一個新的test/
文件夾。 在此文件夾中創建兩個文件:
- 測試/setup.js
- 測試/index.test.js
Mocha 會自動找到test/
文件夾。
打開test/setup.js並粘貼以下代碼。 這只是一個幫助文件,可以幫助我們在測試文件中組織我們需要的所有導入。
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';
這就像一個設置文件,但用於我們的測試。 這樣我們就不必初始化每個測試文件中的所有內容。 因此,我們導入必要的包並導出我們初始化的內容——然後我們可以將其導入需要它們的文件中。
打開index.test.js並粘貼以下測試代碼。
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(); }); }); });
在這裡,我們發出請求以獲取基本端點,即/
並斷言res.
body
對像有一個message
鍵,其值為Environment variable is coming across.
如果您不熟悉describe
, it
模式,我建議您快速查看 Mocha 的“入門”文檔。
將 test 命令添加到package.json的scripts
部分。
"test": "nyc --reporter=html --reporter=text --reporter=lcov mocha -r @babel/register"
該腳本使用nyc
執行我們的測試並生成三種覆蓋率報告:HTML 報告,輸出到coverage/
文件夾; 輸出到終端的文本報告和輸出到.nyc_output/
文件夾的 lcov 報告。
現在運行yarn test
。 您應該在終端中看到一個文本報告,就像下圖中的那個。
請注意,生成了兩個額外的文件夾:
-
.nyc_output/
-
coverage/
查看.gitignore
內部,您會發現我們已經忽略了兩者。 我鼓勵您在瀏覽器中打開coverage/index.html
並查看每個文件的測試報告。
這是提交更改的好點。
- 我的倉庫中對應的分支是 04-first-test。
持續集成(CD)和徽章:Travis、Coveralls、Code Climate、AppVeyor
現在是配置持續集成和部署 (CI/CD) 工具的時候了。 我們將配置常用服務,例如travis-ci
、 coveralls
、 AppVeyor
和codeclimate
,並將徽章添加到我們的 README 文件中。
讓我們開始吧。
特拉維斯 CI
Travis CI 是一個工具,每次我們將提交推送到 GitHub(以及最近的 Bitbucket)以及每次創建拉取請求時,它都會自動運行我們的測試。 通過向我們展示我們的新代碼是否破壞了我們的任何測試,這在發出拉取請求時非常有用。
- 如果您沒有帳戶,請訪問 travis-ci.com 或 travis-ci.org 創建一個帳戶。 您必須使用您的 GitHub 帳戶註冊。
- 將鼠標懸停在您的個人資料圖片旁邊的下拉箭頭上,然後單擊
settings
。 - 在
Repositories
選項卡下,單擊Manage repositories on Github
以重定向到 Github。 - 在 GitHub 頁面上,向下滾動到
Repository access
並單擊Only select repositories
旁邊的複選框。 - 單擊
Select repositories
下拉菜單並找到express-api-template
存儲庫。 單擊它以將其添加到要添加到travis-ci
的存儲庫列表中。 - 單擊
Approve and install
並等待重定向回travis-ci
。 - 在 repo 頁面頂部,靠近 repo 名稱,單擊
build unknown
圖標。 從狀態圖像模式中,從格式下拉列表中選擇降價。 - 複製生成的代碼並將其粘貼到您的README.md文件中。
- 在項目頁面上,單擊
More options
>Settings
。 在Environment Variables
部分下,添加TEST_ENV_VARIABLE
環境變量。 輸入其值時,請務必將其放在雙引號內,例如"Environment variable is coming across."
- 在項目的根目錄下創建.travis.yml文件並粘貼以下代碼(我們將在代碼氣候部分設置
CC_TEST_REPORTER_ID
的值)。
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
首先,我們告訴 Travis 使用 Node.js 運行我們的測試,然後設置CC_TEST_REPORTER_ID
全局環境變量(我們將在代碼氣候部分中介紹)。 在matrix
部分,我們告訴 Travis 使用 Node.js v12 運行我們的測試。 我們還想緩存node_modules/
目錄,這樣就不必每次都重新生成它。
我們使用yarn
install 命令來安裝依賴項,它是yarn install
的簡寫。 before_script
和after_script
命令用於將覆蓋結果上傳到codeclimate
。 我們將很快配置codeclimate
。 在yarn test
成功運行後,我們還想運行yarn coverage
,它將我們的覆蓋率報告上傳到 coveralls.io。
連體工作服
Coveralls 上傳測試覆蓋率數據以便於可視化。 我們可以從覆蓋文件夾中查看本地機器上的測試覆蓋率,但 Coveralls 使其在本地機器之外可用。
- 訪問 coveralls.io 並使用您的 Github 帳戶登錄或註冊。
- 將鼠標懸停在屏幕左側以顯示導航菜單。 單擊
ADD REPOS
。 - 搜索
express-api-template
存儲庫並使用左側的切換按鈕打開覆蓋範圍。 如果找不到,請單擊右上角的SYNC REPOS
試。 請注意,除非您擁有 PRO 帳戶,否則您的回購必須是公開的。 - 點擊詳情進入repo詳情頁面。
- 在項目的根目錄下創建.coveralls.yml文件並輸入以下代碼。 要獲取
repo_token
,請單擊 repo 詳細信息。 您會在該頁面上輕鬆找到它。 您可以在瀏覽器中搜索repo_token
。
repo_token: get-this-from-repo-settings-on-coveralls.io
此令牌將您的覆蓋數據映射到 Coveralls 上的存儲庫。 現在,將coverage
命令添加到package.json文件的scripts
部分:
"coverage": "nyc report --reporter=text-lcov | coveralls"
此命令將.nyc_output
文件夾中的覆蓋率報告上傳到 coveralls.io。 打開您的 Internet 連接並運行:
yarn coverage
這應該將現有的覆蓋率報告上傳到工作服。 刷新工作服的回購頁面以查看完整報告。
在詳細信息頁面上,向下滾動以找到BADGE YOUR REPO
部分。 單擊EMBED
下拉菜單並複制降價代碼並將其粘貼到您的README文件中。
代碼氣候
Code Climate 是一個幫助我們衡量代碼質量的工具。 它通過根據一些定義的模式檢查我們的代碼來向我們展示維護指標。 它檢測諸如不必要的重複和深度嵌套的 for 循環之類的事情。 它還像 coveralls.io 一樣收集測試覆蓋率數據。
- 訪問 codeclimate.com 並單擊“使用 GitHub 註冊”。 如果您已經有帳戶,請登錄。
- 進入儀表板後,單擊
Add a repository
。 - 從列表中找到
express-api-template
repo 並單擊Add Repo
。 - 等待構建完成並重定向到 repo 儀表板。
- 在
Codebase Summary
下,單擊Test Coverage
。 在Test coverage
菜單下,複製TEST REPORTER ID
並將其粘貼到您的.travis.yml作為CC_TEST_REPORTER_ID
的值。 - 仍然在同一頁面上,在左側導航中,在
EXTRAS
下,單擊 Badges。 以 markdown 格式複制maintainability
和test coverage
標記,並將它們粘貼到您的README.md文件中。
需要注意的是,有兩種配置可維護性檢查的方法。 有適用於每個 repo 的默認設置,但如果您願意,您可以在項目的根目錄中提供一個.codeclimate.yml文件。 我將使用默認設置,您可以在 repo 設置頁面的Maintainability
選項卡下找到該設置。 我鼓勵你至少看看。 如果您仍想配置自己的設置,本指南將為您提供所需的所有信息。
應用程序
AppVeyor 和 Travis CI 都是自動化測試運行器。 主要區別在於 travis-ci 在 Linux 環境中運行測試,而 AppVeyor 在 Windows 環境中運行測試。 包含此部分以展示如何開始使用 AppVeyor。
- 訪問 AppVeyor 並登錄或註冊。
- 在下一頁上,單擊
NEW PROJECT
。 - 從 repo 列表中,找到
express-api-template
repo。 將鼠標懸停在它上面並單擊ADD
。 - 單擊
Settings
選項卡。 單擊左側導航中的Environment
。 添加TEST_ENV_VARIABLE
及其值。 點擊頁面底部的“保存”。 - 在項目的根目錄下創建appveyor.yml文件並粘貼以下代碼。
environment: matrix: - nodejs_version: "12" install: - yarn test_script: - yarn test build: off
此代碼指示 AppVeyor 使用 Node.js v12 運行我們的測試。 然後我們使用yarn
命令安裝我們的項目依賴項。 test_script
指定運行我們測試的命令。 最後一行告訴 AppVeyor 不要創建構建文件夾。
單擊Settings
選項卡。 在左側導航中,單擊徽章。 複製降價代碼並將其粘貼到您的README.md文件中。
提交您的代碼並推送到 GitHub。 如果您按照指示完成了所有測試,則所有測試都應該通過,您應該會看到閃亮的新徽章,如下所示。 再次檢查您是否已在 Travis 和 AppVeyor 上設置環境變量。
現在是提交更改的好時機。
- 我的倉庫中對應的分支是 05-ci。
添加控制器
目前,我們正在src/routes/index.js中處理對根 URL /v1
的GET
請求。 這按預期工作,沒有任何問題。 但是,隨著應用程序的增長,您希望保持整潔。 您希望將關注點分開——您希望在處理請求的代碼和生成將發送回客戶端的響應的代碼之間明確分離。 為此,我們編寫了controllers
。 控制器只是處理來自特定 URL 的請求的簡單函數。
首先,在src/
文件夾中創建一個controllers/
文件夾。 內部controllers
創建兩個文件: index.js和home.js 。 我們將從index.js中導出我們的函數。 您可以將home.js命名為任何您想要的名稱,但通常您希望根據控制器的控制來命名控制器。 例如,您可能有一個文件usersController.js來保存與您的應用程序中的用戶相關的每個函數。
打開src/controllers/home.js並輸入以下代碼:
import { testEnvironmentVariable } from '../settings'; export const indexPage = (req, res) => res.status(200).json({ message: testEnvironmentVariable });
你會注意到我們只移動了處理/
路由請求的函數。
打開src/controllers/index.js並輸入以下代碼。
// export everything from home.js export * from './home';
我們從home.js文件中導出所有內容。 這使我們可以縮短導入語句以import { indexPage } from '../controllers';
打開src/routes/index.js並將那裡的代碼替換為以下代碼:
import express from 'express'; import { indexPage } from '../controllers'; const indexRouter = express.Router(); indexRouter.get('/', indexPage); export default indexRouter;
這裡唯一的變化是我們提供了一個函數來處理對/
路由的請求。
您剛剛成功編寫了您的第一個控制器。 從這裡開始,您可以根據需要添加更多文件和功能。
繼續通過添加更多路由和控制器來使用該應用程序。 您可以為 about 頁面添加路由和控制器。 不過,請記住更新您的測試。
運行yarn test
以確認我們沒有破壞任何東西。 你的測試通過了嗎? 這很酷。
這是提交更改的好點。
- 我的倉庫中對應的分支是 06-controllers。
連接PostgreSQL
數據庫並編寫模型
我們的控制器當前返回硬編碼的文本消息。 在現實世界的應用程序中,我們經常需要從數據庫中存儲和檢索信息。 在本節中,我們將把我們的應用程序連接到 PostgreSQL 數據庫。
我們將使用數據庫實現簡單文本消息的存儲和檢索。 我們有兩種設置數據庫的選擇:我們可以從雲服務器提供一個,或者我們可以在本地設置自己的。
我建議您從雲服務器配置數據庫。 ElephantSQL 有一個免費計劃,提供 20MB 的免費存儲空間,對於本教程來說已經足夠了。 訪問該站點並單擊“ Get a managed database today
。 創建一個帳戶(如果您沒有帳戶)並按照說明創建一個免費計劃。 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.
打開query.js並粘貼以下代碼:
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';
在這個文件中,我們定義了三個 SQL 查詢字符串。 第一個查詢刪除並重新創建messages
表。 第二個查詢將兩行插入到messages
表中。 隨意在這裡添加更多項目。 最後一個查詢刪除/刪除messages
表。
打開queryFunctions.js並粘貼以下代碼:
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 ]);
在這裡,我們創建函數來執行我們之前定義的查詢。 請注意, executeQueryArray
函數執行一組查詢並等待每個查詢在循環內完成。 (雖然不要在生產代碼中做這樣的事情)。 然後,我們只有在執行完列表中的最後一個查詢後才解決 promise。 使用數組的原因是這樣的查詢數量會隨著我們數據庫中表數量的增長而增長。
打開runQuery.js並粘貼以下代碼:
import { createTables, insertIntoTables } from './queryFunctions'; (async () => { await createTables(); await insertIntoTables(); })();
這是我們執行函數以創建表並將消息插入表中的地方。 讓我們在package.json的scripts
部分添加一個命令來執行這個文件。
"runQuery": "babel-node ./src/utils/runQuery"
現在運行:
yarn runQuery
如果您檢查您的數據庫,您將看到messages
表已創建並且消息已插入到表中。
如果您使用的是 ElephantSQL,請在數據庫詳細信息頁面上,單擊左側導航菜單中的BROWSER
。 選擇messages
表並單擊Execute
。 您應該會看到來自queries.js文件的消息。
讓我們創建一個控制器和路由來顯示來自我們數據庫的消息。
創建一個新的控制器文件src/controllers/messages.js並粘貼以下代碼:
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 }); } };
我們導入Model
類並創建該模型的新實例。 這表示我們數據庫中的messages
表。 然後我們使用模型的select
方法來查詢我們的數據庫。 我們獲得的數據( name
和message
)在響應中以 JSON 格式發送。
我們將messagesPage
控制器定義為async
函數。 由於node-postgres
查詢返回一個承諾,我們await
該查詢的結果。 如果我們在查詢過程中遇到錯誤,我們會捕獲它並將堆棧顯示給用戶。 您應該決定如何選擇處理錯誤。
將獲取消息端點添加到src/routes/index.js並更新導入行。
# update the import line import { indexPage, messagesPage } from '../controllers'; # add the get messages endpoint indexRouter.get('/messages', messagesPage)
訪問 https://localhost:3000/v1/messages,您應該會看到如下所示的消息。
現在,讓我們更新我們的測試文件。 在進行 TDD 時,您通常在實現使測試通過的代碼之前編寫測試。 我在這裡採取相反的方法,因為我們仍在努力設置數據庫。
在test/
文件夾中創建一個新文件hooks.js並輸入以下代碼:
import { dropTables, createTables, insertIntoTables, } from '../src/utils/queryFunctions'; before(async () => { await createTables(); await insertIntoTables(); }); after(async () => { await dropTables(); });
當我們的測試開始時,Mocha 會找到這個文件並在運行任何測試文件之前執行它。 它執行before
鉤子來創建數據庫並將一些項目插入其中。 然後測試文件在此之後運行。 測試完成後,Mocha 運行我們刪除數據庫的after
鉤子。 這確保了每次我們運行測試時,我們都會在數據庫中使用乾淨的新記錄。
創建一個新的測試文件test/messages.test.js並添加以下代碼:
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(); }); }); });
我們斷言調用/messages
的結果是一個數組。 對於每個消息對象,我們斷言它具有name
和message
屬性。
本節的最後一步是更新 CI 文件。
將以下部分添加到.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
這指示 Travis 在運行我們的測試之前啟動 PostgreSQL 10 數據庫。
添加創建數據庫的命令作為before_script
部分的第一個條目:
# add this as the first line in the before_script section - psql -c 'create database testdb;' -U postgres
在 Travis 上創建CONNECTION_STRING
環境變量,並使用以下值:
CONNECTION_STRING="postgresql://postgres:postgres@localhost:5432/testdb"
將以下部分添加到.appveyor.yml文件中:
before_test: - SET PGUSER=postgres - SET PGPASSWORD=Password12! - PATH=C:\Program Files\PostgreSQL\10\bin\;%PATH% - createdb testdb services: - postgresql101
將連接字符串環境變量添加到 appveyor。 使用以下行:
CONNECTION_STRING=postgresql://postgres:Password12!@localhost:5432/testdb
現在提交您的更改並推送到 GitHub。 你的測試應該通過 Travis CI 和 AppVeyor。
- 我的倉庫中對應的分支是 07-connect-postgres。
注意:我希望你一切正常,但如果你因為某種原因遇到麻煩,你可以隨時在 repo 中檢查我的代碼!
現在,讓我們看看如何將消息添加到我們的數據庫中。 對於這一步,我們需要一種將POST
請求發送到我們的 URL 的方法。 我將使用 Postman 發送POST
請求。
讓我們走 TDD 路線並更新我們的測試以反映我們期望實現的目標。
打開test/message.test.js並添加以下測試用例:
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(); }); });
此測試向/v1/messages
端點發出 POST 請求,我們希望返回一個數組。 我們還檢查數組上的id
、 name
和message
屬性。
運行您的測試以查看此案例是否失敗。 現在讓我們修復它。
要發送 post 請求,我們使用服務器的 post 方法。 我們還發送要插入的名稱和消息。 我們希望響應是一個數組,帶有一個屬性id
和構成查詢的其他信息。 id
是記錄已插入數據庫的證明。
打開src/models/model.js並添加insert
方法:
async insertWithReturn(columns, values) { const query = ` INSERT INTO ${this.table}(${columns}) VALUES (${values}) RETURNING id, ${columns} `; return this.pool.query(query); }
這是允許我們將消息插入數據庫的方法。 插入項目後,它返回id
、 name
和message
。
打開src/controllers/messages.js並添加以下控制器:
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 }); } };
我們解構請求正文以獲取名稱和消息。 然後我們使用這些值形成一個 SQL 查詢字符串,然後我們使用模型的insertWithReturn
方法執行該字符串。
將以下POST
端點添加到/src/routes/index.js並更新您的導入行。
import { indexPage, messagesPage, addMessage } from '../controllers'; indexRouter.post('/messages', addMessage);
運行您的測試以查看它們是否通過。
打開 Postman 並向messages
端點發送POST
請求。 如果您剛剛運行測試,請記住運行yarn query
來重新創建messages
表。
yarn query
提交您的更改並推送到 GitHub。 您的測試應該通過 Travis 和 AppVeyor。 你的測試覆蓋率會下降幾個點,但這沒關係。
- 我的 repo 上的相應分支是 08-post-to-db。
中間件
如果不討論中間件,我們對 Express 的討論是不完整的。 Express 文檔將中間件描述為:
“[...] 可以訪問請求對象 (req
)、響應對象 (res
) 和應用程序請求-響應週期中的下一個中間件函數的函數。 下一個中間件函數通常由一個名為next
的變量表示。”
中間件可以執行任意數量的功能,例如身份驗證、修改請求正文等。 請參閱有關使用中間件的 Express 文檔。
我們將編寫一個簡單的中間件來修改請求正文。 我們的中間件會在傳入的消息保存到數據庫之前附加單詞SAYS:
在我們開始之前,讓我們修改我們的測試以反映我們想要實現的目標。
打開test/messages.test.js並修改posts message
測試用例中的最後一個期望行:
it('posts messages', done => { ... expect(m).to.have.property('message', `SAYS: ${data.message}`); # update this line ... });
我們斷言SAYS:
字符串已附加到消息中。 運行您的測試以確保此測試用例失敗。
現在,讓我們編寫代碼以使測試通過。
在src/
文件夾中創建一個新的middleware/
文件夾。 在此文件夾中創建兩個文件:
- 中間件.js
- index.js
在middleware.js中輸入以下代碼:
export const modifyMessage = (req, res, next) => { req.body.message = `SAYS: ${req.body.message}`; next(); };
在這裡,我們將字符串SAYS:
附加到請求正文中的消息中。 之後,我們必須調用next()
函數將執行傳遞給請求-響應鏈中的下一個函數。 每個中間件都必須調用next
函數以在請求-響應週期中將執行傳遞給下一個中間件。
在index.js中輸入以下代碼:
# export everything from the middleware file export * from './middleware';
這會導出我們在/middleware.js文件中的中間件。 目前,我們只有modifyMessage
中間件。
打開src/routes/index.js並將中間件添加到發布消息請求-響應鏈中。
import { modifyMessage } from '../middleware'; indexRouter.post('/messages', modifyMessage, addMessage);
我們可以看到modifyMessage
函數位於addMessage
函數之前。 我們通過在modifyMessage
中間件中調用next
來調用addMessage
函數。 作為一個實驗,註釋掉modifyMessage
中間的next()
行並觀察請求掛起。
打開 Postman 並創建一條新消息。 您應該看到附加的字符串。
這是提交更改的好點。
- 我的倉庫中對應的分支是 09-middleware。
錯誤處理和異步中間件
在任何應用程序中,錯誤都是不可避免的。 開發人員面前的任務是如何盡可能優雅地處理錯誤。
在快遞:
“錯誤處理是指 Express 如何捕獲和處理同步和異步發生的錯誤。
如果我們只編寫同步函數,我們可能不必擔心錯誤處理,因為 Express 已經在處理這些問題方面做得很好。 根據文檔:
“路由處理程序和中間件內的同步代碼中發生的錯誤不需要額外的工作。”
但是一旦我們開始編寫異步路由器處理程序和中間件,我們就必須進行一些錯誤處理。
我們的modifyMessage
中間件是一個同步函數。 如果該函數發生錯誤,Express 會處理得很好。 讓我們看看我們如何處理異步中間件中的錯誤。
假設,在創建消息之前,我們想使用這個 URL https://picsum.photos/id/0/info
從 Lorem Picsum API 獲取一張圖片。 這是一個可能成功或失敗的異步操作,這為我們提供了一個處理案例。
首先安裝 Axios。
# install axios yarn add axios
打開src/middleware/middleware.js並添加以下函數:
export const performAsyncAction = async (req, res, next) => { try { await axios.get('https://picsum.photos/id/0/info'); next(); } catch (err) { next(err); } };
在這個async
函數中,我們await
對 API 的調用(我們實際上並不需要返回的數據),然後調用請求鏈中的next
函數。 如果請求失敗,我們會捕獲錯誤並將其傳遞給next
。 一旦 Express 看到這個錯誤,它就會跳過鏈中的所有其他中間件。 如果我們沒有調用next(err)
,請求就會掛起。 如果我們只調用next()
而沒有err
,則請求將繼續進行,就好像什麼都沒發生一樣,並且不會捕獲錯誤。
導入此函數並將其添加到 post messages 路由的中間件鏈中:
import { modifyMessage, performAsyncAction } from '../middleware'; indexRouter.post('/messages', modifyMessage, performAsyncAction, addMessage);
打開src/app.js並在export default app
行之前添加以下代碼。
app.use((err, req, res, next) => { res.status(400).json({ error: err.stack }); }); export default app;
這是我們的錯誤處理程序。 根據 Express 錯誤處理文檔:
“[...] 錯誤處理函數有四個參數,而不是三個: (err, req, res, next)
。”
請注意,此錯誤處理程序必須在每次app.use()
調用之後出現。 一旦遇到錯誤,我們會返回帶有狀態碼400
的堆棧跟踪。 你可以對錯誤做任何你喜歡的事情。 您可能想要記錄它或將其發送到某個地方。
這是提交更改的好地方。
- 我的倉庫中對應的分支是 10-async-middleware。
部署到 Heroku
- 要開始使用,請訪問 https://www.heroku.com/ 並登錄或註冊。
- 從這裡下載並安裝 Heroku CLI。
- 在項目文件夾中打開終端以運行命令。
# login to heroku on command line heroku login
這將打開一個瀏覽器窗口並要求您登錄您的 Heroku 帳戶。
登錄以授予終端訪問 Heroku 帳戶的權限,然後運行以下命令創建一個新的 Heroku 應用程序:
#app name is up to you heroku create app-name
這將在 Heroku 上創建應用程序並返回兩個 URL。
# app production url and git url https://app-name.herokuapp.com/ | https://git.heroku.com/app-name.git
複製右側的 URL 並運行以下命令。 請注意,此步驟是可選的,因為您可能會發現 Heroku 已經添加了遠程 URL。
# add heroku remote url git remote add heroku https://git.heroku.com/my-shiny-new-app.git
打開一個側面終端並運行以下命令。 如圖所示,這將實時顯示應用程序日誌。
# see process logs heroku logs --tail
運行以下三個命令來設置所需的環境變量:
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
請記住,在我們的腳本中,我們設置了:
"prestart": "babel ./src --out-dir build", "start": "node ./build/bin/www",
要啟動應用程序,需要在prestart
啟動步驟中使用 babel 將其編譯為 ES5,因為 babel 僅存在於我們的開發依賴項中。 我們必須將NPM_CONFIG_PRODUCTION
設置為false
才能安裝它們。
要確認一切設置正確,請運行以下命令。 您還可以訪問應用頁面上的settings
選項卡,然後單擊Reveal Config Vars
。
# check configuration variables heroku config
現在運行git push heroku
。
要打開應用程序,請運行:
# open /v1 route heroku open /v1 # open /v1/messages route heroku open /v1/messages
如果像我一樣,您在開發和生產中使用相同的 PostgresSQL 數據庫,您可能會發現每次運行測試時,數據庫都會被刪除。 要重新創建它,您可以運行以下任一命令:
# run script locally yarn runQuery # run script with heroku heroku run yarn runQuery
使用 Travis 進行持續部署 (CD)
現在讓我們添加持續部署 (CD) 以完成 CI/CD 流程。 每次成功的測試運行後,我們將從 Travis 進行部署。
第一步是安裝 Travis CI。 (您可以在此處找到安裝說明。)成功安裝 Travis CI 後,運行以下命令登錄。 (請注意,這應該在您的項目存儲庫中完成。)
# login to travis travis login --pro # use this if you're using two factor authentication travis login --pro --github-token enter-github-token-here
如果您的項目託管在 travis-ci.org 上,請刪除--pro
標誌。 要獲取 GitHub 令牌,請訪問您帳戶的開發人員設置頁面並生成一個。 這僅適用於您的帳戶受 2FA 保護的情況。
打開你的.travis.yml並添加一個部署部分:
deploy: provider: heroku app: master: app-name
在這裡,我們指定要部署到 Heroku。 app 子部分指定我們要將 repo 的master
分支部署到 Heroku 上的app-name
應用程序。 可以將不同的分支部署到不同的應用程序。 您可以在此處閱讀有關可用選項的更多信息。
運行以下命令來加密您的 Heroku API 密鑰並將其添加到部署部分:
# encrypt heroku API key and add to .travis.yml travis encrypt $(heroku auth:token) --add deploy.api_key --pro
這會將以下子部分添加到部署部分。
api_key: secure: very-long-encrypted-api-key-string
現在提交您的更改並推送到 GitHub,同時監控您的日誌。 Travis 測試完成後,您將看到構建觸發。 這樣,如果我們的測試失敗,則永遠不會部署更改。 同樣,如果構建失敗,整個測試運行也會失敗。 這樣就完成了 CI/CD 流程。
- 我的倉庫中對應的分支是 11-cd。
結論
如果你已經做到了這一步,我會說,“豎起大拇指!” 在本教程中,我們成功建立了一個新的 Express 項目。 我們繼續配置開發依賴項以及持續集成 (CI)。 然後,我們編寫了異步函數來處理對 API 端點的請求——通過測試完成。 然後我們簡要介紹了錯誤處理。 最後,我們將項目部署到 Heroku 並配置了持續部署。
您現在有了下一個後端項目的模板。 我們所做的只是讓您入門,但您應該繼續學習繼續前進。 請務必同時查看 Express.js 文檔。 如果您更願意使用MongoDB
而不是PostgreSQL
,我在這裡有一個模板可以做到這一點。 您可以檢查它的設置。 它只有幾個不同點。
資源
- “使用 MongoDB 創建 Express API 後端”,Orji Chidi Matthew,GitHub
- “連接中間件的簡短指南”,Stephen Sugden
- “Express API 模板”,GitHub
- “AppVeyor 與 Travis CI,” StackShare
- “Heroku CLI”,Heroku 開發中心
- “Heroku 部署”,Travis CI
- “使用中間件”,Express.js
- “錯誤處理”,Express.js
- “入門”,摩卡
nyc
(GitHub)- 大象SQL
- 郵差
- 表示
- 特拉維斯 CI
- 代碼氣候
- PostgreSQL
- pgAdmin