Хочешь уверенно проходить IT-интервью?

Мы понимаем, как сложно подготовиться: стресс, алгоритмы, вопросы, от которых голова идёт кругом. Но с AI тренажёром всё гораздо проще.
💡 Почему Т1 тренажёр — это мастхэв?
- Получишь настоящую обратную связь: где затык, что подтянуть и как стать лучше
- Научишься не только решать задачи, но и объяснять своё решение так, чтобы интервьюер сказал: "Вау!".
- Освоишь все этапы собеседования, от вопросов по алгоритмам до диалога о твоих целях.
Зачем листать миллион туториалов? Просто зайди в Т1 тренажёр, потренируйся и уверенно удиви интервьюеров. Мы не обещаем лёгкой прогулки, но обещаем, что будешь готов!
Реклама. ООО «Смарт Гико», ИНН 7743264341. Erid 2VtzqwP8vqy
Как структурировать приложение c Express.js
Наличие интуитивно понятной файловой структуры играет огромную роль – легче добавлять новый функционал и рефакторить код. Подходящий способ структурирования выглядит так:
src/
config/
- конфиги
controllers/
- маршруты и обратные вызовы
providers/
- логика для контроллера маршрутов
services/
- общая бизнес-логика, используемая в функциях провайдера
models/
- модели БД
routes.js
- все маршруты
db.js
- все модели
app.js
- загрузка всего вышеперечисленного
test/
unit/
- unit тесты
integration/
- интеграционные тесты
server.js
- загрузка app.js и прослушивание на порту
(cluster.js)
- загрузка app.js и создание кластер
test.js
- главный тестовый файл, который будет запускать все в каталоге test/
Такая организация позволяет ограничить размер файла примерно до 100 строк, что делает ревью и траблшутинг менее кошмарным делом.
Возьмите за правило отделять и выносить логику в отдельный файл – вы ограничите переключение контекста, которое происходит при чтении одного файла. Это также полезно при мерже в мастер – будет меньше конфликтов слияния.
Чтобы обеспечить соблюдение правил в команде, вы можете настроить линтер, который сообщит, когда вы переходите через установленный лимит строк в файле или если одна строка имеет длину более 100 символов.
1. Как повысить производительность и надёжность Express.js

1.1. Переменная окружения NODE_ENV
Установив переменную
окружения NODE_ENV
, вы примерно трёхкратно увеличите производительность. В терминале это можно
сделать следующим образом:
export NODE_ENV=production
Если вы используйте server.js
,
добавьте следующее:
NODE_ENV=production node server.js
1.2. Включаем Gzip-сжатие
Установите npm-пакет компрессии:
npm i compression
Затем добавьте следующий фрагмент в свой код:
const compression = require('compression')
const express = require('express')
const app = express()
app.use(compression())
1.3. Асинхронные функции
Не блокируйте поток выполнения и не используйте синхронные функции. Вместо этого используйте функции Promises и Async/Await. Если у вас есть доступ только к синхронным функциям, оберните их в async-функцию, которая будет выполняться вне основного потока.
(async () => {
const foo = () => {
...some sync code
return val
}
async const asyncWrapper = (syncFun) => {
const val = syncFun()
return val
}
// значение будет возвращено извне основного потока выполнения
const val = await asyncWrapper(foo)
})()
Если не избежать использования синхронных функций – запустите их в отдельном потоке. Чтобы избежать блокировки основного потока и проседания CPU, создайте дочерние процессы для обработки интенсивных задач процессора.
1.4. Система логов
Чтобы унифицировать
журналы по всему Express.js-приложению, а не использовать console.log()
, используйте агент для централизованного ведения, структурирования и сбора журналов.
Можно использовать любой инструмент управления журналами, например, Sematext, Logz.io или Datadog. Практически все агенты базируются на Winston и Morgan. Они отслеживают трафик запросов API с помощью промежуточного программного обеспечения. Это сразу же даст вам журналы и данные по каждому параметру, что важно для отслеживания производительности.
Вот так добавляется логгер и промежуточное ПО:
/const { stLogger, stHttpLoggerMiddleware } = require('sematext-agent-express')
// В верхней части ваших маршрутов добавьте stHttpLoggerMiddleware для отправки журналов
const express = require('express')
const app = express()
app.use(stHttpLoggerMiddleware)
// Используйте stLogger для отправки всех типов журналов непосредственно в Sematext
app.get('/api', (req, res, next) => {
stLogger.info('An info log.')
stLogger.debug('A debug log.')
stLogger.warn('A warning log.')
stLogger.error('An error log.')
res.status(200).send('Hello World.')
})

