Наличие интуитивно понятной файловой структуры играет огромную роль – легче добавлять новый функционал и рефакторить код. Подходящий способ структурирования выглядит так:
Такая организация позволяет ограничить размер файла примерно до 100 строк, что делает ревью и траблшутинг менее кошмарным делом.
Возьмите за правило отделять и выносить логику в отдельный файл – вы ограничите переключение контекста, которое происходит при чтении одного файла. Это также полезно при мерже в мастер – будет меньше конфликтов слияния.
Чтобы обеспечить соблюдение правил в команде, вы можете настроить линтер, который сообщит, когда вы переходите через установленный лимит строк в файле или если одна строка имеет длину более 100 символов.
1.1. Переменная окружения NODE_ENV
Установив переменную окружения NODE_ENV , вы примерно трёхкратно увеличите производительность. В терминале это можно сделать следующим образом:
Если вы используйте server.js , добавьте следующее:
1.2. Включаем Gzip-сжатие
Установите npm-пакет компрессии:
Затем добавьте следующий фрагмент в свой код:
1.3. Асинхронные функции
Не блокируйте поток выполнения и не используйте синхронные функции. Вместо этого используйте функции Promises и Async/Await. Если у вас есть доступ только к синхронным функциям, оберните их в async-функцию, которая будет выполняться вне основного потока.
Если не избежать использования синхронных функций – запустите их в отдельном потоке. Чтобы избежать блокировки основного потока и проседания CPU, создайте дочерние процессы для обработки интенсивных задач процессора.
1.4. Система логов
Чтобы унифицировать журналы по всему Express.js-приложению, а не использовать console.log() , используйте агент для централизованного ведения, структурирования и сбора журналов.
Можно использовать любой инструмент управления журналами, например, Sematext, Logz.io или Datadog. Практически все агенты базируются на Winston и Morgan. Они отслеживают трафик запросов API с помощью промежуточного программного обеспечения. Это сразу же даст вам журналы и данные по каждому параметру, что важно для отслеживания производительности.
Вот так добавляется логгер и промежуточное ПО:
Превью того, что получается на выходе
1.5. Обработка ошибок и исключений
При использовании в коде Async/Await для обработки ошибок и исключений рекомендуется применять операторы try-catch , а также использовать для ведения журнала ошибок Express logger.
Кроме того, рекомендуется настроить catch-all error :
Здесь будет поймана любая ошибка, выброшенная в контроллере. А ещё можно добавить слушателей в сам процесс:
1.6. Следите за утечками памяти
Вы не сможете поймать ошибки до того, как они произойдут. Некоторые проблемы не обусловлены только «тематикой» исключения, вываливающегося при падении приложения. Все решения сводятся к тому, чтобы упредить любую возможность утечки памяти.
Заметить утечку гораздо проще, чем вы думаете. Если память процесса продолжает неуклонно расти, а не периодически сокращается в результате сборки мусора – скорее всего это она и есть. В идеале вы должны сосредоточиться на предотвращении утечек, а не на их устранении и отладке.
Добавьте в Express-приложение сборщик метрик, который будет хранить их в одном месте. Это поможет проанализировать данные и добраться до основной причины.
Результаты работы сборщика метрик
В чем прелесть – это всего лишь одна строка кода. Добавьте ее в файлик app.js .
Благодаря этому вы получите доступ к нескольким информационным панелям, дающим ключевое представление о том, что происходит с вашим Express-приложением. Данные можно фильтровать и группировать для визуализации процессов, памяти, использования CPU и HTTP-запросов/ответов. Что вы должны сделать сразу же – настроить оповещения об изменениях в работе софта.
Двигаемся дальше от Express.js к конкретным советам и рекомендациям по JavaScript и о том, как его использовать оптимизированным и надежным способом.
2.1. Чистые функции
Чистые функции – это функции, которые не изменяют внешнее состояние. Они принимают параметры, что-то делают с ними и возвращают значение.
Вместо использования var , применяйте только const и полагайтесь на чистые функции для создания новых объектов вместо изменения существующих. Это связано с использованием функций высокого порядка в JavaScript, например .map() , .reduce() , .filter() и т. д.
2.2. Параметры объекта
JavaScript – слабо типизированный язык. В вызов функции может быть передан один или несколько параметров. Даже если объявление функции имеет фиксированное число определенных аргументов. Этот огрех можно решить, используя объекты в качестве параметров функции.
Все эти вызовы функций будут работать одинаково. Вы можете принудительно указать имена параметров, при этом вы не связаны порядком, что значительно упрощает управление.
2.3. Тестирование
Используйте что-нибудь простое, например, Mocha и Chai. Mocha – это фреймворк для тестирования, а Chai – assertion библиотека.
Установите npm пакеты:
Давайте потестим функцию. В test.js добавьте следующее:
Добавьте это в package.json :
Теперь запустим тесты, выполнив следующую команду в терминале:
Выведется примерно такое:
3.1. Управление переменными среды в Node.js с dotenv
Dotenv – это модуль npm, позволяющий загружать переменные среды в любое Node.js приложение. В корне вашего проекта создайте .env файл. Здесь вы добавите все необходимые переменные окружения.
Загрузка файл проста. В верхней части app.js разместите dotenv:
Dotenv по умолчанию загружает файл с именем .env . При необходимости прочитайте руководство по настройке нескольких dotenv-файлов.
3.2. Перезапуск приложения с помощью Systemd
Systemd – часть строительных блоков ОС Linux. Он запускает и управляет системными процессами. Вам нужно запустить Node.js процесс, как системную службу, чтобы он восстанавливался после сбоев.
На виртуальной машине или сервере создайте новый файл в разделе /lib/systemd/system/ :
Две важные строки в этом файле – ExecStart и Restart. ExecStart запустит ваш server.js с помощью бинарника /usr/bin/node (обязательно проверяйте абсолютный путь к файлу server.js ). Функция Restart=on-failure перезапустит приложение, если оно «обвалится».
После сохранения fooapp.service , перезагрузите демона и запустите скрипт.
3.3. Перезапуск приложения с помощью PM2
PM2 существует уже несколько лет. Эти ребята используют специальный кастомный скрипт, управляющий и запускающий server.js . Он проще в настройке, но обременён другим Node.js- процессом, выступающим в качестве Master-процесса и менеджера для вашей Express.js.
Сначала нужно установить PM2:
Затем запустите приложение, выполнив следующую команду в корневом каталоге Express-проекта:
Флаг -I max гарантирует, что приложение будет запущено в кластерном режиме, создавая столько воркеров, сколько есть ядер у CPU.
4.1. Балансировка нагрузки с помощью кластерного модуля
Встроенный модуль Node.js позволяет создавать рабочие процессы, обслуживающие ваше приложение. Он основан на реализации child_process и прост в настройке. Вам нужно лишь добавить файл cluster.js и вставить в него следующий код:
Когда вы запустите cluster.js c нодой cluster.js , модуль кластера обнаружит, что он работает как Master-процесс и вызовет функцию masterProcess() . Она подсчитает, сколько процессорных ядер имеет сервер, и вызовет cluster.fork() . Все эти процессы выполняются на одном и том же порту. Подробнее об этом читайте в официальном хелпе.
Слушатель событий cluster.on(‘exit’) перезапустит рабочий процесс, если он завершится неудачей.
Теперь отредактируем поле ExecStart в приложении fooapp.service . Замените:
Перезагрузите Systemd и приложение fooapp.service :
Вы добавили балансировку нагрузки в Express-приложение. Это будет работать только для single-server. Если необходимо несколько серверов – используйте Nginx.
4.2. Добавление обратного прокси с помощью Nginx
Одно из основных правил в работе с приложениями Node.js – не вешайте их на порты 80 и 443. Для перенаправления трафика используйте обратный прокси. Nginx – самый распространённый инструмент для достижения этой цели.
Установка Nginx довольно проста, для Ubuntu это будет выглядеть так:
Если у вас другая ОС, ознакомьтесь с инструкциями по установке Nginx.
Nginx должен стартовать сразу же, но на всякий случай проверьте:
Если не запустился – выполните эту команду:
После запуска Nginx необходимо отредактировать конфиг, чтобы включить обратный прокси. Конфиг Nginx находится в каталоге /etc/nginx/ . Основной конфигурационный файл называется nginx.conf , но есть разные дополнения в каталоге etc/nginx/sites-available/ . Конфигурация сервера по умолчанию находится здесь и называется default.
Чтобы включить обратный прокси-сервер, откройте файл конфигурации по умолчанию и отредактируйте его следующим образом:
Сохранитесь и перезапустите службу Nginx:
Эта настройка будет роутить весь трафик с порта 80 на ваше Express-приложение.
4.3. Кэширование в nginx
Кэширование важно для сокращения времени отклика ресурсов, которые редко изменяются.
Откройте дефолтный конфиг и добавьте эти строки кода:
4.4. Gzip
В серверном блоке конфига добавьте следующие строки:
Если нужно больше информации – читайте официальный хелп.
4.5. Включение кэширования на Redis
Redis – in-memory хранилище, которое часто используется в качестве кэша.
Установка на Ubuntu проста:
Откройте файл /etc/redis/redis.conf и измените одну важную строку:
Перезапустите службу Redis:
Затем установите модуль redis для доступа к Redis из приложения:
Теперь вы можете начать кэшировать запросы. Рассмотрим пример:
Этот код будет кэшировать ответ из БД в виде строки JSON в Redis в течение 3600 секунд. Вы можете изменить это в зависимости от требований.
Идея этой статьи состояла в том, чтобы охватить лучшие практики, которых вы должны придерживаться и те, от которых следует держаться подальше.