Understanding the Decorator Pattern
Apply the Decorator Pattern to wrap route handlers, services, and utilities with cross-cutting concerns like logging, tracing, or auth—without touching their core logic.
We'll cover the following...
Why this pattern matters
We’ve all been there: we need to add logging, tracing, caching, auth checks—or all of the above—to an existing function. The logic is solid, but now it has to report metrics, log inputs, and check a token. Suddenly, what was a clean 5-line function turns into a bloated mess.
This problem only worsens in a large codebase. Cross-cutting concerns creep into core business logic. Every route handler starts with the same logging boilerplate. Every service has inline conditionals for permissions or caching. Changes ripple everywhere.
We want a way to wrap behavior—without rewriting the function itself. That’s what the decorator pattern gives us. It lets us wrap a function or method to extend its behavior transparently. And in Node.js, it maps perfectly to functional wrappers, higher-order functions, and even the merging @decorator syntax.
This is clean, composable extensibility—without modifying the core.
How the pattern works
At its core, the Decorator Pattern works as a wrapper around existing functionality. It takes a function, extends its behavior, and returns a new one that works the same but with added functionality.
The basic structure looks like this:
function decorate(originalFn) {return function wrapped(...args) {// do something beforeconst result = originalFn(...args);// do something afterreturn result;};}
Decorators are pure composition. We stack them, ...