1.5. Обработка ошибок и исключений
При использовании в
коде Async/Await
для обработки ошибок и исключений рекомендуется применять операторы try-catch
, а также использовать для ведения журнала ошибок Express logger.
async function foo() {
try {
const baz = await bar()
return baz
} catch (err) {
stLogger.error('Function \'bar\' threw an exception.', err);
}
}
Кроме того,
рекомендуется настроить catch-all error
:
function errorHandler(err, req, res, next) {
stLogger.error('Catch-All error handler.', err)
res.status(err.status || 500).send(err.message)
}
router.use(errorHandler)
module.exports = router
Здесь будет поймана любая ошибка, выброшенная в контроллере. А ещё можно добавить слушателей в сам процесс:
process.on('uncaughtException', (err) => {
stLogger.error('Uncaught exception', err)
throw err
})
process.on('unhandledRejection', (err) => {
stLogger.error('unhandled rejection', err)
})
1.6. Следите за утечками памяти
Вы не сможете поймать ошибки до того, как они произойдут. Некоторые проблемы не обусловлены только «тематикой» исключения, вываливающегося при падении приложения. Все решения сводятся к тому, чтобы упредить любую возможность утечки памяти.
Заметить утечку гораздо проще, чем вы думаете. Если память процесса продолжает неуклонно расти, а не периодически сокращается в результате сборки мусора – скорее всего это она и есть. В идеале вы должны сосредоточиться на предотвращении утечек, а не на их устранении и отладке.
Добавьте в Express-приложение сборщик метрик, который будет хранить их в одном месте. Это поможет проанализировать данные и добраться до основной причины.

В чем прелесть – это
всего лишь одна строка кода. Добавьте ее в файлик app.js
.
const { stMonitor, stLogger, stHttpLoggerMiddleware } =
require('sematext-agent-express')
stMonitor.start() // запуск метода .start в stMonitor
// В верхней части ваших маршрутов добавьте stHttpLoggerMiddleware для отправки журналов
const express = require('express')
const app = express()
app.use(stHttpLoggerMiddleware)
...
Благодаря этому вы получите доступ к нескольким информационным панелям, дающим ключевое представление о том, что происходит с вашим Express-приложением. Данные можно фильтровать и группировать для визуализации процессов, памяти, использования CPU и HTTP-запросов/ответов. Что вы должны сделать сразу же – настроить оповещения об изменениях в работе софта.
Двигаемся дальше от Express.js к конкретным советам и рекомендациям по JavaScript и о том, как его использовать оптимизированным и надежным способом.
2. Как настроить окружение JavaScript

