Как настроить серверный проект Express API с помощью PostgreSQL
Опубликовано: 2022-03-10Мы воспользуемся подходом разработки через тестирование (TDD) и настроим задание непрерывной интеграции (CI) для автоматического запуска наших тестов в Travis CI и AppVeyor с отчетами о качестве кода и покрытии. Мы узнаем о контроллерах, моделях (с PostgreSQL), обработке ошибок и асинхронном промежуточном программном обеспечении Express. Наконец, мы завершим конвейер CI/CD, настроив автоматическое развертывание на Heroku.
Звучит много, но это руководство предназначено для новичков, которые готовы попробовать свои силы в бэкенд-проекте с определенным уровнем сложности, и которые все еще могут не понимать, как все части сочетаются друг с другом в реальном проекте. .
Он надежный, но не перегруженный и разбит на разделы, которые вы можете выполнить за разумное время.
Начиная
Первым шагом является создание нового каталога для проекта и запуск нового проекта узла. Для продолжения этого руководства требуется узел. Если он у вас не установлен, перейдите на официальный сайт, загрузите и установите его, прежде чем продолжить.
Я буду использовать пряжу в качестве менеджера пакетов для этого проекта. Инструкции по установке для конкретной операционной системы приведены здесь. Не стесняйтесь использовать 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 с множеством плагинов, облегчающих вашу жизнь, и она доступна для всех основных платформ. Вы можете скачать его с официального сайта.
Создайте в папке проекта следующие файлы:
- README.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 — это платформа Node.js для создания веб-приложений. Согласно официальному сайту, это
Быстрый, бескомпромиссный, минималистичный веб-фреймворк для Node.js.
Существуют и другие отличные фреймворки для веб-приложений для Node.js, но Express очень популярен: на момент написания этой статьи он получил более 47 тысяч звезд на GitHub.
В этой статье мы не будем подробно обсуждать все части, из которых состоит Express. Для этого обсуждения я рекомендую вам проверить серию Джейми. Первая часть здесь, а вторая часть здесь.
Установите Express и запустите новый проект Express. Можно вручную настроить сервер Express с нуля, но чтобы упростить себе жизнь, мы будем использовать экспресс-генератор для настройки скелета приложения.
# 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 .
- Удалите
jade
с помощью командыyarn remove jade
. - Создайте новую папку с именем
src/
и переместите в нее следующее: 1. файл app.js 2. папкуbin/
3. папку routeroutes/
внутрь. - Откройте 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 обрабатывать каждый запрос, поступающий в /v1
, с помощью indexRouter
.
Замените содержимое route/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
.
Замените содержимое route/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;
Это тот же код, который мы видели выше, но с оператором импорта и функцией стрелки в обработчике /
route.
Замените содержимое 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
является необязательной, поскольку мы указываем узел, когда хотим выполнить этот файл. Но убедитесь, что он находится в строке 1 файла src/bin/www.js или полностью удалите его.
Давайте посмотрим на функцию обработки ошибок. Скопируйте и вставьте этот блок кода после строки, в которой создается сервер.
/** * 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
, And 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, с дополнительным преимуществом компиляции с пресетами и плагинами 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
указывает файлам не следить за изменениями.
Теперь обновите раздел scripts
в файле package.json , чтобы он выглядел следующим образом:
# 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 создает содержимое папкиsrc/
и помещает его в папкуbuild/
. Когда вы вводите командуyarn start
, этот сценарий запускается первым перед сценариемstart
. -
start
script теперь обслуживает содержимое папки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
Теперь создайте файл .eslintrc.json в root
проекта и добавьте следующий код:
{ "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
. Он запускает команду lint
с добавленным флагом --fix
. Этот флаг указывает ESLint
автоматически исправлять распространенные проблемы с линтингом. Таким образом, я в основном запускаю команду yarn pretty
, не беспокоясь о команде lint
.
Запустите yarn pretty
. Вы должны увидеть, что у нас всего два предупреждения о наличии alert
в файле bin/www.js .
Вот как выглядит структура нашего проекта на данный момент.
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
.
Теперь создайте файл settings.js в папке src/
и добавьте следующий код:
import dotenv from 'dotenv'; dotenv.config(); export const testEnvironmentVariable = process.env.TEST_ENV_VARIABLE;
Мы импортируем пакет dotenv
и вызываем его метод конфигурации. Затем мы экспортируем 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;
Единственное изменение, которое мы здесь сделали, заключается в том, что мы импортируем testEnvironmentVariable
из нашего файла settings
и используем ее в качестве ответного сообщения для запроса из /
route.
Посетите https://localhost:3000/v1, и вы должны увидеть сообщение, как показано ниже.
{ "message": "Environment variable is coming across." }
Вот и все. С этого момента мы можем добавить столько переменных среды, сколько захотим, и мы можем экспортировать их из нашего файла settings.js .
Это хороший момент для фиксации вашего кода. Не забывайте претифицировать и анализировать код.
- Соответствующая ветка в моем репо — 03-env-variables.
Пишем наш первый тест
Пришло время включить тестирование в наше приложение. Одна из вещей, которая дает разработчику уверенность в своем коде, — это тесты. Я уверен, что вы видели бесчисленное количество статей в Интернете, проповедующих разработку через тестирование (TDD). Нельзя не подчеркнуть, что ваш код нуждается в некотором тестировании. TDD очень легко понять, когда вы работаете с Express.js.
В наших тестах мы будем совершать вызовы к нашим конечным точкам API и проверять, соответствует ли результат тому, что мы ожидаем.
Установите необходимые зависимости:
# install dependencies yarn add mocha chai nyc sinon-chai supertest coveralls --dev
Каждая из этих библиотек играет свою роль в наших тестах.
mocha | бегун-испытатель |
chai | используется для утверждений |
nyc | собрать отчет о тестовом покрытии |
sinon-chai | расширяет утверждения чая |
supertest | используется для выполнения HTTP-вызовов к нашим конечным точкам API |
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 «Getting Started».
Добавьте тестовую команду в раздел scripts
package.json .
"test": "nyc --reporter=html --reporter=text --reporter=lcov mocha -r @babel/register"
Этот сценарий выполняет наш тест с nyc
и создает три вида отчета о покрытии: отчет в формате HTML, выводимый в папку coverage/
; текстовый отчет выводится на терминал, а отчет lcov выводится в папку .nyc_output/
.
Теперь запустите yarn test
. Вы должны увидеть текстовый отчет в своем терминале, как на фотографии ниже.

Обратите внимание, что создаются две дополнительные папки:
-
.nyc_output/
-
coverage/
Загляните внутрь .gitignore
, и вы увидите, что мы уже игнорируем оба. Я рекомендую вам открыть в браузере файл coverage/index.html
и просмотреть отчет о тестировании для каждого файла.
Это хороший момент, чтобы зафиксировать ваши изменения.
- Соответствующая ветка в моем репо — 04-first-test.
Непрерывная интеграция (CD) и значки: Travis, комбинезоны, Code Climate, AppVeyor
Пришло время настроить инструменты непрерывной интеграции и развертывания (CI/CD). Мы настроим общие сервисы, такие как travis-ci
, coveralls
, AppVeyor
и codeclimate
, и добавим значки в наш файл README.
Давайте начнем.
Трэвис Си
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
. - В верхней части страницы репозитория рядом с именем репозитория щелкните значок
build unknown
. В модальном окне «Изображение состояния» выберите уценку из раскрывающегося списка форматов. - Скопируйте полученный код и вставьте его в файл README.md .
- На странице проекта нажмите
More options
>Settings
. В разделе «Environment Variables
» добавьте переменную envTEST_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
Сначала мы говорим Трэвису запустить наш тест с помощью Node.js, затем устанавливаем глобальную переменную среды CC_TEST_REPORTER_ID
(мы вернемся к этому в разделе «Климат кода»). В разделе matrix
мы говорим Трэвису запустить наши тесты с Node.js v12. Мы также хотим кэшировать node_modules/
, чтобы его не нужно было каждый раз регенерировать.
Мы устанавливаем наши зависимости с помощью команды yarn
, которая является сокращением для 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. - Нажмите детали, чтобы перейти на страницу сведений о репо.
- Создайте файл .coveralls.yml в корне вашего проекта и введите приведенный ниже код. Чтобы получить
repo_token
, нажмите на детали репо. Вы легко найдете его на этой странице. Вы можете просто выполнить поискrepo_token
в браузере.
repo_token: get-this-from-repo-settings-on-coveralls.io
Этот токен сопоставляет ваши данные покрытия с репозиторием на комбинезоне. Теперь добавьте команду coverage
в раздел scripts
вашего файла package.json :
"coverage": "nyc report --reporter=text-lcov | coveralls"
Эта команда загружает отчет о покрытии из папки .nyc_output
на сайт coveralls.io. Включите интернет-соединение и запустите:
yarn coverage
Это должно загрузить существующий отчет о покрытии в комбинезон. Обновите страницу репозитория комбинезонов, чтобы увидеть полный отчет.
На странице сведений прокрутите вниз, чтобы найти раздел BADGE YOUR REPO
. Щелкните раскрывающийся список EMBED
скопируйте код уценки и вставьте его в файл README .
Код Климат
Code Climate — это инструмент, который помогает нам измерять качество кода. Он показывает нам показатели обслуживания, проверяя наш код на соответствие некоторым определенным шаблонам. Он обнаруживает такие вещи, как ненужные повторения и глубоко вложенные циклы. Он также собирает данные о тестовом покрытии, как и coveralls.io.
- Посетите codeclimate.com и нажмите «Зарегистрироваться на GitHub». Войдите, если у вас уже есть аккаунт.
- Оказавшись на панели инструментов, нажмите
Add a repository
». - Найдите в списке
express-api-template
и нажмите «Add Repo
». - Дождитесь завершения сборки и перенаправления на панель управления репо.
- В разделе
Codebase Summary
» нажмитеTest Coverage
. В меню «Test coverage
» скопируйтеTEST REPORTER ID
и вставьте его в свой .travis.yml в качестве значенияCC_TEST_REPORTER_ID
. - На той же странице на левой панели навигации в разделе
EXTRAS
щелкните Значки. Скопируйте значкиmaintainability
иtest coverage
в формате уценки и вставьте их в файл README.md .
Важно отметить, что существует два способа настройки проверок ремонтопригодности. Существуют настройки по умолчанию, которые применяются к каждому репозиторию, но при желании вы можете предоставить файл .codeclimate.yml в корне вашего проекта. Я буду использовать настройки по умолчанию, которые вы можете найти на вкладке « Maintainability
» на странице настроек репо. Я призываю вас хотя бы взглянуть. Если вы все еще хотите настроить свои собственные параметры, это руководство предоставит вам всю необходимую информацию.

AppVeyor
AppVeyor и Travis CI — это автоматические средства запуска тестов. Основное отличие состоит в том, что travis-ci запускает тесты в среде Linux, а AppVeyor — в среде Windows. Этот раздел включен, чтобы показать, как начать работу с AppVeyor.
- Посетите AppVeyor и войдите или зарегистрируйтесь.
- На следующей странице нажмите
NEW PROJECT
. - В списке репозиториев найдите
express-api-template
. Наведите на него курсор и нажмите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.
Добавление контроллера
В настоящее время мы обрабатываем запрос GET
к корневому URL-адресу /v1
внутри src/routes/index.js . Это работает так, как ожидалось, и в этом нет ничего плохого. Однако по мере роста вашего приложения вы хотите поддерживать порядок. Вы хотите, чтобы проблемы были разделены — вам нужно четкое разделение между кодом, который обрабатывает запрос, и кодом, который генерирует ответ, который будет отправлен обратно клиенту. Для этого мы пишем controllers
. Контроллеры — это просто функции, которые обрабатывают запросы, поступающие через определенный URL-адрес.
Для начала создайте папку controllers/
внутри папки src/
. Внутри 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 есть бесплатный план, который предоставляет 20 МБ бесплатного хранилища, которого достаточно для этого руководства. 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.
Откройте 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
выполняет массив запросов и ожидает завершения каждого из них внутри цикла. (Не делайте этого в производственном коде). Затем мы разрешаем промис только после того, как выполним последний запрос в списке. Причина использования массива в том, что количество таких запросов будет расти по мере роста количества таблиц в нашей базе данных.
Откройте runQuery.js и вставьте следующий код:
import { createTables, insertIntoTables } from './queryFunctions'; (async () => { await createTables(); await insertIntoTables(); })();
Здесь мы выполняем функции для создания таблицы и вставки сообщений в таблицу. Давайте добавим команду в раздел scripts
нашего package.json для выполнения этого файла.
"runQuery": "babel-node ./src/utils/runQuery"
Теперь запустите:
yarn runQuery
Если вы проверите свою базу данных, вы увидите, что таблица messages
была создана и что сообщения были вставлены в таблицу.
Если вы используете ElephantSQL, на странице сведений о базе данных щелкните BROWSER
в левом меню навигации. Выберите таблицу messages
и нажмите « Execute
. Вы должны увидеть сообщения из файла query.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 вы обычно пишете свои тесты перед реализацией кода, обеспечивающего их прохождение. Здесь я использую противоположный подход, потому что мы все еще работаем над настройкой базы данных.
Создайте новый файл hooks.js в папке test/
и введите следующий код:
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
Это указывает Трэвису раскрутить базу данных PostgreSQL 10 перед запуском наших тестов.
Добавьте команду для создания базы данных в качестве первой записи в разделе before_script
:
# add this as the first line in the before_script section - psql -c 'create database testdb;' -U postgres
Создайте переменную среды CONNECTION_STRING
в Travis и используйте следующее значение:
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.
Примечание . Я надеюсь, что у вас все работает нормально, но если по какой-то причине у вас возникнут проблемы, вы всегда можете проверить мой код в репозитории!
Теперь давайте посмотрим, как мы можем добавить сообщение в нашу базу данных. Для этого шага нам понадобится способ отправки 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(); }); });
Этот тест отправляет запрос POST к конечной точке /v1/messages
, и мы ожидаем, что будет возвращен массив. Мы также проверяем свойства id
, name
и message
в массиве.
Запустите свои тесты, чтобы убедиться, что этот случай не работает. Теперь исправим.
Для отправки почтовых запросов мы используем метод 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 и отправьте запрос POST
в конечную точку messages
. Если вы только что запустили свой тест, не забудьте запустить yarn query
, чтобы воссоздать таблицу messages
.
yarn query


Зафиксируйте свои изменения и отправьте их на GitHub. Ваши тесты должны пройти как на Travis, так и на AppVeyor. Ваше тестовое покрытие снизится на несколько пунктов, но это нормально.
- Соответствующая ветка в моем репо — 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:
Запустите свои тесты, чтобы убедиться, что этот тестовый пример не пройден.
Теперь давайте напишем код для прохождения теста.
Создайте новую папку middleware/
внутри папки src/
. Создайте в этой папке два файла:
- промежуточное ПО.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
. Мы вызываем функцию addMessage
, вызывая next
в modifyMessage
программном обеспеченииmodifyMessage. В качестве эксперимента закомментируйте строку modifyMessage
next()
в середине сообщенияmodifyMessage и посмотрите, как зависает запрос.
Откройте Postman и создайте новое сообщение. Вы должны увидеть добавленную строку.

Это хороший момент, чтобы зафиксировать наши изменения.
- Соответствующая ветка в моем репозитории — 09-middleware.
Обработка ошибок и асинхронное промежуточное ПО
Ошибки неизбежны в любом приложении. Задача, стоящая перед разработчиком, состоит в том, чтобы как можно более изящно справляться с ошибками.
В экспрессе:
Обработка ошибок относится к тому, как Express улавливает и обрабатывает ошибки, возникающие как синхронно, так и асинхронно.
Если бы мы писали только синхронные функции, нам, возможно, не пришлось бы так сильно беспокоиться об обработке ошибок, поскольку Express уже отлично с ними справляется. Согласно документам:
«Ошибки, возникающие в синхронном коде внутри обработчиков маршрутов и промежуточного программного обеспечения, не требуют дополнительной работы».
Но как только мы начнем писать обработчики асинхронных маршрутизаторов и промежуточное ПО, нам придется заняться обработкой ошибок.
Наше modifyMessage
ПО для модификации сообщений является синхронной функцией. Если в этой функции возникает ошибка, Express отлично с ней справится. Давайте посмотрим, как мы справляемся с ошибками в асинхронном промежуточном программном обеспечении.
Допустим, перед созданием сообщения мы хотим получить картинку из Lorem Picsum API по этому URL https://picsum.photos/id/0/info
. Это асинхронная операция, которая может быть успешной или неудачной, и это представляет собой случай, с которым нам нужно иметь дело.
Начните с установки 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
, запрос будет выполняться так, как будто ничего не произошло, и ошибка не будет обнаружена.
Импортируйте эту функцию и добавьте ее в цепочку промежуточного программного обеспечения маршрута почтовых сообщений:
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 отсюда.
- Откройте терминал в папке проекта, чтобы запустить команду.
# 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 на этапе перед запуском, потому что 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
Непрерывное развертывание (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. Подраздел приложения указывает, что мы хотим развернуть master
ветку нашего репозитория в app-name
приложения на Heroku. Можно развернуть разные ветки для разных приложений. Подробнее о доступных опциях можно прочитать здесь.
Выполните приведенную ниже команду, чтобы зашифровать ключ API Heroku и добавить его в раздел развертывания:
# 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, отслеживая свои журналы. Вы увидите, что сборка запущена, как только будет выполнен тест Трэвиса. Таким образом, если у нас будет неудачный тест, изменения никогда не будут развернуты. Точно так же, если сборка завершится ошибкой, весь тестовый прогон завершится неудачей. На этом процесс CI/CD завершается.
- Соответствующая ветка в моем репо — 11-cd.
Заключение
Если вы зашли так далеко, я говорю: «Большой палец вверх!» В этом руководстве мы успешно настроили новый проект Express. Мы приступили к настройке зависимостей разработки, а также непрерывной интеграции (CI). Затем мы написали асинхронные функции для обработки запросов к нашим конечным точкам API, завершив их тестами. Затем мы кратко рассмотрели обработку ошибок. Наконец, мы развернули наш проект на Heroku и настроили непрерывное развертывание.
Теперь у вас есть шаблон для вашего следующего внутреннего проекта. Мы сделали достаточно только для того, чтобы вы начали, но вы должны продолжать учиться, чтобы продолжать работать. Обязательно ознакомьтесь с документацией Express.js. Если вы предпочитаете использовать MongoDB
вместо PostgreSQL
, у меня есть шаблон, который делает именно это. Вы можете проверить его для настройки. У него всего несколько моментов отличия.
Ресурсы
- «Создание серверной части Express API с помощью MongoDB», Орджи Чиди Мэтью, GitHub
- «Краткое руководство по подключению ПО промежуточного слоя», Стивен Сагден
- «Шаблон Express API», GitHub
- «AppVeyor против Travis CI», StackShare
- «CLI Heroku», Центр разработчиков Heroku
- «Развертывание Heroku», Travis CI
- «Использование промежуточного программного обеспечения», Express.js
- «Обработка ошибок», Express.js
- «Начало работы», Мокко
-
nyc
(GitHub) - СлонSQL
- Почтальон
- выражать
- Трэвис Си
- Код Климат
- PostgreSQL
- pgAdmin