RisingStack: Node Hero
Перевод Андрея Мелихова
Редактор-корректор Роман Пономарёв
- Начало работы с Node.js
- Использование NPM
- Понимание асинхронного программирования
- Ваш первый сервер на Node.js
- Работа с базами данных
- Модуль Request
- Файловая структура проекта
- Аутентификация в Node.js с использованием Passport.js
- Модульное тестирование
- Отладка
- Безопасность
- Деплой вашего приложения
- Мониторинг Node.js-приложений
Мы начнем с основ: никаких предварительных знаний Node.js не требуется. Цель этой книги — начать работу с Node.js и убедиться, что вы понимаете, как писать приложение с использованием этой платформы.
В первой главе вы узнаете, что такое Node.js, как установить её на свой компьютер и как начать с ней работать — так что в следующих главах можно будет приступить к реальной разработке. Приступим!
Node.js — это среда выполнения JavaScript, построенная на JavaScript-движке V8 из Chrome. В основе Node.js лежит событийно-управляемая модель с неблокирующими операциями I/O, что делает её легкой и эффективной.
Другими словами: Node.js предлагает вам возможность писать невероятно производительный серверный код с использованием JavaScript. Как говорится в официальном описании: Node.js — это среда выполнения, использующая тот же JavaScript-движок V8, который вы можете найти в браузере Google Chrome. Но этого недостаточно для успеха Node.js. В Node.js используется libuv - кросс-платформенная библиотека поддержки с акцентом на асинхронный ввод-вывод.
С точки зрения разработчика, Node.js однопоточна, но под капотом libuv использует треды, события файловой системы, реализует цикл событий, включает в себя тред-пулинг и так далее. В большинстве случаев вы не будете взаимодействовать с libuv напрямую.
Последнюю версию Node.js вы можете найти на официальном сайте: https://nodejs.org/en/download/.
При таком подходе довольно легко начать работу, но если позже вы захотите добавить в систему больше версий Node.js, лучше начать использовать nvm (node version manager) - диспетчер версий Node.js.
После его установки вы сможете использовать очень простой CLI API для смены версии Node.js:
Установка различных версий Node.js
nvm install 4.4
Затем, если вы хотите проверить в работе экспериментальную версию:
nvm install 5
Чтобы убедиться, что у вас установлена и запущена Node.js, выполните:
node --version
Если все в порядке, эта команда вернет номер версии текущего активного бинарного файла Node.js.
Если вы работаете над проектом, поддерживающим Node.js v4, вы можете начать использовать эту версию с помощью следующей команды:
nvm use 4
Затем вы можете переключиться на Node.js v5 с помощью той же самой команды:
nvm use 5
Хорошо, теперь мы знаем, как устанавливать Node.js и переключаться между её версиями, но в чём смысл?
С тех пор как был сформирован Node.js Foundation, Node.js имеет план релизов. Это очень похоже на другие проекты Linux Foundation. Это означает, что есть два релиза: стабильный и экспериментальный. В Node.js стабильными версиями с долговременной поддержкой (LTS) являются те, которые начинаются с четных чисел (4, 6, 8, ...). Экспериментальные версии нумеруются нечетными числами (5, 7, ...).
Мы рекомендуем использовать версию LTS в продакшене и пробовать новые возможности с экспериментальной версией.
Если вы используете Windows, здесь можно скачать альтернативу для nvm: nvm-windows.
Чтобы начать работу с Node.js, давайте попробуем её в консоли! Запустите Node.js, просто набрав node
:
$ node
>
Хорошо, давайте попробуем что-то напечатать:
$ node
> console.log('hello from Node.js')
После нажатия Enter вы получите следующее:
> console.log('hello from Node.js')
hello from Node.js
undefined
Не стесняйтесь играть с Node.js с помощью этого интерфейса: я обычно тестирую небольшие фрагменты кода здесь, если я не хочу помещать их в файл.
Пришло время создать наше приложение Hello Node.js!
Начнем с создания файла index.js
. Откройте свою IDE (Atom, Sublime, Code — выбор за вами), создайте новый файл и сохраните его с именем index.js
. Если вы закончили, скопируйте в него следующий фрагмент кода:
// index.js
console.log('hello from Node.js')
Чтобы запустить этот файл, вы должны снова открыть свой терминал и перейти в каталог, в котором размещён index.js
.
Как только вы успешно переместитесь в нужное место, запустите файл, используя команду node index.js
. Вы увидите, что эта команда будет выдавать тот же результат, что и раньше, выводя строку непосредственно в терминале.
Теперь у вас есть файл index.js
, поэтому пришло время перейти на следующий уровень! Давайте создадим что-то более сложное, разделив наш исходный код на несколько JavaScript-файлов с целью удобочитаемости и поддерживаемости. Чтобы начать работу, вернитесь в свою IDE и создайте следующую структуру каталогов (с пустыми файлами), но пока не трогайте package.json
, мы сгенерируем его автоматически на следующем шаге:
├── app
| ├── calc.js
| └── index.js
├── index.js
└── package.json
Каждый проект Node.js начинается с создания файла package.json
. Вы можете думать о нем как о JSON-представлении приложения и его зависимостей. Он содержит имя вашего приложения, автора (вас) и все зависимости, необходимые для запуска приложения. Мы рассмотрим раздел зависимостей позже в главе «Использование NPM».
Вы можете интерактивно генерировать файл package.json
с помощью команды npm init
в терминале. После запуска команды вас попросят ввести некоторые данные, например имя вашего приложения, версию, описание и так далее. Не нужно беспокоиться, просто нажимайте Enter, пока не получите сформированный JSON и вопрос is it ok?
. Нажмите Enter в последний раз и вуаля: ваш package.json
был автоматически сгенерирован и помещен в папку вашего приложения. Если вы откроете этот файл в своей IDE, он будет очень похож на фрагмент кода ниже.
// package.json
{
"name": "@risingstack/node-hero",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js"
},
"author": "", "license": "ISC"
}
Хорошей практикой является добавление стартового скрипта в ваш пакет package.json
. Как только вы это сделаете, как показано в примере выше, вы можете запустить приложение с помощью команды npm start
. Это очень удобно, когда вы хотите развернуть свое приложение у PaaS-провайдера: они могут распознать команду start
и использовать её для запуска приложения.
Теперь давайте вернемся к первому созданному вами файлу под названием index.js
. Я рекомендую оставить этот файл очень компактным: только подключение самого приложения (файл index.js
из подкаталога /app
, созданного ранее). Скопируйте следующий код в свой файл index.js
и сохраните:
// index.js
require('./app/index')
Теперь пришло время приступить к созданию реального приложения. Откройте файл index.js
из папки /app
, чтобы создать очень простой пример: добавление массива чисел. В этом случае файл index.js
будет содержать только числа, которые мы хотим добавить, а логика, требующая вычислений, должна быть помещена в отдельный модуль.
Вставьте этот код в файл index.js
в каталоге /app
.
// app/index.js
const calc = require('./calc')
const numbersToAdd = [
3,
4,
10,
2
]
const result = calc.sum(numbersToAdd)
console.log(`The result is: ${result}`)
Теперь вставьте фактическую бизнес-логику в файл calc.js
, который можно найти в той же папке.
// app/calc.js
function sum (arr) {
return arr.reduce(function(a, b) {
return a + b
}, 0)
}
module.exports.sum = sum
Чтобы проверить, всё ли вы сделали правильно, сохраните эти файлы, откройте терминал и введите npm start
или node index.js
. Если вы все сделали правильно, вы получите ответ: 19. Если что-то пошло не так, внимательно просмотрите лог в консоли и найдите проблему на его основе.
В следующей главе под названием «Использование NPM» мы рассмотрим, как использовать NPM - менеджер пакетов для JavaScript.
В этой главе вы узнаете, что такое NPM и как его использовать. Давайте приступим к изучению!
NPM — это менеджер пакетов, используемый Node.js-приложениями. В нём вы можете найти массу готовых модулей, поэтому вам не нужно изобретать колесо. Это похоже на Maven для Java или Composer для PHP. Существует два основных интерфейса, с которыми вы будете взаимодействовать: сайт NPM и набор инструментов командной строки (CLI).
И веб-сайт, и CLI используют один и тот же реестр, чтобы искать и отображать модули.
Сайт NPM можно найти по адресу https://npmjs.com. Здесь вы можете зарегистрироваться как новый пользователь или поискать нужные пакеты.
Чтобы запустить CLI, достаточно написать:
npm
Обратите внимание, что NPM поставляется вместе с бинарным файлом Node.js, поэтому вам не нужно его устанавливать, однако если вы хотите использовать определенную версию NPM, вы можете его обновить. Если вы хотите установить NPM версии 3, вы можете сделать это с помощью: npm install npm@3 -g
.
Вы уже встречались с NPM в предыдущей главе, когда создавали файл package.json
. Давайте расширим наши знания!
В этом разделе вы узнаете, как добавлять рантайм зависимости к вашему приложению.
Когда у вас есть файл package.json
, вы можете добавить зависимости к вашему приложению. Давайте добавим одну! Попробуйте следующее:
npm install lodash --save
С помощью этой единственной команды мы достигли двух вещей: во-первых, lodash
загружен и помещён в папку node_modules
. Это папка, в которой будут находиться все ваши внешние зависимости. Обычно вы не хотите добавлять её в свою систему управления версиями, поэтому, если вы используете git, обязательно добавьте node_modules
в файл .gitignore
.
Это может быть хорошей отправной точкой для вашего .gitignore
(ссылка на GitHub).
Давайте посмотрим, что происходит в файле package.json
! Появилось новое свойство, называемое dependencies
:
"dependencies": {
"lodash": "4.6.1"
}
Это означает, что lodash
версии 4.6.1
теперь установлен и готов к использованию. Обратите внимание, что NPM следует правилам SemVer для версионирования пакетов.
Используя нумерацию версий вида MAJOR.MINOR.PATCH, повышайте MAJOR-версию, когда вы делаете несовместимые изменения API, MINOR-версию, когда вы добавляете функциональность обратно-совместимым образом, и PATCH-версию, когда вы делаете исправления ошибок с обратной совместимостью. Для получения дополнительной информации: http://semver.org/
Поскольку lodash
готов к использованию, давайте посмотрим, как мы можем им воспользоваться! Вы можете сделать это так же, как и с вашим собственным модулем, но теперь вам не нужно указывать полный путь, достаточно только имени модуля:
// index.js
const _ = require('lodash')
_.assign({ 'a': 1 }, { 'b': 2 }, { 'c': 3 });
// → { 'a': 1, 'b': 2, 'c': 3 }
В этом разделе вы узнаете, как добавлять зависимости, необходимые только во время сборки приложения.
Когда вы собираете веб-приложения, вам может потребоваться минимизировать ваши JavaScript-файлы, объединить CSS-файлы и так далее. Модули, которые это сделают, будут выполняться только во время создания ресурсов, поэтому работающее приложение не нуждается в них.
Вы можете установить такие скрипты с помощью:
npm install mocha --save-dev
После этого в вашем файле package.json
появится новый раздел, называемый devDependencies
. Все модули, которые вы устанавливаете с помощью --save-dev
, будут описаны там, а также они будут помещены в тот же самый каталог node_modules
.
NPM-скрипты — очень мощная концепция, с их помощью вы можете создавать небольшие утилиты или даже описывать сложные системы сборки.
Наиболее распространенными являются скрипты start
и test
. С помощью start
вы можете описать, как нужно запускать приложение, а test
используется для запуска тестов. В вашем package.json
они могут выглядеть примерно так:
"scripts": {
"start": "node index.js",
"test": "mocha test",
"your-custom-script": "echo npm"
}
Что следует здесь отметить:
start
: просто описывает начальную точку для запуска вашего приложения, этот скрипт можно вызвать с помощьюnpm start
.test
: цель этого скрипта заключается в том, чтобы запускать ваши тесты: одно из удобств такого запуска тестов заключается в том, что в этом случаеmocha
не нужно устанавливать глобально, так как NPM будет искать её в папкеnode_modules/.bin
, аmocha
будет размещена там же. Запуск тестов может быть вызван с помощьюnpm test
.your-custom-script
: всё, что вы захотите (вы можете выбрать любое имя). Его можно вызвать с помощьюnpm run your-custom-script
— не забывайте проrun
в этом случае!
Первоначально у NPM было глобальное пространство имен для названий модулей, и с более чем 250 000 модулями в реестре большинство простых имен уже заняты. Кроме того, глобальное пространство имен содержит только общедоступные модули.
В NPM урегулировали эту проблему внедрением пакетов с ограниченной областью видимости (scoped packages), они имеют следующий шаблон именования:
@myorg/mypackage
Вы можете устанавливать пакеты с ограниченной областью видимости так же, как и раньше:
npm install @myorg/mypackage --save-dev
Они будут отображаться в вашем package.json
следующим образом:
"dependencies": {
"@myorg/mypackage": "^1.0.0"
}
Подключения пакетов с ограниченной областью видимости работает должным образом:
require('@myorg/mypackage')
Для получения большей информации обратитесь к документации NPM-модулей с ограниченной областью видимости.
В этой главе я расскажу вам о принципах асинхронного программирования и покажу, как создавать асинхронные операции в JavaScript и Node.js.
В традиционной практике программирования большинство операций ввода-вывода происходит синхронно. Если вы задумайтесь о Java и о том, как вы читаете файл в ней, вы получите что-то вроде этого:
try(FileInputStream inputStream = new FileInputStream("foo.txt")) {
Session IOUtils;
String fileContent = IOUtils.toString(inputStream);
}
Что происходит в фоновом режиме? Основной поток будет заблокирован до тех пор, пока файл не будет прочитан, а это означает, что за это время ничего другого не может быть сделано. Чтобы решить эту проблему и лучше использовать ваш CPU, вам придется управлять потоками вручную.
Если у вас больше блокирующих операций, очередь событий становится ещё хуже:
(Красные полосы отображают промежутки времени, в которые процесс ожидает ответа от внешнего ресурса и блокируется, чёрные полосы показывают, когда ваш код работает, зелёные полосы отображают остальную часть приложения)
Для решения этой проблемы Node.js предлагает модель асинхронного программирования.
Асинхронный ввод-вывод — это форма обработки ввода/вывода, позволяющая продолжить обработку других задач, не ожидая завершения передачи.
В следующем примере я покажу простой процесс чтения файлов в Node.js, как синхронным, так и асинхронным способом, с целью показать вам, чего вы можете достигнуть, если будете избегать блокировки ваших приложений.
Начнем с простого примера: синхронное чтение файла с использованием Node.js:
const fs = require('fs')
let content
try {
content = fs.readFileSync('file.md', 'utf-8')
} catch (ex) {
console.log(ex)
}
console.log(content)
Что здесь происходит? Мы читаем файл, используя синхронный интерфейс модуля fs
. Он работает ожидаемым образом: в переменную content
сохраняется содержимое file.md
. Проблема с этим подходом заключается в том, что Node.js будет заблокирована до завершения операции, то есть, пока читается файл, она не может сделать ничего полезного.
Посмотрим, как мы можем это исправить!
Асинхронное программирование, в том виде, в каком мы знаем его в JavaScript, может быть реализовано только при условии, что функции являются объектами первого класса: они могут передаваться как любые другие переменные другим функциям. Функции, которые могут принимать другие функции в качестве аргументов, называются функциями высшего порядка.
Один из самых простых примеров функций высшего порядка:
const numbers = [2,4,1,5,4]
function isBiggerThanTwo (num) {
return num > 2
}
numbers.filter(isBiggerThanTwo)
В приведенном выше примере мы передаем функцию isBiggerThanTwo
в функцию filter
. Таким образом, мы можем определить логику фильтрации.
Так появились функции обратного вызова (колбеки): если вы передаете функцию другой функции в качестве параметра, вы можете вызвать её внутри функции, когда она закончит свою работу. Нет необходимости возвращать значения, нужно только вызывать другую функцию с этими значениями.
В основе Node.js лежит принцип «первым аргументом в колбеке должна быть ошибка». Его придерживаются базовые модули, а также большинство модулей, найденных в NPM.
const fs = require('fs')
fs.readFile('file.md', 'utf-8', function (err, content) {
if (err) {
return console.log(err)
}
console.log(content)
})
Что следует здесь выделить:
- обработка ошибок: вместо блока
try-catch
вы проверяете ошибку в колбеке - отсутствует возвращаемое значение: асинхронные функции не возвращают значения, но значения будут переданы в колбеки
Давайте немного изменим этот файл, чтобы увидеть, как это работает на практике:
const fs = require('fs')
console.log('start reading a file...')
fs.readFile('file.md', 'utf-8', function (err, content) {
if (err) {
console.log('error happened during reading the file')
return console.log(err)
}
console.log(content)
})
console.log('end of the file')
Результатом выполнения этого кода будет:
start reading a file...
end of the file
error happened during reading the file
Как вы можете видеть, как только мы начали читать наш файл, выполнение кода продолжилось, а приложение вывело end of the file
. Наш колбек вызвался только после завершения чтения файла. Как такое возможно? Встречайте цикл событий (event loop).
Цикл событий лежит в основе Node.js и JavaScript и отвечает за планирование асинхронных операций.
Прежде чем погрузиться глубже, давайте убедимся, что мы понимаем, что такое программирование с управлением по событиям (event-driven programming).
Программирование с управлением по событиям представляет собой парадигму программирования, в которой поток выполнения программы определяется событиями, такими как действия пользователя (щелчки мышью, нажатия клавиш), выходы датчиков или сообщения из других программ/потоков.
На практике это означает, что приложения реагируют на события.
Кроме того, как мы уже узнали в первой главе, с точки зрения разработчика Node.js является однопоточным. Это означает, что вам не нужно иметь дело с потоками и синхронизировать их, Node.js скрывает эту сложность за абстракцией. Всё, кроме кода, выполняется параллельно.
Для более глубокого понимания работы цикла событий рекомендуем посмотреть это видео:
https://www.youtube.com/watch?v=8cV4ZvHXQL4
Поскольку теперь у вас есть общее представление о том, как работает асинхронное программирование в JavaScript, давайте рассмотрим несколько примеров того, как вы можете организовать свой код.
Чтобы избежать так называемого Callback-Hell, вы можете начать использовать async.js.
Async.js помогает структурировать ваши приложения и упрощает понимание потока управления.
Давайте рассмотрим короткий пример использования Async.js, а затем перепишем его с помощью промисов.
Следующий фрагмент перебирает три файла и выводит системную информацию по каждому:
async.parallel(['file1', 'file2', 'file3'],
fs.stat,
function (err, results) {
// results теперь содержит массив системных данных для каждого файла
})
Примечание переводчика: если вы пользуетесь Node.js версии 7 и выше, лучше воспользоваться встроенными конструкциями языка, такими как async/await.
Объект
Promise
используется для отложенных и асинхронных вычислений. Промис представляет собой операцию, которая еще не завершена, но ожидается в будущем.
На практике предыдущий пример можно переписать следующим образом:
function stats (file) {
return new Promise((resolve, reject) => {
fs.stat(file, (err, data) => {
if (err) {
return reject (err)
}
resolve(data)
})
})
}
Promise.all([
stats('file1'),
stats('file2'),
stats('file3')
])
.then((data) => console.log(data))
.catch((err) => console.log(err))
Конечно, если вы используете метод, возвращающий промис, то пример будет заметно компактнее.
В следующей главе вы узнаете, как запустить ваш первый Node.js HTTP-сервер.
В этой главе я расскажу вам о том, как вы можете запустить простой HTTP-сервер на Node.js и начать обрабатывать запросы.
Когда вы начинаете создавать HTTP-приложения в Node.js, встроенные модули http/https
- это то, с чем вы будете взаимодействовать.
Давайте создадим ваш первый HTTP-сервер на Node.js! Нам будет нужно подключить модуль http
и привязать наш сервер к порту 3000
.
// содежимое index.js
const http = require('http')
const port = 3000
const requestHandler = (request, response) => {
console.log(request.url)
response.end('Hello Node.js Server!')
}
const server = http.createServer(requestHandler)
server.listen(port, (err) => {
if (err) {
return console.log('something bad happened', err)
}
console.log(`server is listening on ${port}`)
})
Затем запускаем этот скрипт:
$ node index.js
Что нужно здесь отметить:
requestHandler
: эта функция будет вызываться каждый раз, когда на сервер придёт запрос. Если вы откроете в своём браузере адресlocalhost:3000
, два сообщения появятся в консоли: одно для/
и одно дляfavicon.ico
.if (err)
: обработка ошибок: если порт уже занят или есть какие-то другие причины, по которым сервер не может быть запущен, мы получим уведомление об этом.
Модуль http
крайне низкоуровневый: создание сложного веб-приложения с использованием вышеприведенного фрагмента кода очень трудоемко. Именно по этой причине мы обычно выбираем фреймворки для работы над нашими проектами. Есть множество фреймворков, вот самые популярные:
В этой и следующих главах мы будем использовать Express, так как именно для него вы можете найти множество модулей в NPM.
Быстрый, гибкий, минималистичный веб-фреймворк для Node.js — http://expressjs.com/
Добавление Express в ваш проект - это просто установка через NPM:
$ npm install express --save
После того, как вы установили Express, давайте посмотрим, как создать приложение аналогичное тому, что мы написали ранее:
const express = require('express')
const app = express()
const port = 3000
app.get('/', (request, response) => {
response.send('Hello from Express!')
})
app.listen(port, (err) => {
if (err) {
return console.log('something bad happened', err)
}
console.log(`server is listening on ${port}`)
})
Самое большое различие, которое вы можете здесь заметить, заключается в том, что Express по умолчанию даёт вам роутер. Вам не нужно вручную разбирать URL, чтобы решить, что делать, вместо этого вы определяете маршрутизацию приложения с помощью app.get
, app.post
, app.put
и так далее, а они уже транслируются в соответствующие HTTP-запросы.
Одна из самых мощных концепций, которую реализует Express — это паттерн Middleware.
Вы можете думать о промежуточных обработчиках как о конвейерах Unix, но для HTTP-запросов.
На диаграмме вы можете увидеть, как запрос идёт через условное Express-приложение. Он проходит через три промежуточных обработчика. Каждый обработчик может изменить этот запрос, а затем, основываясь на вашей бизнес-логике, третий middleware отправит ответ, либо запрос попадёт в обработчик соответствующего роута.
На практике вы можете сделать это следующим образом:
const express = require('express')
const app = express()
app.use((request, response, next) => {
console.log(request.headers)
next()
})
app.use((request, response, next) => {
request.chance = Math.random()
next()
})
app.get('/', (request, response) => {
response.json({
chance: request.chance
})
})
app.listen(3000)
Что следует здесь отметить:
app.use
: это то, как вы можете описать middleware. Этот метод принимает функцию с тремя параметрами, первый из которых является запросом, второй — ответом, а третий — коллбекомnext
. Вызовnext
сигнализирует Express о том, что он может переходить к следующему промежуточному обработчику.- Первый промежуточный обработчик только логирует заголовки и мгновенно вызывает следующий.
- Второй добавляет дополнительное свойство к запросу - это одна из самых мощных функций шаблона middleware. Ваши промежуточные обработчики могут добавлять дополнительные данные к объекту запроса, который могут считывать/изменять middleware, расположенные ниже.
Как и во всех фреймворках, правильная обработка ошибок имеет решающее значение. В Express вы должны создать специальный промежуточный обработчик - middleware с четырьмя входными параметрами:
const express = require('express')
const app = express()
app.get('/', (request, response) => {
throw new Error('oops')
})
app.use((err, request, response, next) => {
// логирование ошибки, пока просто console.log
console.log(err)
response.status(500).send('Something broke!')
})
Что следует здесь отметить:
- Обработчик ошибок должен быть последней функцией, добавленной с помощью
app.use
. - Обработчик ошибок принимает коллбек
next
. Он может использоваться для объединения нескольких обработчиков ошибок.
Ранее мы рассмотрели, как отправлять JSON-ответы. Пришло время узнать, как отрендерить HTML простым способом. Для этого мы собираемся использовать пакет handlebars с обёрткой express-handlebars.
Сначала создадим следующую структуру каталогов:
├── index.js
└── views
├── home.hbs
└── layouts
└── main.hbs
После этого заполните index.js
следующим кодом:
// index.js
const path = require('path')
const express = require('express')
const exphbs = require('express-handlebars')
app.engine('.hbs', exphbs({
defaultLayout: 'main',
extname: '.hbs',
layoutsDir: path.join(__dirname, 'views/layouts')
}))
app.set('view engine', '.hbs')
app.set('views', path.join(__dirname, 'views'))
Приведенный выше код инициализирует движок handlebars и устанавливает каталог шаблонов в views/layouts
. Это каталог, в котором будут храниться ваши шаблоны.
После того, как вы сделали эту настройку, вы можете поместить свой начальный html
в main.hbs
. Чтобы всё было проще, давайте сразу перейдём к этому:
<html>
<head>
<title>Express handlebars</title>
</head>
<body>
{{{body}}}
</body>
</html>
Вы можете заметить метку {{{body}}}
— здесь будет размещен ваш контент. Давайте создадим home.hbs
!
<h2>Hello {{name}}<h2>
Последнее, что мы должны сделать, чтобы заставить всё это работать, - добавить обработчик маршрута в наше приложение Express:
app.get('/', (request, response) => {
response.render('home', {
name: 'John'
})
})
Метод render
принимает два параметра:
- Первый — это имя шаблона.
- Второй — данные, необходимые для рендеринга.
Как только вы сделаете запрос по этому адресу, вы получите что-то вроде этого:
<html>
<head>
<title>Express handlebars</title>
</head>
<body>
<h2>Hello John</h2>
</body>
</html>
Это всего лишь верхушка айсберга. Чтобы узнать, как добавить больше шаблонов (и даже частичных), обратитесь к официальной документации express-handlebars.
В некоторых случаях вам может потребоваться выяснить, что происходит с Express, когда приложение работает. Для этого вы можете передать следующую переменную окружения в Express: DEBUG=express*
.
Вы должны запустить свой Node.js HTTP-сервер, используя:
$ DEBUG=express* node index.js
Вот как вы можете настроить свой первый HTTP-сервер на Node.js с нуля. Я рекомендую Express для начала, а затем поэкспериментируйте.
В следующей главе вы узнаете, как получать информацию из баз данных.
В следующей главе я покажу вам, как вы можете настроить приложение Node.js для работы с базой данных и научу вас основам её использования.
Раздача статических страниц пользователям (о чём вы узнали в предыдущей главе) может быть подходящим вариантом для лэндингов или для личных блогов. Однако, если вы хотите отдавать персонализированный контент, вам придётся где-то хранить данные.
Возьмём простой пример: регистрация пользователя. Вы можете отдавать пользовательский контент для отдельных пользователей или сделать его доступным для них только после идентификации.
Если пользователь хочет зарегистрироваться в вашем приложении, вы можете создать обработчик соответствующего роута, чтобы сделать регистрацию возможной:
const users = []
app.post('/users', function (req, res) {
// извлекаем данные пользователя из тела запроса
const user = req.body
users.push({
name: user.name,
age: user.age
})
res.send('successfully registered')
})
Таким образом, вы можете хранить пользователей в глобальной переменной, которая будет храниться в памяти в течение всего срока жизни вашего приложения.
Использование этого метода может быть проблематичным по нескольким причинам:
- оперативная память дорогая
- память очищается каждый раз, когда вы перезапускаете ваше приложение
- если вы не будете чистить память, иногда вы можете получить переполнение памяти
Следующее решение, которое может прийти вам в голову, это хранить данные в файлах.
Если мы постоянно сохраняем наши пользовательские данные в файловой системе, мы можем избежать ранее перечисленных проблем.
На практике этот метод выглядит следующим образом:
const fs = require('fs')
app.post('/users', function (req, res) {
const user = req.body
fs.appendToFile('users.txt', JSON.stringify({name: user.name, age: user.age }), (err) => {
res.send('successfully registered')
})
})
Таким образом, мы не потеряем пользовательские данные даже после перезагрузки сервера. Это решение также экономически выгодно, так как увеличение дискового пространства дешевле, чем покупка ОЗУ.
К сожалению, хранение пользовательских данных таким образом все ещё имеет несколько недостатков:
- добавление данных работает неплохо, но подумайте об обновлении или удалении
- если мы работаем с файлами, нет простого решения для параллельного доступа к ним (системные блокировки не позволят вам писать в один файл параллельно)
- когда мы пытаемся масштабировать наше приложение, мы не можем разделить файлы между серверами (на самом деле можем, но это далеко за пределами уровня этого руководства)
Здесь выходят на сцену настоящие базы данных.
Возможно, вы уже слышали, что существуют два основных типа баз данных: SQL и NoSQL.
Начнём с SQL. Это язык запросов, предназначенный для работы с реляционными базами данных. SQL немного отличается в зависимости от продукта, который вы используете, но базовые вещи в них тождественны.
Сами данные хранятся в таблицах. Каждая добавленная часть будет представлена в виде строки в таблице, как в Google Sheets или Microsoft Excel.
В базе данных SQL вы можете определить схемы. Они предоставят скелет для данных, которые вы собираетесь разместить. Также, перед тем, как сохранить данные, будет необходимо задать типы различных значений. Например, вам нужно будет определить таблицу для ваших пользовательских данных и сообщить базе данных, что у неё есть имя пользователя, являющееся строкой, и возраст - целый тип.
С другой стороны, в последнее десятилетие стали весьма популярны NoSQL базы данных. С NoSQL вам не нужно определять схему и вы можете хранить любой произвольный JSON. Это хорошо сочетается с JavaScript, потому что мы можем легко превратить любой объект в JSON. Будьте осторожны, потому что вы никогда не можете гарантировать, что данные консистентны, и вы никогда не сможете узнать, какая структура находится в базе данных.
Существует распространённое заблуждение о Node.js, которое можно услышать довольно часто:
«Node.js можно использовать только с MongoDB (самая популярная NoSQL база данных)».
По моему опыту, это не так. У большинства баз данных имеются драйверы для Node.js и библиотеки в NPM. По моему мнению, они такие же простые и лёгкие в использовании, как MongoDB.
Для простоты мы будем использовать SQL в следующем примере. Мой выбор — PostgreSQL.
Чтобы запустить PostgreSQL, вам необходимо установить его на свой компьютер. Если вы используете Mac, вы можете использовать Homebrew для установки PostgreSQL. В противном случае, если вы работаете в Linux, вы можете установить его с помощью своего диспетчера пакетов.
Для получения дополнительной информации ознакомьтесь с этим отличным руководством по началу работы с вашей первой базой данных.
Если вы планируете использовать инструмент для просмотра базы данных, я бы рекомендовал утилиту для командной строки — psql
. Она поставляется вместе с сервером PostgreSQL. Вот небольшая инструкция, которая пригодится, если вы начнёте использовать psql
.
Если вам не нравится интерфейс командной строки, вы можете использовать pgAdmin, который является инструментом с открытым исходным кодом и предназначен для администрирования PostgreSQL.
Обратите внимание, что SQL — это сам по себе язык программирования. Мы не будем рассматривать все его возможности, а только наиболее простые. Если вам потребуется глубже изучить SQL, то в интернете есть много отличных онлайн-курсов, охватывающих все основы PostgreSQL.
Во-первых, мы должны создать базу данных, которую мы будем использовать. Для этого введите следующую команду в терминал: createdb node_hero
.
Затем мы должны создать таблицу для наших пользователей.
CREATE TABLE users(
name VARCHAR(20),
age SMALLINT
);
Наконец, мы можем вернуться к программированию. Вот как вы можете взаимодействовать с вашей базой данных через вашу программу на Node.js:
'use strict'
const pg = require('pg')
const conString = 'postgres://username:password@ localhost/node_hero' // Убедитесь, что вы указали данные от вашей базы данных
pg.connect(conString, function (err, client, done) {
if (err) {
return console.error('error fetching client from pool', err)
}
client.query('SELECT $1::varchar AS my_first_query', ['node hero'], function (err, result) {
done()
if (err) {
return console.error('error happened during query', err)
}
console.log(result.rows[0])
process.exit(0)
})
})
Это был простой пример — "hello world" в PostgreSQL. Обратите внимание, что первым параметром является строка, которая является нашей SQL-командой, второй параметр представляет собой массив значений, которыми мы хотели бы параметризовать наш запрос.
Большой ошибкой с точки зрения безопасности был бы ввод данных, пришедших от пользователя, в том виде, в котором они были переданы. Приведённая выше функция client.query
защищает вас от SQL-инъекций, являющихся распространённым видом атаки, когда злоумышленник пытается внедрить в запрос произвольный SQL-код. Всегда учитывайте это при создании любого приложения, в котором возможен ввод данных со стороны пользователя. Чтобы узнать больше, ознакомьтесь с нашим контрольным списком безопасности Node.js-приложений.
Примечание переводчика: обычно никто не пишет SQL-запросы руками, вместо этого используют так называемые конструкторы запросов (query builder), например sequelize и knex.
Давайте продолжим наш предыдущий пример.
app.post('/users', function (req, res, next) {
const user = req.body
pg.connect(conString, function (err, client, done) {
if (err) {
// Передача ошибки в обработчик express
return next(err)
}
client.query('INSERT INTO users (name, age) VALUES ($1, $2);', [user.name, user.age], function (err, result) {
done() // Этот коллбек сигнализирует драйверу pg, что соединение может быть закрыто или возвращено в пул соединений
if (err) {
// Передача ошибки в обработчик express
return next(err)
}
res.send(200)
})
})
})
Достижение разблокировано: пользователь сохранён в базе данных! :) Теперь давайте попробуем прочитать эти данные. Затем добавим в наше приложение новый роут для поиска пользователей.
app.get('/users', function (req, res, next {
pg.connect(conString, function (err, client, done) {
if (err) {
// Передача ошибки в обработчик express
return next(err)
}
client.query('SELECT name, age FROM users;', [], function (err, result) {
done()
if (err) {
// Передача ошибки в обработчик express
return next(err)
}
res.json(result.rows)
})
})
})
Теперь вы можете запустить любой сложный SQL-запрос, который вы только сможете вообразить, в вашем Node.js-приложении.
С помощью этой техники вы можете постоянно хранить данные в своём приложении, а благодаря трудолюбивой команде разработчиков модуля node-postgres это проще простого.
Мы рассмотрели все основы, которые вы должны знать об использовании баз данных в Node.js. Теперь попробуйте создать что-то самостоятельно.
Пробуйте всё и экспериментируйте, потому что это лучший способ стать настоящим героем Node.js! Практикуйтесь и будьте готовы к следующей главе о том, как общаться со сторонними API!
В следующей главе вы узнаете основы HTTP и как вы можете получать ресурсы из внешних источников с помощью модуля Node.js request
.
HTTP - это протокол передачи гипертекста. HTTP функционирует как протокол запроса-ответа в модели клиент-сервер.
Прежде чем погрузиться в общение с другими API-интерфейсами, давайте рассмотрим коды состояния HTTP, с которыми мы можем столкнуться во время работы нашего приложения. Они описывают результаты наших запросов и очень важны для обработки ошибок.
- 1xx — Информационный
- 2xx — Успех: Эти коды состояния говорят о том, что наш запрос был получен и обработан правильно. Наиболее распространённые коды успеха -
200 OK
,201 Created
и204 No Content
. - 3xx — Редирект: Эта группа кодов показывает, что клиент должен будет выполнить дополнительные действия для завершения запроса. Наиболее распространёнными кодами перенаправления являются
301 Moved Permanently
,304 Not Modified
. - 4xx — Ошибка клиента. Этот класс кодов состояния используется, когда запрос, отправленный клиентом, содержит какую-то ошибку. Ответ сервера обычно содержит объяснение ошибки. Наиболее распространёнными кодами ошибок клиента являются
400 Bad Request
,401 Unauthorized
,403 Forbidden
,404 Not Found
,409 Conflict
. - 5xx - Ошибка сервера: эти коды отправляются, когда сервер не смог выполнить корректный запрос из-за какой-то ошибки. Причиной может быть ошибка в коде или некоторая временная или постоянная неисправность. Наиболее распространённые коды ошибок сервера:
500 Internal Server Error
,503 Service Unavailable
.
Если вы хотите узнать больше о кодах состояния HTTP, вы можете найти подробное объяснение здесь.
Подключение к внешним API-интерфейсам — простая задача в Node. Вы можете просто подключить базовый модуль HTTP и начать отправку запросов.
Конечно, есть способы обращения к внешним API намного лучше. В NPM вы можете найти несколько модулей, которые облегчат вам этот процесс. Например, двумя наиболее популярными являются модули request и superagent.
Оба этих модуля имеют интерфейс, построенный на колбеках, который может привести к проблемам (я уверен, вы слышали о Callback-Hell), но, к счастью, у нас есть доступ к версиям, обёрнутым в промисы.
Использование модуля request-promise — это очень просто. После установки из NPM вам нужно только подключить его к программе:
const request = require('request-promise')
Отправка GET-запроса:
const options = {
method: 'GET',
uri: 'https://risingstack.com'
}
request(options)
.then(function (response) {
// Запрос был успешным, используйте объект ответа как хотите
})
.catch(function (err) {
// Произошло что-то плохое, обработка ошибки
})
Если вы вызываете JSON API, вам может потребоваться, чтобы request-promise
автоматически распарсил ответ. В этом случае просто добавьте это в параметры запроса:
json: true
POST-запросы работают аналогичным образом:
const options = {
method: 'POST',
uri: 'https://risingstack.com/login',
body: {
foo: 'bar'
},
json: true
// Тело запроса приводится к формату JSON автоматически
}
request(options)
.then(function (response) {
// Обработка ответа
})
.catch(function (err) {
// Работа с ошибкой
})
Чтобы добавить параметры строки запроса, вам просто нужно добавить свойство qs
к объекту options
:
const options = {
method: 'GET',
uri: 'https://risingstack.com',
qs: {
limit: 10,
skip: 20,
sort: 'asc'
}
}
Этот код соберёт следующий URL для запроса: https://risingstack.com?limit=10&skip=20&sort=asc
. Вы также можете назначить любой заголовок так же, как мы добавили параметры запроса:
const options = {
method: 'GET',
uri: 'https://risingstack.com',
headers: {
'User-Agent': 'Request-Promise',
'Authorization': 'Basic QWxhZGRpbjpPcGVuU2VzYW1l'
}
}
Обработка ошибок - это неотъемлемая часть запросов на внешние API, поскольку мы никогда не можем быть уверены в том, что с ними произойдёт. Помимо наших ошибок клиента сервер может ответить с ошибкой или просто отправить данные в неправильном или непоследовательном формате. Помните об этом, когда вы пытаетесь обработать ответ. Кроме того, использование catch
для каждого запроса - хороший способ избежать сбоя на нашем сервере по вине внешнего сервиса.
Поскольку вы уже узнали, как развернуть HTTP-сервер на Node.js, как отрисовать HTML и как получить данные из внешних API, пришло время собрать эти знания вместе!
В этом примере мы собираемся создать небольшое приложение на Express, отображающее текущие погодные условия на основе названий городов.
(Чтобы получить ключ для API AccuWeather, посетите их сайт для разработчиков)
const express = require('express')
const rp = require('request-promise')
const exphbs = require('express-handlebars')
const path = require('path')
const app = express()
app.engine('.hbs', exphbs({
defaultLayout: 'main',
extname: '.hbs',
layoutsDir: path.join(__dirname, 'views/layouts')
}))
app.set('view engine', '.hbs')
app.set('views', path.join(__dirname, 'views'))
app.get('/:city', (req, res) => {
rp({
uri: 'http://dataservice.accuweather.com/locations/v1/cities/search',
qs: {
q: req.params.city,
apikey: 'api-key'
// Используйте ваш ключ для accuweather API
},
json: true
})
.then((data) => {
res.render('home', {
res: JSON.stringify(data)
})
})
.catch((err) => {
console.log(err)
res.render('error')
})
})
app.listen(3000)
Пример выше делает следующее:
- создаёт Express-сервер
- устанавливает handlebars в качестве шаблонизатора
- отправляет запрос к внешнему API
- если все в порядке, то приложение отображает страницу
- в противном случае приложение показывает страницу неудачи и регистрирует ошибку
В следующей главе Node Hero вы узнаете, какая файловая структура предпочтительней в Node.js проектах.
Большинство Node.js-фреймворков не имеют фиксированной структуры каталогов и может быть непросто с самого начала проекта придерживаться правильной структуры. В этой главе вы узнаете, как правильно структурировать проект на Node.js, чтобы избежать сложностей, когда ваши приложения начнут расти.
Существует множество возможных способов организации Node.js-проектов и каждый из известных методов имеет свои плюсы и минусы. Однако, по нашему опыту, разработчики всегда хотят добиться одного и того же: чистоты кода и возможности легко добавлять новые функции.
В последние годы у нас в RisingStack была возможность создавать эффективные Node.js-приложения различного размера и мы получили много информации о том, что нужно, и что не нужно делать, когда занимаешься структурированием проекта.
Мы сформулировали пять простых правил, которые мы применяем во время разработки на Node.js. Если вы будете им следовать, ваши проекты будут в порядке.
Представьте, что у вас есть следующая структура каталогов:
// Неправильно
.
├── controllers
| ├── product.js
| └── user.js
├── models
| ├── product.js
| └── user.js
├── views
| ├── product.hbs
| └── user.hbs
Проблемы с этим подходом:
- Чтобы понять, как работает страница
product
, вам нужно открыть три разных каталога с большим количеством переключений контекста - В конечном итоге вы пишете длинные пути при подключении модулей:
require('../../controllers/user.js')
Вместо этого вы можете структурировать Node.js-приложения вокруг функций продукта / страниц / компонентов. Это облегчает понимание:
// Правильно
.
├── product
| ├── index.js
| ├── product.js
| └── product.hbs
├── user
| ├── index.js
| ├── user.js
| └── user.hbs
Используйте эти файлы только для экспорта, например:
// product/index.js
var product = require('./product')
module.exports = {
create: product.create
}
Тесты предназначены не только для проверки того, генерирует ли модуль ожидаемый результат, они также документируют ваши модули (вы узнаете больше о написании тестов в следующих главах). Из-за этого легче понять код приложения, когда тестовые файлы находятся рядом с реализацией.
Поместите ваши дополнительные тестовые файлы в отдельную папку test
, чтобы избежать путаницы.
.
├── test
| └── setup.spec.js
├── product
| ├── index.js
| ├── product.js
| ├── product.spec.js
| └── product.hbs
├── user
| ├── index.js
| ├── user.js
| ├── user.spec.js
| └── user.hbs
Для хранения файлов конфигурации используйте каталог config
.
├── config
| ├── index.js
| └── server.js
├── product
| ├── index.js
| ├── product.js
| ├── product.spec.js
| └── product.hbs
Создайте отдельный каталог для ваших дополнительных скриптов в package.json
.
.
├── scripts
| ├── syncDb.sh
| └── provision.sh
├── product
| ├── index.js
| ├── product.js
| ├── product.spec.js
| └── product.hbs
В следующей главе Node Hero вы узнаете, как аутентифицировать пользователей, используя Passport.js.
В этой главе вы узнаете, как реализовать стратегию локальной аутентификации в Node.js приложении с использованием Passport.js и Redis.
Прежде чем перейти к написанию кода, давайте рассмотрим новые технологии, которые мы будем использовать в этой главе.
Простая ненавязчивая аутентификация для Node.js - passportjs.org
Passport.js - это middleware для проверки подлинности. Мы будем исполььзовать её для управления сессиями.
Redis это опенсорс (лицензии BSD) хранилище структур данных в оперативной памяти, используемое как база данных, кэш и брокер сообщений — redis.io
Мы собираемся хранить информацию о сессии пользователя в Redis, а не в памяти процесса. Таким образом, наше приложение будет намного проще масштабировать.
Для демонстрационных целей создадим приложение, которое умеет только следующее:
- предоставляет форму входа
- предоставляет две защищённые страницы:
- страницу профиля
- безопасные заметки
Вы уже научились структурировать Node.js-проекты в предыдущей главе Node Hero, поэтому давайте использовать эти знания!
Мы собираемся использовать следующую структуру:
├── app
| ├── authentication
| ├── note
| ├── user
| ├── index.js
| └── layout.hbs
├── config
| └── index.js
├── index.js
└── package.json
Как вы можете видеть, мы организуем файлы и каталоги вокруг функций. У нас будет страница пользователя, страница заметок и некоторые функции, связанные с проверкой подлинности.
(Вы можете скачать исходный код по ссылке)
Наша цель - реализовать в нашем приложении следующий процесс аутентификации:
- Пользователь вводит имя и пароль
- Приложение проверяет, являются ли они корректными
- Если имя и пароль корректны, приложение отправляет заголовок
Set-Cookie
, который будет использоваться для аутентификации дальнейших страниц - Когда пользователь посещает страницы в том же домене, ранее установленный cookie добавляется ко всем запросам
- Аутентификация на закрытых страницах происходит с помощью этого файла cookie
Чтобы настроить такую стратегию аутентификации, выполните следующие три действия:
- Настройте Express
- Настройте Passport
- Добавьте защищённые разделы на сайте
Мы будем использовать Express для серверной среды. Вы можете узнать больше на эту тему, перечитав главу «Ваш первый сервер на Node.js».
// file:app/index.js
const express = require('express')
const passport = require('passport')
const session = require('express-session')
const RedisStore = require('connect-redis')(session)
const app = express()
app.use(session({
store: new RedisStore({
url: config.redisStore.url
}),
secret: config.redisStore.secret,
resave: false,
saveUninitialized: false
}))
app.use(passport.initialize())
app.use(passport.session())
Что мы тут делаем?
Прежде всего, нам нужны все зависимости, требующиеся для управления сессией. После этого мы создали новый экземпляр из модуля express-session
, который будет хранить наши сессии.
Для хранения сессий мы используем Redis, но вы можете использовать любые другие, такие как MySQL или MongoDB.
Passport.js — отличный пример библиотеки, использующей плагины. В этом уроке мы добавляем модуль passport-local
, который добавляет простую локальную стратегию аутентификации с использованием имён пользователей и паролей.
Для простоты в этом примере (см. ниже) мы не используем базу данных, вместо неё используется экземпляр объекта пользователя в памяти. В реальных приложениях findUser
будет искать пользователя в базе данных.
file:app/authenticate/init.js
const passport = require('passport')
const LocalStrategy = require('passport-local').Strategy
const user = {
username: 'test-user',
password: 'test-password', id: 1
}
passport.use(new LocalStrategy(
function(username, password, done) {
findUser(username, function (err, user) {
if (err) {
return done(err)
}
if (!user) {
return done(null, false)
}
if (password !== user.password ) {
return done(null, false)
}
return done(null, user)
})
}
))
Как только findUser возвращается с нашим объектом пользователя, остаётся только сравнить введённый пользователем и реальный пароли, чтобы увидеть, есть ли совпадение.
Если они совпадают, мы разрешаем пользователю войти (возвращая объект пользователя в passport
- return done(null, user)
), если нет — возвращаем ошибку авторизации (путём возврата null
- return done(null)
).
Примечание переводчика: в настоящих приложениях стараются никогда не хранить пароли пользователей в открытом виде. В базу данных записывают хэш пароля и сравнивают его с хэшом значения, введённого пользователем.
Чтобы добавить защищенные разделы на сайт, мы используем шаблон middleware Express. Для этого сначала создадим middleware для аутентификации:
// file:app/user/init.js
const passport = require('passport')
app.get('/profile', passport.authenticationMiddleware(), renderProfile)
Она имеет только одну задачу: если пользователь аутентифицирован (имеет правильные cookie-файлы), она просто вызывает следующую middleware. В противном случае пользователь перенаправляется на страницу, где он может войти в систему.
Использование этой middleware также просто, как добавление любой другой middleware в определение роута.
// file:app/authentication/middleware.js
function authenticationMiddleware () {
return function (req, res, next) {
if (req.isAuthenticated()) {
return next()
}
res.redirect('/')
}
}
В этом разделе учебника по Node.js вы узнали, как добавить базовую аутентификацию в ваше приложение. Позже вы можете расширить его с помощью различных стратегий аутентификации, таких как Facebook или Twitter. Вы можете найти больше стратегий по адресу http://passportjs.org/.
Полный рабочий пример нашего демонстрационного приложения вы можете найти на GitHub.
Следующая глава Node Hero будет посвящена юнит-тестированию Node.js приложений. Вы узнаете такие концепции, как юнит-тестирование, пирамида тестирования, дублёры и многое другое!
В этой главе вы узнаете, что такое модульное тестирование (юнит-тестирование) в Node.js и как правильно тестировать ваши приложения.
Вы можете рассматривать тесты как гарантии надёжности ваших приложений. Они будут запускаться не только на вашей локальной машине, но и на CI-сервисах, чтобы сломанные сборки не попадали в продакшен.
«Тесты - это больше, чем просто гарантии, они обеспечивают живую документацию для вашей кодовой базы».
Вы можете спросить: что я должен протестировать в своём приложении? Сколько тестов у меня должно быть?
Ответ варьируется, но, как правило, вы можете следовать рекомендациям, установленным пирамидой тестирования.
По сути, тестовая пирамида описывает, что вы должны писать модульные тесты, интеграционные тесты и e2e тесты. У вас должно быть больше интеграционных тестов, чем e2e и ещё больше модульных тестов.
Давайте посмотрим, как вы можете добавить модульные тесты для своих приложений!
Обратите внимание, что здесь мы не собираемся говорить об интеграционных и e2e тестах, поскольку они выходят далеко за рамки этого учебника.
Мы пишем модульные тесты для того, чтобы проверить, работает ли данный модуль (юнит). Все зависимости исключаются, что означает, что мы предоставляем поддельные зависимости (фейки) для модуля.
Вы должны писать тесты для публичных методов, а не для внутренней реализации данного модуля.
Каждый модульный тест имеет следующую структуру:
- Настройка теста
- Вызов тестируемого метода
- Утверждение
В каждом модульном тесте должна проверяться только одна проблема. (Конечно, это не означает, что вы можете добавить только одно утверждение).
Для модульного тестирования мы собираемся использовать следующие библиотеки:
- запуск тестов: mocha, альтернативно tape
- библиотека утверждений: chai, альтернативно assert
- шпионы, стабы и моки: sinon (для настройки тестов).
Прежде чем приступить к практике модульного тестирования, давайте разберёмся, что такое шпионы, стабы (заглушки) и моки!
Вы можете использовать шпионов для получения информации о вызовах функций, например, сколько раз они были вызваны или какие аргументы были им переданы.
it('calls subscribers on publish', function () {
var callback = sinon.spy()
PubSub.subscribe('message', callback)
PubSub.publishSync('message')
assertTrue(callback.called)
})
// Пример взят из документации Sinon: http://sinonjs.org/docs/
Стабы похожи на шпионов, но они заменяют целевую функцию. Вы можете использовать стабы для управления поведением метода, чтобы форсировать какие-то события в коде (например, выброс ошибки) или предотвратить вызовы внешних ресурсов (таких как HTTP API).
it('calls all subscribers, even if there are exceptions', function (){
var message = 'an example message'
var error = 'an example error message'
var stub = sinon.stub().throws()
var spy1 = sinon.spy()
var spy2 = sinon.spy()
PubSub.subscribe(message, stub)
PubSub.subscribe(message, spy1)
PubSub.subscribe(message, spy2)
PubSub.publishSync(message, undefined)
assert(spy1.called)
assert(spy2.called)
assert(stub.calledBefore(spy1))
})
// Пример взят из документации Sinon: http://sinonjs.org/docs/
Моки — это поддельные методы с заранее запрограммированным поведением и соглашениями.
it('calls all subscribers when exceptions happen', function () {
var myAPI = {
method: function () {}
}
var spy = sinon.spy()
var mock = sinon.mock(myAPI)
mock.expects("method").once().throws()
PubSub.subscribe("message", myAPI.method)
PubSub.subscribe("message", spy)
PubSub.publishSync("message", undefined)
mock.verify()
assert(spy.calledOnce)
})
// Пример взят из документации Sinon: http://sinonjs.org/docs/
Как вы можете видеть, для моков вы должны заранее определить соглашения.
Представьте, что вы хотите протестировать следующий модуль:
const fs = require('fs')
const request = require('request')
function saveWebpage (url, filePath) {
return getWebpage(url, lePath)
.then(writeFile)
}
function getWebpage (url) {
return new Promise (function (resolve, reject) {
request.get(url, function (err, response, body) {
if (err) {
return reject(err)
}
resolve(body)
})
})
}
function writeFile (fileContent) {
let lePath = 'page'
return new Promise (function (resolve, reject) {
fs.writeFile(filePath, fileContent, function (err) {
if (err) {
return reject(err)
}
resolve(filePath)
})
})
}
module.exports = {
saveWebpage
}
Этот модуль делает одну вещь: он сохраняет веб-страницу (основываясь на переданном URL) в файл на локальном компьютере. Чтобы протестировать этот модуль, мы должны «застабить» как модуль fs
, так и модуль request
.
Прежде чем начинать писать модульные тесты, в RisingStack мы обычно добавляем файл test-setup.spec.js
для создания базовой настройки тестов, например создания песочниц Sinon. Это избавит вас от написания sinon.sandbox.create()
и sinon.sandbox.restore()
после каждого теста.
// test-setup.spec.js
const sinon = require('sinon')
const chai = require('chai')
beforeEach(function () {
this.sandbox = sinon.sandbox.create()
})
afterEach(function () {
this.sandbox.restore()
})
Кроме того, обратите внимание, что мы всегда ставим файлы тестов рядом с реализацией и даём им имя вида .spec.js
. В нашем package.json
вы можете найти следующие строки:
{
"test-unit": "NODE_ENV=test mocha '/**/*.spec.js'",
}
Как только у нас появились эти настройки, пришло время написать сами тесты!
const fs = require('fs')
const request = require('request')
const expect = require('chai').expect
const webpage = require('./webpage')
describe('The webpage module', function () {
it('saves the content', function * () {
const url = 'google.com'
const content = '<h1>title</h1>'
const writeFileStub = this.sandbox.stub(fs, 'writeFile', function ( filePath, fileContent, cb) {
cb(null)
})
const requestStub = this.sandbox.stub(request, 'get', function (url, cb) {
cb(null, null, content)
})
const result = yield webpage.saveWebpage(url)
expect(writeFileStub).to.be.calledWith()
expect(requestStub).to.be.calledWith(url)
expect(result).to.eql('page')
})
})
Полные исходники примера вы можете найти здесь.
Чтобы лучше понять, насколько хорошо ваша кодовая база покрыта тестами, вы можете сгенерировать отчёт о покрытии.
Этот отчёт будет включать следующие метрики:
- покрытие строк кода,
- покрытие инструкций,
- покрытие ветвлений,
- и покрытие функций.
В RisingStack мы используем istanbul для анализа покрытия кода. Вы должны добавить следующий скрипт к вашему package.json
, чтобы использовать istanbul
с mocha
:
istanbul cover _mocha $(find ./lib -name \"*.spec.js\" -not -path \"./node_modules/*\")
Как только вы это сделаете, вы получите что-то вроде:
Вы можете кликнуть мышью и на самом деле увидеть, что ваш исходный код аннотирован: какая часть протестирована, а какая — нет.
Тестирование может уберечь вас от множества неприятностей. Тем не менее, неизбежно наступает время отладки. В следующей главе Node Hero вы узнаете, как отлаживать Node.js-приложения.
В этой главе вы узнаете, как отлаживать Node.js-приложения с помощью модуля debug
, встроенного отладчика Node.js и инструментов разработчика в Chrome.
Термины «баг» и «дебаггинг» - часть инженерного жаргона в течение многих десятилетий. Одно из первых упоминаний о багах:
Так было со всеми моими изобретениями. Первый шаг — интуиция, приходящая, как вспышка. Затем возникают трудности: устройство отказывается работать и именно тогда проявляются «жучки» — как называют эти мелкие ошибки и трудности — и требуются месяцы пристального наблюдения, исследований и усилий, прежде чем дело дойдёт до коммерческого успеха или неудачи. Томас Эдисон
Одним из наиболее часто используемых методов поиска проблем в приложениях Node.js является интенсивное использование console.log
для отладки.
console.log
эффективен для отладки небольших фрагментов, но мы рекомендуем использовать альтернативы получше!
Давайте посмотрим на них!
Некоторые из наиболее популярных модулей, которые могут потребоваться в вашем проекте, поставляются с модулем debug
. С помощью этого модуля вы можете включить сторонние модули для логирования в стандартный поток вывода — stdout
. Чтобы проверить, использует ли его модуль, посмотрите раздел зависимостей файла package.json
.
Чтобы использовать модуль debug
, вы должны установить переменную среды DEBUG
при запуске вашего приложения. Вы также можете использовать символ *
для имён модулей. Следующая настройка будет выводить все логи, связанные с express, в стандартный поток вывода.
DEBUG=express* node app.js
Вывод в консоль будет выглядеть так:
Node.js включает полнофункциональную утилиту для отладки вне процесса, основанную на TCP-подобном протоколе и встроенном дебаггинг-клиенте.
Чтобы запустить встроенный отладчик, вы должны запустить приложение следующим образом:
node debug app.js
Как только вы это сделаете, вы увидите что-то вроде этого:
Для навигации по интерфейсу вы можете использовать следующие команды:
c
=> продолжить выполнение кодаn
=> выполнить эту строку и перейти к следующей строкеs
=> войти в эту функциюo
=> закончить выполнение функции и выйти наружуrepl
=> удалённо выполнить код
Вы можете добавить точки останова в свои приложения, вставив инструкцию debugger
в код.
function add (a, b) {
debugger
return a + b
}
var res = add('apple', 4)
Во время отладки можно наблюдать за изменением значений выражений и переменных. В каждой точке останова каждое выражение из списка наблюдаемых будет исполняться в текущем контексте и отображаться непосредственно перед списком точек останова.
Чтобы начать использовать вотчеры, вы должны «навесить» их для выражений, за значениями которых вы хотите наблюдать. Это можно сделать следующим образом:
watch('expression')
Чтобы получить список активных вотчеров, наберите watchers
, а чтобы перестать отслеживать выражение, используйте unwatch('expression')
.
Подсказка для профессионалов: вы можете переключать процессы Node.js в режим отладки, отправив им команду
SIGUSR1
. После этого вы можете подключить отладчик с помощьюdebug -p <pid>
.
Чтобы понять все возможности встроенного отладчика, ознакомьтесь с официальной документацией API.
Когда вы начинаете отладку сложных приложений, визуализация может неплохо помочь. Было бы хорошо использовать знакомый пользовательский интерфейс Chrome DevTools для отладки Node.js-приложений, не правда ли?
Хорошие новости! Протокол отладки Chrome уже портирован в Node.js и может использоваться для отладки Node.js-приложений.
Чтобы начать его использовать, вам необходимо сначала установить node-inspector
:
npm install -g node-inspector
После его установки вы можете начать отладку своих приложений, запустив их следующим образом:
node-debug index.js --debug-brk
(-debug-brk
приостанавливает выполнение на первой строке)
node-inspector
откроет Chrome DevTools и вы можете приступить к отладке своего Node.js-приложения.
Примечание переводчика: начиная с Node.js 6, интеграция с Chrome DevTools встроена в ядро. Для отладки необходимо запустить приложение с ключом --inspect
.
В следующей главе Node Hero вы узнаете, как защитить ваши Node.js-приложения.
В этой главе, посвящённой безопасности Node.js, вы узнаете, как защитить свои приложения от наиболее распространённых атак.
В настоящее время мы почти каждую неделю видим серьёзные нарушения безопасности, например, в случаях LinkedIn или MySpace. Во время этих атак было украдено огромное количество пользовательских данных, а также нанесён вред корпоративной репутации.
Исследования также показывают, что тикеты на ошибки, связанные с безопасностью, остаются открытыми в среднем в течение 18 месяцев в некоторых областях индустрии.
Мы должны исправить это. Если вы разрабатываете программное обеспечение, безопасность является частью вашей работы.
Давайте начнём и защитим наше приложение Node.js путём правильного отношения к написанию кода, использованию утилит и эксплуатации!
Использование eval
может сделать приложение уязвимым для атак с инъекцией кода. Старайтесь не использовать его, но если вам нужно, никогда не отправляйте непровалидированный пользовательский ввод в eval
.
Явный вызов eval
— это не единственное, чего вам следует избегать. В фоновом режиме каждое из следующих выражений использует eval:
setInterval(String, 2)
setTimeout(String, 2)
new Function(String)
С помощью use strict
вы можете использовать ограниченный «вариант» JavaScript. Он устраняет бесшумность некоторых ошибок и делает их явными.
'use strict'
delete Object.prototype
// TypeError
var obj = {
a: 1,
a: 2
}
// Синтаксическая ошибка
Во время различных ошибочных сценариев ваше приложение может выдавать конфиденциальные сведения о внутренней инфраструктуре, например: X-Powered-By: Express
.
Трассировки стека сами по себе не рассматриваются как уязвимости, но они часто показывают информацию, которая может быть интересна злоумышленнику. Предоставление отладочной информации в результате операций, генерирующих ошибки, считается плохой практикой. Вы должны всегда логировать их, но никогда не показывать пользователям.
Статический анализ кода вашего приложения может поймать много ошибок. Для этого мы предлагаем использовать ESLint с JavaScript Standard Style.
Использование правильного стиля кода недостаточно для эффективной защиты Node.js, вы также должны быть осторожны в том, как вы запускаете свои сервисы в продакшене.
К сожалению, мы это встречаем довольно часто: разработчики запускают своё Node.js-приложение с правами суперпользователя, так как они хотят, чтобы приложение слушало порт 80 или 443.
Это просто неправильно. В случае ошибки ваш процесс может привести к сбою всей системы, поскольку вы дали ему права на что угодно.
Вместо этого вы можете настроить HTTP-сервер/прокси для пересылки запросов. Это может быть nginx или Apache. Ознакомьтесь с нашей статьёй об использовании Node.js в продакшене, чтобы узнать больше.
Существуют определённые HTTP-заголовки, связанные с безопасностью, которые должен установить ваш сайт. Эти заголовки:
- Strict-Transport-Security обеспечивает безопасные подключения к серверу (HTTP через SSL/TLS)
- X-Frame-Options обеспечивает защиту от кликджекинга
- X-XSS-Protection включает XSS-фильтр, встроенный в большинство современных веб-браузеров
- X-Content-Type-Options содержит инструкции по определению типа файла по content-type и не допускает MIME-сниффинг контента
- Content-Security-Policy предотвращает широкий спектр атак, включая межсайтовый скриптинг и другие межсайтовые инъекции
В Node.js их легко установить с помощью модуля Helmet:
var express = require('express')
var helmet = require('helmet')
var app = express()
app.use(helmet())
Helmet также доступен для Koa: koa-helmet.
Для каждого cookie-файла должен быть указан следующий список флагов:
- secure — этот атрибут говорит браузеру отправлять cookie, только если запрос отправляется через HTTPS
- HttpOnly — этот атрибут используется для предотвращения атак, таких как межсайтовый скриптинг, поскольку он не позволяет получить доступ к файлу cookie через JavaScript
- domain — этот атрибут используется для сравнения с доменом сервера, на котором запрашивается URL; если домен соответствует или является субдоменом, тогда будет проверен атрибут
path
- path — в дополнение к домену может быть указан путь URL, по которому действителен файл cookie; если домен и путь совпадают, cookie будет отправлен в запросе
- expires — этот атрибут используется для установки постоянных файлов cookie, поскольку cookie не истекает, пока не будет превышена установленная дата
В Node.js вы можете легко создать этот файл cookie, используя пакет cookie. Опять же, это довольно низкий уровень, поэтому вы, вероятно, в конечном итоге будете использовать обёртку, например, cookie-session.
var cookieSession = require('cookie-session')
var express = require('express')
var app = express()
app.use(cookieSession({
name: 'session',
keys: [
process.env.COOKIE_KEY1,
process.env.COOKIE_KEY2
]
}))
app.use(function (req, res, next) {
var n = req.session.views || 0
req.session.views = n++
res.end(n + ' views')
})
app.listen(3000)
(Этот пример взят из документации модуля cookie-session)
Поздравляю, вы почти закончили! Если вы следовали этому руководству и тщательно выполнили предыдущие шаги, у вас останется только одна область, касающаяся безопасности Node.js. Давайте перейдём к использованию надлежащих инструментов для поиска уязвимостей модулей!
«Всегда ищите уязвимости в своих модулях Node.js. Вы то, что вы реквайрите.»
Цель Retire.js - помочь вам определить использование версий модулей с известными уязвимостями.
Просто установите:
npm install -g retire
После этого запуск с помощью команды retire
будет искать уязвимости в вашем каталоге node_modules
. Также обратите внимание, что Retire.js работает не только с Node.js-модулями, но и с фронтенд-библиотеками.
nsp
является основным интерфейсом командной строки для Node Security Platform. Он позволяет производить аудит файлов package.json
или npm-shrinkwrap.json
с помощью API NSP для выявления уязвимых модулей.
npm install nsp --global
# Внутри директории вашего проекта
nsp check
Безопасность Node.js не является большой проблемой после того, как вы это прочитали? Надеюсь, вы нашли, что эти правила будут полезны для защиты ваших Node.js-приложений, и будете следовать им в будущем, поскольку безопасность является частью вашей работы!
Если вы хотите больше узнать о безопасности Node.js, я могу порекомендовать эти статьи:
В следующей главе Node Hero вы узнаете, как развернуть защищённое Node.js-приложение, чтобы люди могли начать его использовать!
В этой главе о деплое Node.js вы узнаете, как развернуть Node.js-приложения на сервер PaaS-провайдера (Heroku), либо с помощью Docker.
Поставщики платформы как услуги (Platform-as-a-Service, PaaS) могут отлично подойти для команд, которые находятся на начальном этапе разработки или создают небольшие приложения.
В этой части учебника вы узнаете, как использовать Heroku для лёгкого развёртывания Node.js-приложений.
Для развертывания в Heroku нам нужно отправить код в удалённый git-репозиторий. Для этого добавьте свой публичный ключ в Heroku: после регистрации перейдите к своей учётной записи и сохраните его там (в качестве альтернативы вы можете сделать это с помощью CLI).
Нам также нужно будет загрузить и установить Heroku toolbelt. Чтобы убедиться, что ваша установка прошла успешно, выполните следующую команду в своём терминале:
heroku --version
heroku-toolbelt/3.40.11 (x86_64-darwin10.8.0) ruby/1.9.3
После того, как toolbelt запущен и работает, пройдите процедуру логина:
heroku login
Enter your Heroku credentials.
Email: [email protected]
Password:
(Для получения большей информации о тулките, посетите Heroku Devcenter)
Нажмите «Create New App», добавьте новое приложение и выберите регион. Через несколько секунд ваше приложение будет готово, и вы увидите следующий экран:
Перейдите на страницу настроек приложения и скопируйте git-url. В своём терминале добавьте удаленный url-адрес Heroku:
git remote add heroku HEROKU_URL
Вы готовы развернуть свое первое приложение в Heroku, это действительно просто git push
:
git push heroku master
Как только вы это сделаете, Heroku начинает собирать ваше приложение и развернёт его. После развертывания ваш сервис будет доступен на странице https://YOUR-APP-NAME.herokuapp.com
.
Одной из наиболее ценных частей Heroku является экосистема, поскольку десятки партнёров предоставляют базы данных, инструменты мониторинга и другие решения.
Чтобы опробовать работу с дополнениями, установите Trace, наше решение для мониторинга Node.js. Чтобы сделать это, найдите Add-ons на странице своего приложения и начните вводить Trace, затем кликните на ссылку для установки. Легко, правда?
(Чтобы закончить интеграцию с Trace, следуйте нашей инструкции)
В последние годы Docker набирал мощный импульс и стал программным обеспечением для контейнеризации. В этой части учебника вы узнаете, как создавать образы из Node.js-приложений и запускать их.
«Docker для Node.js - отличный выбор, если вы хотите большего контроля и уменьшения затрат»
Чтобы начать работу с Docker, загрузите и установите его с официального веб-сайта Docker.
Во-первых, мы должны выучить два понятия:
- Dockerfile: вы можете представить Dockerfile в качестве рецепта — он содержит инструкции по созданию Docker-образа
- Docker-образ: результат выполнения Dockerfile — это исполняемый юнит
Чтобы запустить приложение внутри Docker, мы должны сначала написать Dockerfile.
В корневой папке вашего проекта создайте пустой текстовый файл Dockerfile
, а затем вставьте в него следующий код:
FROM risingstack/alpine:3.3-v4.2.6-1.1.3
COPY package.json package.json
RUN npm install
# Add your source files
COPY . .
CMD ["npm", "start"]
Что можно здесь отметить:
FROM
: описывает базовый образ, используемый для создания нового образа — в данном случае он будет получен из публичного репозитория Docker HubCOPY
: эта команда копирует файлpackage.json
в Docker-образ, чтобы мы могли запускатьnpm install
внутриRUN
: это команда запуска, в данном случае она запускаетnpm install
COPY
снова: обратите внимание, что мы сделали копии в двух отдельных шагах. Причина в том, что Docker создаёт слои из результатов выполнения команд, поэтому, если нашpackage.json
не меняется, он не будет вызыватьnpm install
сноваCMD
: Docker-образ может иметь только одну командуCMD
— она определяет, какой процесс должен быть запущен образом
После того, как вы создали Dockerfile
, вы можете создать из него Docker-образ:
docker build .
Используете приватные NPM-модули? Прочитайте нашу инструкцию по установке приватных NPM-модулей в Docker!
После успешной сборки вы можете вывести список всех доступных образов с помощью:
docker images
Для запуска образа:
docker run IMAGE_ID
Поздравляю! Вы только что локально запустили докеризированное Node.js-приложение. Время развернуть его на сервере!
Одна из замечательных особенностей Docker заключается в том, что, когда у вас есть собранный образ, вы можете запускать его повсюду: в большинстве окружений достаточно вызвать docker pull
вашего образа и запустить его.
Некоторые провайдеры, которые вы можете попробовать:
- AWSBeanStalk
- HerokuDockerSupport
- DockerCloud
- Kubernetes в Google Cloud (я очень рекомендую прочитать нашу статью о переходе на Kubernetes с PaaS-провайдера)
Настройка их очень проста. Если у вас возникнут проблемы, не стесняйтесь задать вопрос в разделе комментариев!
В следующей главе Node Hero вы узнаете, как мониторить свои приложения Node.js, так, чтобы они могли быть онлайн 24/7.
В последней главе этого учебника я расскажу вам, как настроить мониторинг Node.js и как найти важнейшие проблемы в продакшен-среде.
Получение информации о работе системы в продакшене имеет решающее значение при создании Node.js-приложений! У вас есть обязательство постоянно обнаруживать узкие места и выяснить, что замедляет работу вашего продукта.
Ещё большая задача заключается в том, чтобы обрабатывать и пресекать простои. Вы должны быть уведомлены, как только это произойдёт, желательно, прежде чем ваши клиенты начнут жаловаться. Исходя из этих потребностей, надлежащий мониторинг должен дать вам, по крайней мере, следующие возможности и понимание поведения вашего приложения:
- Профилирование на уровне кода: Вы должны понимать, сколько времени требуется для запуска каждой функции в продакшен-среде, а не только локально.
- Мониторинг сетевых подключений: Если вы строите микросервисную архитектуру, вы должны контролировать сетевые подключения и снизить задержки в обмене данными между вашими службами.
- Дашборд отслеживания производительности: Знание и постоянное наблюдение за важнейшими метриками производительности вашего приложения необходимы для быстрой и стабильной продакшен-системы.
- Предупреждения в режиме реального времени: По понятным причинам, если что-то ломается, вам необходимо немедленно получить уведомление. Это означает, что вам нужны инструменты, которые могут интегрироваться с Pagerduty или Opsgenie, и ваша DevOps-команда не пропустит ничего важного.
«Получение информации о работе системы в продакшене имеет решающее значение при создании Node.js-приложений»
Разработчики, придерживающиеся первой концепции, склонны смешивать мониторинг серверов и мониторинг самих приложений. Поскольку мы склонны делать много виртуализаций, эти концепции следует рассматривать отдельно, так как один сервер может поддерживать десятки приложений.
Рассмотрим основные различия!
Мониторинг сервера отвечает за хост-машину. Он должен помочь вам ответить на следующие вопросы:
- У моего сервера достаточно места на диске?
- У моего сервера достаточно ресурсов процессора?
- У моего сервера достаточно памяти?
- Доступен ли мой сервер по сети?
Для мониторинга сервера вы можете использовать такие инструменты, как zabbix.
Мониторинг приложения, с другой стороны, отвечает за здоровье конкретного экземпляра приложения. Он должен дать вам ответы на следующие вопросы:
- Может ли экземпляр приложения попасть в базу данных?
- Как много запросов обрабатывается?
- Каково время ответа для отдельных случаев?
- Может ли моё приложение обрабатывать запросы? Оно запущено?
Для мониторинга приложения я рекомендую использовать наш инструмент под названием Trace. Что же ещё? :)
Мы разработали его как простой в использовании и эффективный инструмент, который можно использовать для мониторинга и отладки приложений от момента их создания и до момента, когда у вас будет огромное приложение в продакшене с сотнями сервисов.
Чтобы начать работу с Trace, перейдите на https://trace.risingstack.com и создайте свою бесплатную учётную запись!
После регистрации выполните следующие действия, чтобы добавить Trace в ваше Node.js-приложение. Это займёт всего минуту:
Легко, правда? Если всё пойдёт хорошо, вы увидите, что подключённый сервис только что начал отправлять данные в Trace:
В качестве первого шага мониторинга вашего Node.js-приложения я рекомендую перейти на страницу метрик и проверить производительность ваших сервисов.
- Вы можете использовать панель времени ответа для проверки медианы и 95-го перцентиля. Это поможет вам понять, когда и почему ваше приложение замедляется и как оно влияет на ваших пользователей.
- График пропускной способности показывает количество запросов в минуту (rpm) для категорий кодов состояния (200-299 // 300-399 // 400-499 //> 500). Таким образом, вы сможете легко отделить здоровые и проблемные HTTP-запросы в своём приложении.
- График использования памяти показывает, сколько памяти использует ваш процесс. Это очень полезно для распознавания утечек памяти и предотвращения сбоев.
Если вы хотите увидеть специальные метрики Node.js, проверьте графики сбора мусора и графики циклов событий. Они могут помочь вам отследить утечки памяти. Прочтите нашу документацию по метрикам.
Как я упоминал ранее, для вашего приложения в продакшене вам нужна надлежащая система оповещения.
Перейдите на страницу оповещения Trace и нажмите Create a new alert.
- Самое важное, что нужно сделать, это настроить оповещения о простоях и проблемах с памятью. Trace уведомит вас по электронной почте / Slack / Pagerduty / Opsgenie, и вы также можете использовать Webhooks.
- Я рекомендую настроить оповещение, которое мы называем «Ошибка по коду состояния», чтобы узнать о HTTP-запросах с кодами состояния 4XX или 5XX. Это ошибки, о которых вам следует позаботиться.
- Также может быть полезно создать оповещение о времени отклика и получить уведомление, когда ваше приложение начнёт работать медленней.
Перейдите на страницу Profiler и запросите новый дамп памяти, подождите 5 минут и запросите другой. Загрузите их и откройте на странице Profiles Chrome DevTool. Выберите вторую (самую последнюю) и нажмите Comparison.
Этим способом вы можете легко найти утечки памяти в вашем приложении. Мы подробно описали этот процесс, вы можете прочитать его здесь: Охота на привидение — Поиск утечки памяти в Node.js
Профилирование на уровне кода важно для понимания того, сколько времени занимает ваша функция для работы в реальной продакшен-среде. К счастью, Trace также покрывает эту область.
Все, что вам нужно сделать, перейти на вкладку CPU Profiles на странице * Profiler*. Здесь вы можете запросить и скачать профиль, который вы можете загрузить в Chrome DevTool. После того, как вы его загрузили, вы сможете увидеть 10-секундный фрейм своего приложения и просмотреть все свои функции с таймингами и URL-адресами.
С помощью этих данных вы сможете выяснить, что замедляет ваше приложение и справиться с этим!
Вот и всё.
За время чтения 13 глав Node Hero вы изучили основы построения отличных приложений с помощью Node.js.
Надеюсь, вам понравилось и вы стали лучше разбираться в Node.js! Пожалуйста, поделитесь этим учебником с друзьями, если вы считаете, что им это тоже нужно, и покажите им Trace. Это отличный инструмент для разработки на Node.js!
Если вы хотите продолжить совершенствование своих навыков Node.js-разработчика, рекомендуем наш новый учебник Node.js at Scale!