Middleware — антипаттерн (альтернативная позиция)

Альтернативная позиция (контр-консенсус, 2026): middleware-паттерн (Connect/Express) — антипаттерн в Node.js. Reference pollution, race conditions, неявный shared state, толстые контроллеры.

В чём суть проблемы

  • Контракт (req, res, next) — middleware миксинит свои поля в request/response. Полевая загрязнённость в обе стороны.
  • Reference pollution: ссылки на req/res утекают в разные части приложения, у них нет единого владельца.
  • Shared state на уровне модуля (например, let userId для всего модуля) → конкурентные пользователи перетирают друг друга.
  • Утечка абстракций: непонятно, какая middleware за что отвечает — куки или auth?
  • Порядок критичен: переставил местами — сломалось. Люди запускают legacy-проект методом тыка middleware.
  • Race conditions на одном response: одна middleware пишет в response, другая ещё хочет писать.

Признаки антипаттерна

// shared state на модуле — катастрофа при конкуренции
let userId;
app.use((req, res, next) => { userId = req.session.userId; next; });
app.use((req, res, next) => { checkAccess(userId); next; }); // не тот юзер!

// миксинг полей в req
app.use((req, res, next) => { req.customAuth = doAuth(req); next; });
// в контроллере: что такое req.customAuth? кто положил? когда?

// EventEmitter держит ссылку на response
emitter.on('timeout', () => { if (!res.ended) res.send('timeout'); });
// race: res уже завершён другим путём

Чем заменить

  • NestJS — изолирует от middleware-проблем сервисами/роутингом.
  • Слоистая/гексагональная архитектура — middleware заменяется явными слоями: pipeline-валидации, use cases, репозитории.
  • Контексты вместо shared state — каждый запрос имеет свой immutable context.
  • DDD-подход — бизнес-логика не знает про req/res.

Когда middleware OK

  • Лог-функции, не меняющие состояние (доступ только на чтение).
  • Адаптеры/декораторы из библиотек, не миксящие в req/res.

Антипаттерн (полный пример)

// толстый контроллер на middleware-цепочке
app.post('/order',
  authMiddleware,    // кладёт req.user
  validateMiddleware,// кладёт req.validated
  rateLimit,         // меняет shared rate-limit state
  async (req, res) => {
    if (!req.user) return res.status(401).end(); // дубль auth
    const order = await db.query(`INSERT…`, req.validated);
    await mailer.send(req.user.email, 'Order'); // ещё ответственность
    res.json(order);
  }
);

🎓 Источники

  • 🎓 [Middleware антипаттерн для Node.js в 2026] · 2026-03-13 · YouTube
    • Альтернативная позиция: «На 26-й год middleware в Node.js — сплошной антипаттерн».
    • «Reference pollution в две стороны, race conditions, shared state — это всё опасно. Как только middleware больше одной — конфликты».
    • «Разработчики переставляют middleware местами, чтобы запустился legacy».
  • 🎓 [Middleware антипатерн (українською)] · 2026-03-12 · YouTube
    • Контексты как альтернатива middleware. Nest скрывает middleware за сервисами.

См. также