Как настроить серверный проект Express API с помощью PostgreSQL

Опубликовано: 2022-03-10
Краткое резюме ↬ В этой статье мы создадим набор конечных точек API с помощью Express с нуля в синтаксисе ES6 и рассмотрим некоторые передовые методы разработки. Узнайте, как все части работают вместе, когда вы создаете небольшой проект с использованием непрерывной интеграции и разработки через тестирование перед развертыванием в Heroku.

Мы воспользуемся подходом разработки через тестирование (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 создать проект в текущем каталоге.

Сейчас мы выполним некоторые операции по уборке дома.

  1. Удалите файл index/users.js .
  2. Удалите папки public/ и views/ .
  3. Переименуйте файл bin/www в bin/www.js .
  4. Удалите jade с помощью команды yarn remove jade .
  5. Создайте новую папку с именем src/ и переместите в нее следующее: 1. файл app.js 2. папку bin/ 3. папку route 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 обрабатывать каждый запрос, поступающий в /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"
  1. prestart scripts создает содержимое папки src/ и помещает его в папку build/ . Когда вы вводите команду yarn start , этот сценарий запускается первым перед сценарием start .
  2. start script теперь обслуживает содержимое папки build/ вместо папки src/ , которую мы обслуживали ранее. Это сценарий, который вы будете использовать при обслуживании файла в рабочей среде. На самом деле такие сервисы, как Heroku, автоматически запускают этот скрипт при развертывании.
  3. 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) и каждый раз, когда мы создаем запрос на включение. Это в основном полезно при выполнении запросов на вытягивание, показывая нам, не нарушил ли наш новый код какой-либо из наших тестов.

  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. В верхней части страницы репозитория рядом с именем репозитория щелкните значок build unknown . В модальном окне «Изображение состояния» выберите уценку из раскрывающегося списка форматов.
  8. Скопируйте полученный код и вставьте его в файл README.md .
  9. На странице проекта нажмите More options > Settings . В разделе « Environment Variables » добавьте переменную env 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

Сначала мы говорим Трэвису запустить наш тест с помощью 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 делает его доступным за пределами нашего локального компьютера.

  1. Посетите coveralls.io и либо войдите, либо зарегистрируйтесь с помощью своей учетной записи Github.
  2. Наведите указатель мыши на левую часть экрана, чтобы открыть меню навигации. Нажмите ADD REPOS .
  3. Найдите express-api-template и включите охват с помощью переключателя слева. Если вы не можете найти его, нажмите SYNC REPOS в правом верхнем углу и повторите попытку. Обратите внимание, что ваше репо должно быть общедоступным, если у вас нет учетной записи PRO.
  4. Нажмите детали, чтобы перейти на страницу сведений о репо.
  5. Создайте файл .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.

  1. Посетите codeclimate.com и нажмите «Зарегистрироваться на GitHub». Войдите, если у вас уже есть аккаунт.
  2. Оказавшись на панели инструментов, нажмите Add a repository ».
  3. Найдите в списке express-api-template и нажмите « Add Repo ».
  4. Дождитесь завершения сборки и перенаправления на панель управления репо.
  5. В разделе Codebase Summary » нажмите Test Coverage . В меню « Test coverage » скопируйте TEST REPORTER ID и вставьте его в свой .travis.yml в качестве значения CC_TEST_REPORTER_ID .
  6. На той же странице на левой панели навигации в разделе 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.

Значки Repo CI/CD. (Большой превью)

Сейчас самое время зафиксировать наши изменения.

  • Соответствующая ветка в моем репо — 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.

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 выполняет массив запросов и ожидает завершения каждого из них внутри цикла. (Не делайте этого в производственном коде). Затем мы разрешаем промис только после того, как выполним последний запрос в списке. Причина использования массива в том, что количество таких запросов будет расти по мере роста количества таблиц в нашей базе данных.

Откройте 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 
POST-запрос к конечной точке сообщений. (Большой превью)
Запрос GET, показывающий недавно добавленное сообщение. (Большой превью)

Зафиксируйте свои изменения и отправьте их на 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

  1. Чтобы начать, перейдите на https://www.heroku.com/ и либо войдите в систему, либо зарегистрируйтесь.
  2. Загрузите и установите интерфейс командной строки Heroku отсюда.
  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 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