2.1. Чистые функции
Чистые функции – это функции, которые не изменяют внешнее состояние. Они принимают параметры, что-то делают с ними и возвращают значение.
Вместо использования
var
, применяйте только const
и полагайтесь на чистые функции для создания новых
объектов вместо изменения существующих. Это связано с использованием функций высокого
порядка в JavaScript, например .map()
, .reduce()
, .filter()
и т. д.
2.2. Параметры объекта
JavaScript – слабо типизированный язык. В вызов функции может быть передан один или несколько параметров. Даже если объявление функции имеет фиксированное число определенных аргументов. Этот огрех можно решить, используя объекты в качестве параметров функции.
const foo = ({ param1, param2, param3 }) => {
if (!(param1 && param2 && param3)) {
throw Error('Invalid parameters in function: foo.')
}
const sum = param1 + param2 + param3
return sum
}
foo({ param1: 5, param2: 345, param3: 98 })
foo({ param2: 45, param3: 57, param1: 81 })
Все эти вызовы функций будут работать одинаково. Вы можете принудительно указать имена параметров, при этом вы не связаны порядком, что значительно упрощает управление.
2.3. Тестирование
Используйте что-нибудь простое, например, Mocha и Chai. Mocha – это фреймворк для тестирования, а Chai – assertion библиотека.
Установите npm пакеты:
npm i mocha chai
Давайте потестим
функцию. В test.js
добавьте следующее:
const chai = require('chai')
const expect = chai.expect
const foo = require('./src/foo')
describe('foo', function () {
it('should be a function', function () {
expect(foo).to.be.a('function')
})
it('should take one parameter', function () {
expect(
foo.bind(null, { param1: 5, param2: 345, param3: 98 }))
.to.not.throw(Error)
})
it('should throw error if the parameter is missing', function () {
expect(foo.bind(null, {})).to.throw(Error)
})
it('should throw error if the parameter does not have 3 values', function () {
expect(foo.bind(null, { param1: 4, param2: 1 })).to.throw(Error)
})
it('should return the sum of three values', function () {
expect(foo({ param1: 1, param2: 2, param3: 3 })).to.equal(6)
})
})
Добавьте это в package.json
:
"scripts": {
"test": "mocha"
}
Теперь запустим тесты, выполнив следующую команду в терминале:
npm test
Выведется примерно такое:
> test-mocha@1.0.0 test /path/to/your/expressjs/project
> mocha
foo
✓ should be a function
✓ should take one parameter
✓ should throw error if the parameter is missing
✓ should throw error if the parameter does not have 3 values
✓ should return the sum of three values
5 passing (6ms)
3. Использование DevOps-инструментов
3.1. Управление переменными среды в Node.js с dotenv
Dotenv – это модуль
npm, позволяющий загружать переменные среды в любое Node.js приложение. В корне вашего проекта
создайте .env
файл. Здесь вы добавите все необходимые переменные окружения.
NODE_ENV=production
DEBUG=false
LOGS_TOKEN=xxx-yyy-zzz
MONITORING_TOKEN=xxx-yyy-zzz
INFRA_TOKEN=xxx-yyy-zzz
...
Загрузка файл проста. В верхней части app.js
разместите dotenv:
// dotenv вверху
require('dotenv').config()
// другие агенты
const { stLogger, stHttpLoggerMiddleware } = require('sematext-agent-express')
// требуется express и создать экземпляр приложения
const express = require('express')
const app = express()
app.use(stHttpLoggerMiddleware)
...
Dotenv по умолчанию загружает файл с
именем .env
. При необходимости прочитайте руководство по настройке нескольких dotenv-файлов.
3.2. Перезапуск приложения с помощью Systemd
Systemd – часть строительных блоков ОС Linux. Он запускает и управляет системными процессами. Вам нужно запустить Node.js процесс, как системную службу, чтобы он восстанавливался после сбоев.
На виртуальной машине
или сервере создайте новый файл в разделе /lib/systemd/system/
:
# /lib/systemd/system/fooapp.service
[Unit]
Description=Node.js as a system service.
Documentation=https://example.com
After=network.target
[Service]
Type=simple
User=ubuntu
ExecStart=/usr/bin/node /path/to/your/express/project/server.js
Restart=on-failure
[Install]
WantedBy=multi-user.target
Две важные строки в
этом файле – ExecStart
и Restart. ExecStart
запустит ваш server.js
с помощью
бинарника /usr/bin/node
(обязательно проверяйте абсолютный путь к файлу server.js
).
Функция Restart=on-failure
перезапустит приложение, если оно «обвалится».
После сохранения fooapp.service
,
перезагрузите демона и запустите скрипт.
systemctl daemon-reload
systemctl start fooapp
systemctl enable fooapp
systemctl status fooapp
3.3. Перезапуск приложения с помощью PM2
PM2
существует уже несколько лет. Эти ребята используют специальный кастомный скрипт, управляющий
и запускающий server.js
. Он проще в настройке, но обременён другим Node.js- процессом, выступающим в качестве Master-процесса
и менеджера для вашей Express.js.
Сначала нужно установить PM2:
npm i -g pm2
Затем запустите приложение, выполнив следующую команду в корневом каталоге Express-проекта:
pm2 start server.js -i max
Флаг -I max
гарантирует, что приложение будет запущено в кластерном режиме, создавая
столько воркеров, сколько есть ядер у CPU.
4. Балансировка нагрузки и обратный прокси
4.1. Балансировка нагрузки с помощью кластерного модуля
Встроенный модуль Node.js
позволяет создавать рабочие процессы, обслуживающие ваше приложение. Он основан
на реализации child_process
и прост в настройке. Вам нужно лишь добавить файл cluster.js
и вставить в него
следующий код:
const cluster = require('cluster')
const numCPUs = require('os').cpus().length
const app = require('./src/app')
const port = process.env.PORT || 3000
const masterProcess = () => Array.from(Array(numCPUs)).map(cluster.fork)
const childProcess = () => app.listen(port)
if (cluster.isMaster) {
masterProcess()
} else {
childProcess()
}
cluster.on('exit', () => cluster.fork())
Когда вы запустите
cluster.js
c нодой cluster.js
, модуль кластера обнаружит, что он работает как
Master-процесс и вызовет функцию masterProcess()
. Она
подсчитает, сколько процессорных ядер имеет сервер, и вызовет cluster.fork()
. Все
эти процессы выполняются на одном и том же порту. Подробнее об этом читайте в официальном хелпе.
Слушатель событий
cluster.on('exit')
перезапустит рабочий процесс, если он завершится неудачей.
Теперь отредактируем
поле ExecStart
в приложении fooapp.service
. Замените:
ExecStart=/usr/bin/node /path/to/your/express/project/server.js
На следующее:
ExecStart=/usr/bin/node /path/to/your/express/project/cluster.js
Перезагрузите Systemd и
приложение fooapp.service
:
systemctl daemon-reload
systemctl restart fooapp
Вы добавили балансировку нагрузки в Express-приложение. Это будет работать только для single-server. Если необходимо несколько серверов – используйте Nginx.
4.2. Добавление обратного прокси с помощью Nginx
Одно из основных правил в работе с приложениями Node.js – не вешайте их на порты 80 и 443. Для перенаправления трафика используйте обратный прокси. Nginx – самый распространённый инструмент для достижения этой цели.
Установка Nginx довольно проста, для Ubuntu это будет выглядеть так:
apt update
apt install nginx
Если у вас другая ОС, ознакомьтесь с инструкциями по установке Nginx.
Nginx должен стартовать сразу же, но на всякий случай проверьте:
systemctl status nginx
[Output]
nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2018-04-20 16:08:19
UTC; 3 days ago
Docs: man:nginx(8)
Main PID: 2369 (nginx)
Tasks: 2 (limit: 1153)
CGroup: /system.slice/nginx.service
├─2369 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
└─2380 nginx: worker process
Если не запустился – выполните эту команду:
systemctl start nginx
После запуска Nginx необходимо отредактировать конфиг, чтобы включить обратный прокси. Конфиг Nginx
находится в каталоге /etc/nginx/
. Основной конфигурационный файл называется
nginx.conf
, но есть разные дополнения в каталоге etc/nginx/sites-available/
.
Конфигурация сервера по умолчанию находится здесь и называется default.
Чтобы включить обратный прокси-сервер, откройте файл конфигурации по умолчанию и отредактируйте его следующим образом:
server {
listen 80;
location / {
proxy_pass http://localhost:3000; #change the port if needed
}
}
Сохранитесь и перезапустите службу Nginx:
systemctl restart nginx
Эта настройка будет роутить весь трафик с порта 80 на ваше Express-приложение.
4.3. Кэширование в nginx
Кэширование важно для сокращения времени отклика ресурсов, которые редко изменяются.
Отредактируйте nginx.conf
:
http {
upstream fooapp {
server localhost:3000;
server domain2;
server domain3;
...
}
...
}
Откройте дефолтный конфиг и добавьте эти строки кода:
server {
listen 80;
location / {
proxy_pass http://fooapp;
}
}
4.4. Gzip
В серверном блоке конфига добавьте следующие строки:
server {
gzip on;
gzip_types text/plain application/xml;
gzip_proxied no-cache no-store private expired auth;
gzip_min_length 1000;
...
}
Если нужно больше информации – читайте официальный хелп.
4.5. Включение кэширования на Redis
Redis – in-memory хранилище, которое часто используется в качестве кэша.
Установка на Ubuntu проста:
apt update
apt install redis-server
Откройте файл /etc/redis/redis.conf
и измените одну
важную строку:
supervised no
на следующее:
supervised systemd
Перезапустите службу Redis:
systemctl restart redis
systemctl status redis
[Output]
● redis-server.service - Advanced key-value store
Loaded: loaded (/lib/systemd/system/redis-server.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2018-06-27 18:48:52
UTC; 12s ago
Docs: http://redis.io/documentation,
man:redis-server(1)
Process: 2421 ExecStop=/bin/kill -s TERM $MAINPID (code=exited, status=0/SUCCESS)
Process: 2424 ExecStart=/usr/bin/redis-server
/etc/redis/redis.conf (code=exited, status=0/SUCCESS)
Main PID: 2445 (redis-server)
Tasks: 4 (limit: 4704)
CGroup: /system.slice/redis-server.service
└─2445 /usr/bin/redis-server 127.0.0.1:6379
Затем установите модуль redis для доступа к Redis из приложения:
npm i redis
Теперь вы можете начать кэшировать запросы. Рассмотрим пример:
const express = require('express')
const app = express()
const redis = require('redis')
const redisClient = redis.createClient(6379)
async function getSomethingFromDatabase (req, res, next) {
try {
const { id } = req.params;
const data = await database.query()
// Установим данные в Redis
redisClient.setex(id, 3600, JSON.stringify(data))
res.status(200).send(data)
} catch (err) {
console.error(err)
res.status(500)
}
}
function cache (req, res, next) {
const { id } = req.params
redisClient.get(id, (err, data) => {
if (err) {
return res.status(500).send(err)
}
// Если данные существуют вернем кэшированное значение
if (data != null) {
return res.status(200).send(data)
}
// Если данные не существуют, идем в функцию getSomethingFromDatabase
next()
})
}
app.get('/data/:id', cache, getSomethingFromDatabase)
app.listen(3000, () => console.log(`Server running on Port ${port}`))
Этот код будет кэшировать ответ из БД в виде строки JSON в Redis в течение 3600 секунд. Вы можете изменить это в зависимости от требований.
Заключение
Идея этой статьи состояла в том, чтобы охватить лучшие практики, которых вы должны придерживаться и те, от которых следует держаться подальше.
Надеемся, что вам понравилось. Удачи в обучении!
Комментарии