Middleware Pattern (антипаттерн по автору, 2026)
Цепочка обработчиков, мутирующих общий req/res. Похоже на Chain of Responsibility, но без концентрации ответственности. Согласно автору — антипаттерн для Node.js в 2026 году.
Проблема (которую middleware пытается решить)
Express/Connect: запрос проходит через серию обработчиков. Каждый что-то делает (логирование, auth, body parsing, validation). В конце цепочки — controller. Middleware добавляет сквозную функциональность (логирование, аутентификацию, валидацию, кэширование) без изменения основного кода.
app.use(logger);
app.use(auth);
app.use(bodyParser);
app.get('/users', controller);
Где исторически используется
- HTTP-серверы: Express, Koa, Fastify
- Redux:
applyMiddlewareдля side-эффектов (redux-thunk, redux-saga) - Валидация и трансформация данных в пайплайне
- Логирование и мониторинг запросов
- Аутентификация и авторизация
- GraphQL middleware
Базовая реализация цепочки
class Pipeline {
constructor { this.middlewares = ; }
use(fn) { this.middlewares.push(fn); return this; }
execute(context) {
let index = -1;
const next = (i) => {
if (i <= index) throw new Error('next called multiple times');
index = i;
const fn = this.middlewares[i];
if (!fn) return;
fn(context, () => next(i + 1));
};
next(0);
return context;
}
}
const pipeline = new Pipeline();
pipeline
.use((ctx, next) => { ctx.value *= 2; next; })
.use((ctx, next) => { ctx.value += 10; next; });
pipeline.execute({ value: 5 }); // value: 20
Express-стиль
class MiniExpress {
constructor { this.stack = ; }
use(fn) { this.stack.push(fn); }
handle(req, res) {
let idx = 0;
const next = (err) => {
if (err) return console.error('Error:', err);
const fn = this.stack[idx++];
if (fn) fn(req, res, next);
};
next;
}
}
const app = new MiniExpress();
app.use((req, res, next) => { console.log(`${req.method} ${req.url}`); next; });
app.use((req, res, next) => {
if (!req.headers.authorization) { res.status = 401; return; }
next;
});
app.use((req, res, next) => { res.body = 'OK'; res.status = 200; });
Redux-стиль (currying)
const logger = store => next => action => {
console.log('Action:', action.type);
return next(action);
};
const thunk = store => next => action =>
typeof action === 'function'
? action(store.dispatch, store.getState)
: next(action);
Почему это антипаттерн (по автору)
1. Reference pollution
Middleware мутируют req и res:
function authMw(req, res, next) {
req.user = await loadUser(req.headers.token); // мутация req
sockets.add(req.socket); // утечка ссылки в коллекцию
next;
}
Ссылки на req/res утекают в разные части приложения. Где-то ещё могут писать в req.socket — race conditions.
2. Shared state и неявное зацепление
Все middleware зацеплены друг за друга через общий мутабельный state (req, res). Зацепление высокое, неявное, опасное. Одна middleware ничего, а десятки — конфликтуют.
3. Зависимость от порядка
Перепутал порядок app.use(...) — проект не запускается. Авторы middleware не согласовали поведение между собой.
4. Размытые границы ответственности
Кто отвечает за auth? Один middleware или другой? Кто проверяет cookies — этот или вон тот? Утечки абстракции.
5. Невозможность изолированно тестировать
Middleware не работают в одиночку. Нужно запускать всю цепочку с реальным req/res.
6. Другие частые ошибки
- Забыть вызвать
next— цепочка обрывается молча. - Изменение
req/resпослеnext— состояние несогласованно. - Слишком длинная цепочка — сложно отлаживать.
Правильные альтернативы
Chain of Responsibility
// Каждый обработчик САМ решает: моё или нет
class AuthHandler {
canHandle(req) { return req.path.startsWith('/api'); }
handle(req) { return { user: this.authenticate(req) }; }
}
Один обработчик берёт ответственность, остальные не мутируют общее состояние.
Context Pattern
class RequestContext {
#req; #res;
constructor(req, res) { this.#req = req; this.#res = res; }
getHeader(name) { return this.#req.headers[name]; }
setBody(body) { /* безопасная запись */ }
}
Стримы недоступны напрямую. Любая операция оставляет контекст в консистентном состоянии.
NestJS-стиль
@Guard, @Pipe, @Interceptor — каждый имеет чёткую фазу и ответственность. Не мутируют общий объект.
Главные тезисы автора
- «Middleware приводит к высокому зацеплению» через shared state.
- «Reference pollution: ссылки на request/response утекают».
- «Они неявно зацепляются через шареный стейт» — порядок становится критичен.
- «Одна middleware ничего — десятки конфликтуют».
- «Проект запускается перестановкой middleware» — типичный симптом.
- «Chain of Responsibility безопаснее middleware» — есть концентрация ответственности.
- «Если поверх Express вы используете Nest — вы изолированы от этих проблем».
- Context Pattern — правильное решение: req/res в приватных полях, безопасный API наружу.
- Metarhia metacom — пример системы без middleware-антипаттерна.
🎓 Источники
- 🎓 Middleware это антипаттерн для Node.js в 2026 · 2026-03-13
- Reference pollution
- Shared state и порядок middleware
- Размытые границы ответственности
- Context Pattern как альтернатива
- 🎓 Middleware антипатерн для Node.js у 2026 · 2026-03-12 (укр)
- 🎓 GoF Patterns Обзор всех паттернов · 2025-04-29
- refactoring.guru — Chain of Responsibility