如何使用 PostgreSQL 设置 Express API 后端项目

已发表: 2022-03-10
快速总结↬在本文中,我们将使用 ES6 语法中的 Express 从头开始​​创建一组 API 端点,并介绍一些开发最佳实践。 在部署到 Heroku 之前,在使用持续集成和测试驱动开发创建一个小项目时,了解所有部分如何协同工作。

我们将采用测试驱动开发 (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 在当前目录中创建项目。

我们现在将执行一些房屋清洁操作。

  1. 删除文件index/users.js
  2. 删除文件夹public/views/
  3. 将文件bin/www重命名为bin/www.js
  4. 使用命令yarn remove jade jade
  5. 创建一个名为src/的新文件夹并在其中移动以下内容: 1. app.js文件 2. bin/文件夹 3. routes/文件夹在里面。
  6. 打开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(在撰写本文时)不支持我们在代码中使用的某些语法。

我们现在将在下一节中解决这个问题。

配置开发依赖: babelnodemoneslintprettier

是时候设置我们在项目的这个阶段需要的大部分脚本了。

使用以下命令安装所需的库。 您可以复制所有内容并将其粘贴到终端中。 注释行将被跳过。

 # 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"
  1. prestart脚本构建src/文件夹的内容并将其放入build/文件夹中。 当您发出yarn start命令时,此脚本在start脚本之前首先运行。
  2. start脚本现在提供build/文件夹的内容,而不是我们之前提供的src/文件夹。 这是您在生产中提供文件时将使用的脚本。 事实上,像 Heroku 这样的服务会在你部署时自动运行这个脚本。
  3. yarn startdev用于在开发过程中启动服务器。 从现在开始,我们将在开发应用程序时使用此脚本。 请注意,我们现在使用babel-node来运行应用程序而不是常规node--exec标志强制babel-node服务于src/文件夹。 对于start脚本,我们使用node ,因为build/文件夹中的文件已编译为 ES5。

运行yarn startdev并访问 https://localhost:3000/v1。 您的服务器应该重新启动并运行。

本节的最后一步是配置ESLintprettier 。 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.jsconfig.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.jsonscripts部分。

 "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-cicoverallsAppVeyorcodeclimate ,并将徽章添加到我们的 README 文件中。

让我们开始吧。

特拉维斯 CI

Travis CI 是一个工具,每次我们将提交推送到 GitHub(以及最近的 Bitbucket)以及每次创建拉取请求时,它都会自动运行我们的测试。 通过向我们展示我们的新代码是否破坏了我们的任何测试,这在发出拉取请求时非常有用。

  1. 如果您没有帐户,请访问 travis-ci.com 或 travis-ci.org 创建一个帐户。 您必须使用您的 GitHub 帐户注册。
  2. 将鼠标悬停在您的个人资料图片旁边的下拉箭头上,然后单击settings
  3. Repositories选项卡下,单击Manage repositories on Github以重定向到 Github。
  4. 在 GitHub 页面上,向下滚动到Repository access并单击Only select repositories旁边的复选框。
  5. 单击Select repositories下拉菜单并找到express-api-template存储库。 单击它以将其添加到要添加到travis-ci的存储库列表中。
  6. 单击Approve and install并等待重定向回travis-ci
  7. 在 repo 页面顶部,靠近 repo 名称,单击build unknown图标。 从状态图像模式中,从格式下拉列表中选择降价。
  8. 复制生成的代码并将其粘贴到您的README.md文件中。
  9. 在项目页面上,单击More options > Settings 。 在Environment Variables部分下,添加TEST_ENV_VARIABLE环境变量。 输入其值时,请务必将其放在双引号内,例如"Environment variable is coming across."
  10. 在项目的根目录下创建.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_scriptafter_script命令用于将覆盖结果上传到codeclimate 。 我们将很快配置codeclimate 。 在yarn test成功运行后,我们还想运行yarn coverage ,它将我们的覆盖率报告上传到 coveralls.io。

连体工作服

Coveralls 上传测试覆盖率数据以便于可视化。 我们可以从覆盖文件夹中查看本地机器上的测试覆盖率,但 Coveralls 使其在本地机器之外可用。

  1. 访问 coveralls.io 并使用您的 Github 帐户登录或注册。
  2. 将鼠标悬停在屏幕左侧以显示导航菜单。 单击ADD REPOS
  3. 搜索express-api-template存储库并使用左侧的切换按钮打开覆盖范围。 如果找不到,请单击右上角的SYNC REPOS试。 请注意,除非您拥有 PRO 帐户,否则您的回购必须是公开的。
  4. 点击详情进入repo详情页面。
  5. 在项目的根目录下创建.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 一样收集测试覆盖率数据。

  1. 访问 codeclimate.com 并单击“使用 GitHub 注册”。 如果您已经有帐户,请登录。
  2. 进入仪表板后,单击Add a repository
  3. 从列表中找到express-api-template repo 并单击Add Repo
  4. 等待构建完成并重定向到 repo 仪表板。
  5. Codebase Summary下,单击Test Coverage 。 在Test coverage菜单下,复制TEST REPORTER ID并将其粘贴到您的.travis.yml作为CC_TEST_REPORTER_ID的值。
  6. 仍然在同一页面上,在左侧导航中,在EXTRAS下,单击 Badges。 以 markdown 格式复制maintainabilitytest 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 上设置环境变量。

回购 CI/CD 徽章。 (大预览)

现在是提交更改的好时机。

  • 我的仓库中对应的分支是 05-ci。

添加控制器

目前,我们正在src/routes/index.js中处理对根 URL /v1GET请求。 这按预期工作,没有任何问题。 但是,随着应用程序的增长,您希望保持整洁。 您希望将关注点分开——您希望在处理请求的代码和生成将发送回客户端的响应的代码之间明确分离。 为此,我们编写了controllers 。 控制器只是处理来自特定 URL 的请求的简单函数。

首先,在src/文件夹中创建一个controllers/文件夹。 内部controllers创建两个文件: index.jshome.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 。 Create an account (if you don't have one) and follow the instructions to create a free plan. Take note of the URL on the database details page. We'll be needing it soon.

ElephantSQL turtle plan details page (Large preview)

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

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

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

Let's connect node-postgres to our application.

 # install node-postgres yarn add pg

Open settings.js and add the line below:

 export const connectionString = process.env.CONNECTION_STRING;

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

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

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

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

  • pool.js
  • model.js

Open pools.js and paste the following code:

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

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

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

Open model.js and paste the following code:

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

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

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

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

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

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

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

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

打开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.jsonscripts部分添加一个命令来执行这个文件。

 "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方法来查询我们的数据库。 我们获得的数据( namemessage )在响应中以 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的结果是一个数组。 对于每个消息对象,我们断言它具有namemessage属性。

本节的最后一步是更新 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 请求,我们希望返回一个数组。 我们还检查数组上的idnamemessage属性。

运行您的测试以查看此案例是否失败。 现在让我们修复它。

要发送 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); }

这是允许我们将消息插入数据库的方法。 插入项目后,它返回idnamemessage

打开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 
对消息端点的 POST 请求。 (大预览)
GET 请求显示新添加的消息。 (大预览)

提交您的更改并推送到 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

  1. 要开始使用,请访问 https://www.heroku.com/ 并登录或注册。
  2. 从这里下载并安装 Heroku CLI。
  3. 在项目文件夹中打开终端以运行命令。
 # 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 日志。 (大预览)

运行以下三个命令来设置所需的环境变量:

 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