One route handled validation, mapping, and side effects
PATCH /orders/:id grew every sprint. It validated input, recalculated tax, wrote audit logs, and fired webhooks.
Tests needed six mocks to assert one branch. Nobody wanted to touch it.
Shipping through the handler
Feature pressure was high. The handler was the fastest place to ship. The cost showed up in regressions.
Six mocks for one branch
When a route cannot be described in one sentence, it is doing too many jobs.
Failures were hard to classify because layers were fused together.
One job per route
Validate at the edge. Keep handlers thin. Let services own rules and repositories own persistence.
One job per endpoint makes tests small and failures obvious.
Thin handler shape
async updateStatus(id: string, dto: StatusDto) {
const order = await this.orders.find(id);
const next = this.statusRules.apply(order, dto.status);
await this.orders.save(next);
await this.events.publish('order.status_changed', { id });
}Split before conditionals win
If you cannot describe the endpoint in one sentence, split it before the conditionals win.