Express.js middleware

Понятие middleware и место в жизненном цикле запроса

Middleware в Express.js представляет собой функции промежуточной обработки, которые получают доступ к объектам запроса (req), ответа (res) и функции перехода к следующему обработчику (next). Эти функции формируют цепочку, через которую проходит каждый HTTP-запрос внутри приложения.

Каждый middleware может:

  • завершить обработку запроса, отправив ответ клиенту;
  • модифицировать req или res;
  • передать управление следующему обработчику через next();
  • обработать ошибку, если она передана в next(err).

Жизненный цикл запроса в Express строится как последовательность вызовов middleware, что позволяет гибко разделять ответственность между компонентами приложения.


Типы middleware

Приложенческие middleware

Применяются через app.use() и работают для всех маршрутов или для маршрутов с указанным префиксом.

app.use((req, res, next) => {
  console.log('Запрос:', req.method, req.url);
  next();
});

Такие middleware часто используются для логирования, парсинга тела запроса, установки заголовков и других глобальных задач.


Маршрутные middleware

Привязываются к конкретным маршрутам и выполняются только при совпадении пути.

app.get('/users', (req, res, next) => {
  console.log('Обработка /users');
  next();
}, (req, res) => {
  res.send('Список пользователей');
});

Подобная структура позволяет разделять обработку на логические этапы внутри одного маршрута.


Ошибочные middleware

Middleware, предназначенные для обработки ошибок, отличаются сигнатурой: они принимают четыре аргумента.

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Ошибка сервера');
});

Такие функции активируются только при вызове next(err).


Встроенные middleware

Express.js предоставляет ряд встроенных middleware:

  • express.json() — обработка JSON-тела запроса;
  • express.urlencoded() — обработка form-data;
  • express.static() — раздача статических файлов.
app.use(express.json());
app.use(express.static('public'));

Сторонние middleware

Экосистема Express активно использует внешние пакеты, которые подключаются как обычные функции middleware.

Примеры типичных задач:

  • логирование запросов;
  • защита от атак;
  • сжатие ответов;
  • обработка CORS.

Механизм цепочки middleware

Каждый запрос проходит через цепочку функций, определённую порядком регистрации middleware. Порядок имеет критическое значение: изменение последовательности может радикально изменить поведение приложения.

app.use((req, res, next) => {
  console.log('Первый middleware');
  next();
});

app.use((req, res, next) => {
  console.log('Второй middleware');
  next();
});

Если один из middleware не вызывает next() и не отправляет ответ, дальнейшая обработка останавливается.


Работа функции next()

Функция next() управляет переходом между middleware.

Основные сценарии:

  • next() — переход к следующему middleware;
  • next('route') — пропуск оставшихся middleware текущего маршрута;
  • next(err) — переход к обработчику ошибок.
app.use((req, res, next) => {
  if (!req.headers.authorization) {
    return next('route');
  }
  next();
});

Middleware и маршрутизация

Middleware тесно связаны с системой маршрутов. Каждый маршрут может рассматриваться как цепочка middleware-функций, выполняемых последовательно.

app.get('/profile',
  authMiddleware,
  loadUser,
  (req, res) => {
    res.json(req.user);
  }
);

Такой подход позволяет разделять бизнес-логику на независимые этапы.


Асинхронные middleware

В современных приложениях часто используются асинхронные операции (работа с базой данных, внешними API). Middleware могут быть асинхронными функциями.

app.use(async (req, res, next) => {
  try {
    req.user = await getUserFromDB();
    next();
  } catch (err) {
    next(err);
  }
});

Важно корректно передавать ошибки в next, иначе они не попадут в error-handling middleware.


Обработка ошибок в цепочке middleware

Ошибки в Express распространяются по специальному пути. Как только вызывается next(err), управление передаётся ближайшему middleware с четырьмя параметрами.

app.use((req, res, next) => {
  throw new Error('Ошибка обработки');
});

app.use((err, req, res, next) => {
  res.status(500).json({ message: err.message });
});

Middleware без err-параметра игнорируются при обработке ошибок.


Порядок регистрации middleware

Порядок регистрации определяет поведение всей системы:

  • глобальные middleware регистрируются первыми;
  • маршрутные middleware идут после них;
  • обработчики ошибок — в самом конце.

Нарушение порядка приводит к некорректной обработке запросов или невозможности перехвата ошибок.


Практические паттерны использования middleware

Аутентификация

Middleware проверяет наличие и валидность токена.

function auth(req, res, next) {
  if (!req.token) {
    return res.status(401).send('Unauthorized');
  }
  next();
}

Авторизация

Определяет права доступа после аутентификации.

function role(roleName) {
  return (req, res, next) => {
    if (req.user.role !== roleName) {
      return res.status(403).send('Forbidden');
    }
    next();
  };
}

Логирование

Фиксация всех входящих запросов.

app.use((req, res, next) => {
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
  next();
});

Валидация данных

Middleware может проверять входные данные до передачи в бизнес-логику.

function validateUser(req, res, next) {
  if (!req.body.name) {
    return res.status(400).send('Name required');
  }
  next();
}

Композиция middleware

Middleware можно комбинировать, формируя переиспользуемые цепочки.

const userPipeline = [
  auth,
  loadUser,
  validateUser
];

app.post('/user', userPipeline, (req, res) => {
  res.send('OK');
});

Такой подход повышает модульность и упрощает поддержку кода.


Ошибки проектирования middleware

Частые проблемы:

  • отсутствие next() → зависание запроса;
  • некорректная обработка ошибок;
  • нарушение порядка middleware;
  • дублирование логики в разных слоях;
  • смешение ответственности (например, бизнес-логика внутри middleware).

Производительность middleware-цепочек

Каждый middleware добавляет накладные расходы. В высоконагруженных системах важно:

  • минимизировать количество глобальных middleware;
  • избегать тяжёлых синхронных операций;
  • кешировать результаты;
  • использовать условное выполнение middleware.
app.use((req, res, next) => {
  if (req.method !== 'GET') return next();
  // лёгкая логика только для GET
  next();
});