Что такое middleware

Middleware — функция, которая видит запрос до обработчика и решает: обработать, изменить или передать дальше через next().
«Запрос в Express проходит конвейер: каждая станция-middleware что-то делает и зовёт следующую.»

Middleware — вторая по важности идея Express после маршрутов. Это функция, которая встраивается в поток обработки запроса между приёмом и ответом. Логирование, разбор тела, проверка авторизации, обработка ошибок — всё это middleware. Понять их — значит понять, как Express устроен на самом деле.

Анатомия middleware

Middleware — это функция трёх аргументов: (req, res, next). Она может что-то сделать с запросом или ответом, а затем либо отправить ответ, либо вызвать next(), чтобы передать управление дальше:

// простейший логгер
function logger(req, res, next) {
  console.log(`${req.method} ${req.url}`);
  next();  // передаём управление следующему
}

app.use(logger);  // применить ко всем запросам

app.get('/', (req, res) => {
  res.send('главная');
});

Ключевой момент: если middleware не вызовет next() и не отправит ответ, запрос зависнет навсегда. next() — это эстафетная палочка.

Конвейер запроса

Каждый запрос проходит цепочку middleware по порядку, пока кто-то не отправит ответ:

запрос
  |
  v
[ logger ] --next()--> [ express.json ] --next()--> [ auth ]
                                                       |
                                          next()       v
                                       [ обработчик маршрута ]
                                                       |
                                                       v
                                                    ОТВЕТ

Собираем конвейер в браузере

Реализуем цепочку middleware как массив функций. Каждая вызывает next, чтобы передать управление дальше — ровно как в Express:

function runPipeline(req, middlewares) {
  let i = 0;
  function next() {
    const mw = middlewares[i++];
    if (mw) mw(req, next);
  }
  next();
}

const logger = (req, next) => { console.log('LOG', req.url); next(); };
const addUser = (req, next) => { req.user = 'Аня'; next(); };
const handler = (req, next) => { console.log('Привет,', req.user); };

runPipeline({ url: '/home' }, [logger, addUser, handler]);
// LOG /home
// Привет, Аня

Обрати внимание: addUser дописал свойство в req, и обработчик его увидел. Так middleware обогащают запрос данными (например, авторизованным пользователем).

Как работает под капотом

Express хранит все middleware и маршруты в одном стеке слоёв. На каждый запрос он создаёт функцию next, которая берёт следующий слой из стека и вызывает его. Когда слой вызывает next(), цикл продолжается; когда отправляет ответ — цикл естественно завершается, потому что next больше никто не дёргает. Именно поэтому порядок подключения middleware критичен.

Частые ошибки

  • Забыть next(). Запрос повиснет: ни ответа, ни передачи дальше.
  • Вызвать next() после ответа. Если ты уже отправил res.send(), лишний next() может привести к ошибке "headers already sent".
  • Неверный порядок. Middleware, который должен видеть тело, обязан стоять после express.json().

Best practices

  • Делай middleware маленькими и с одной задачей: лог, парсинг, проверка.
  • Подключай глобальные middleware через app.use() в начале файла.
  • Всегда либо вызывай next(), либо отправляй ответ — но не оба сразу.

Итоги

Middleware — функция (req, res, next), звено конвейера обработки запроса. Она может изменить запрос, отправить ответ или передать эстафету через next(). Дальше научимся писать собственные middleware и привязывать их не только глобально, но и к конкретным маршрутам.

req и res живут весь запрос

Объекты req и res создаются один раз на запрос и проходят через всю цепочку middleware. Это делает их идеальным местом для передачи данных между звеньями: middleware аутентификации кладёт в req.user распознанного пользователя, и любой следующий обработчик его видит. Важно не злоупотреблять: дописывай в req только то, что действительно относится к запросу, и избегай конфликтов имён с тем, что уже кладёт Express. Хорошее правило — складывать своё в один вложенный объект, например req.context = {}, чтобы не затирать стандартные свойства и видеть, что именно добавило приложение.

Проверьте себя
1. Какова сигнатура обычного middleware в Express?
A(req, res)
B(req, res, next)
C(err, req, res)
D(next, req)
2. Что произойдёт, если middleware не вызовет next() и не отправит ответ?
AExpress вызовет next() сам
BЗапрос зависнет навсегда
CСервер вернёт 500
DПерейдёт к следующему маршруту автоматически