# How to Add Custom Decorators for Middleware Functionality in TypeScript

> Learn how to add custom decorators for middleware functionality in TypeScript. This guide shows you how to wrap method descriptors with cross-cutting logic for logging, authentication, and more.

- Repository: [Lucas Zanoni⠀⠀⠀⠀⠀ ⠀╱|、 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ (˚ˎ 。7 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |、˜〵 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ じしˍ,)ノ/tcc](https://github.com/castrozan/tcc)
- Tags: how-to-guide
- Published: 2026-02-27

---

**You can add custom decorators for middleware functionality by creating higher-order functions that intercept method descriptors, wrap the original implementation with cross-cutting logic like logging or authentication, and return the modified descriptor while preserving the `this` context.**

The `castrozan/tcc` repository (Instagit) demonstrates a robust decorator-based middleware pattern for HTTP controllers that extend `OpenAPIRoute`. This architecture allows you to inject reusable concerns—such as error handling, request logging, and authentication—into controller methods without cluttering business logic. Understanding how to create custom decorators for middleware functionality enables you to extend this pattern for any application-specific cross-cutting concern.

## Understanding the Decorator-Based Middleware Pattern

The core pattern relies on TypeScript's method decorators, which receive three arguments: the target prototype, the property key, and the property descriptor. In [`professionals-dummy-app/src/presentation/decorators/handleErrors.ts`](https://github.com/castrozan/tcc/blob/main/professionals-dummy-app/src/presentation/decorators/handleErrors.ts), the `withErrorHandling` decorator captures the original method from `descriptor.value` and replaces it with a wrapper function that implements middleware logic.

This wrapper executes before and after the original method, enabling pre-processing (validation, logging) and post-processing (error transformation, response formatting). The pattern preserves the original method's signature while injecting reusable behavior, making it ideal for middleware functionality in controller classes extending `OpenAPIRoute`.

## Creating a Custom Decorator for Middleware Functionality

### Step 1 - Define the Higher-Order Function

Create a function that accepts the standard decorator arguments: `target`, `propertyKey`, and `descriptor`. The function must return a `PropertyDescriptor` to properly modify the method behavior. Place your decorator in a dedicated file, such as [`src/presentation/decorators/logRequest.ts`](https://github.com/castrozan/tcc/blob/main/src/presentation/decorators/logRequest.ts), following the project's organizational structure.

### Step 2 - Wrap the Original Method

Inside the decorator, store a reference to `descriptor.value` (the original method). Then assign a new function to `descriptor.value` that implements your middleware logic. This new function executes when the decorated method is called, allowing you to intercept arguments and modify return values.

### Step 3 - Preserve Context and Async Behavior

Use `originalMethod.apply(this, args)` to maintain the correct `this` context (the controller instance). If the original method is asynchronous, declare the wrapper as `async` and `await` the original call. This ensures that middleware functionality works correctly with both sync and async controller methods, as implemented in [`professionals-dummy-app/src/presentation/controllers/professionals/CreateProfessionalController.ts`](https://github.com/castrozan/tcc/blob/main/professionals-dummy-app/src/presentation/controllers/professionals/CreateProfessionalController.ts).

## Practical Examples of Custom Middleware Decorators

### Logging Decorator

Implement request logging by creating a decorator that records method entry, arguments, execution time, and exit. This example demonstrates the pattern used in the repository for cross-cutting concerns:

```typescript
// src/presentation/decorators/logRequest.ts
export function logRequest(
  target: object,
  propertyKey: string,
  descriptor: PropertyDescriptor
): PropertyDescriptor {
  const original = descriptor.value;

  descriptor.value = async function (...args: unknown[]) {
    const start = Date.now();
    console.log(`[${new Date().toISOString()}] → ${propertyKey}`, { args });

    try {
      const result = await original.apply(this, args);
      console.log(
        `[${new Date().toISOString()}] ← ${propertyKey} (took ${Date.now() - start}ms)`,
        { result }
      );
      return result;
    } catch (err) {
      console.error(
        `[${new Date().toISOString()}] ✖ ${propertyKey} (took ${Date.now() - start}ms)`,
        { error: err }
      );
      throw err;
    }
  };

  return descriptor;
}

```

**Usage in a controller:**

```typescript
import { withErrorHandling } from 'presentation/decorators';
import { logRequest } from 'presentation/decorators/logRequest';

export class UpdateProfessionalController extends OpenAPIRoute {
  @logRequest
  @withErrorHandling
  async handle(): Promise<object> {
    // business logic
    return { success: true };
  }
}

```

### Authentication Decorator

Add JWT verification to protect specific controller methods. This decorator checks for valid tokens before executing the original method, similar to the error-handling pattern in [`handleErrors.ts`](https://github.com/castrozan/tcc/blob/main/handleErrors.ts):

```typescript
// src/presentation/decorators/requireAuth.ts
import { z } from 'zod';

export function requireAuth(
  target: object,
  propertyKey: string,
  descriptor: PropertyDescriptor
): PropertyDescriptor {
  const original = descriptor.value;

  descriptor.value = async function (this: any, ...args: unknown[]) {
    const request = await this.getRequest();
    const authHeader = request.headers['authorization'];

    if (!authHeader) {
      return { 
        success: false, 
        message: 'Missing Authorization header', 
        statusCode: 401 
      };
    }

    const token = authHeader.replace('Bearer ', '');
    try {
      const payload = z.object({ sub: z.string() }).parse(
        JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString())
      );
      this.user = payload;
    } catch {
      return { 
        success: false, 
        message: 'Invalid or expired token', 
        statusCode: 401 
      };
    }

    return original.apply(this, args);
  };

  return descriptor;
}

```

**Usage:**

```typescript
import { withErrorHandling } from 'presentation/decorators';
import { requireAuth } from 'presentation/decorators/requireAuth';

export class DeleteEquipmentController extends OpenAPIRoute {
  @requireAuth
  @withErrorHandling
  async handle(): Promise<object> {
    // this.user is now populated with JWT payload
    return { success: true, deletedBy: this.user.sub };
  }
}

```

### Chaining Multiple Decorators

Stack decorators to combine middleware functionality. When multiple decorators are applied to a single method, they execute from bottom to top (the decorator closest to the method runs first):

```typescript
export class FindAllEquipmentController extends OpenAPIRoute {
  @logRequest
  @requireAuth
  @withErrorHandling
  async handle(): Promise<object> {
    // Execution order:
    // 1. withErrorHandling (innermost)
    // 2. requireAuth
    // 3. logRequest (outermost)
    // 4. original handle method
    return { success: true, data: [] };
  }
}

```

## Key Implementation Details from the Source Code

The `castrozan/tcc` repository implements this pattern consistently across multiple applications. The `withErrorHandling` decorator in [`professionals-dummy-app/src/presentation/decorators/handleErrors.ts`](https://github.com/castrozan/tcc/blob/main/professionals-dummy-app/src/presentation/decorators/handleErrors.ts) serves as the reference implementation, wrapping controller methods to catch errors and return standardized error responses.

Controllers such as `CreateProfessionalController` in [`professionals-dummy-app/src/presentation/controllers/professionals/CreateProfessionalController.ts`](https://github.com/castrozan/tcc/blob/main/professionals-dummy-app/src/presentation/controllers/professionals/CreateProfessionalController.ts) and `CreateEquipmentController` in [`equipments-dummy-app/src/presentation/controllers/equipments/CreateEquipmentController.ts`](https://github.com/castrozan/tcc/blob/main/equipments-dummy-app/src/presentation/controllers/equipments/CreateEquipmentController.ts) demonstrate real-world usage. These classes extend `OpenAPIRoute` and apply the `@withErrorHandling` decorator to their `handle` methods, ensuring consistent error processing across the application layer.

When creating custom decorators for middleware functionality, follow the established project structure by placing decorator files in `src/presentation/decorators/` and exporting them for use in controller files. This maintains consistency with the existing architecture and ensures that middleware logic remains reusable and testable.

## Summary

- **Custom decorators for middleware functionality** in TypeScript are higher-order functions that intercept method descriptors to inject cross-cutting concerns like logging, authentication, and error handling.
- The `castrozan/tcc` repository demonstrates this pattern through the `withErrorHandling` decorator in [`professionals-dummy-app/src/presentation/decorators/handleErrors.ts`](https://github.com/castrozan/tcc/blob/main/professionals-dummy-app/src/presentation/decorators/handleErrors.ts).
- To create a custom decorator, define a function accepting `target`, `propertyKey`, and `descriptor`, store the original method, assign a wrapper function to `descriptor.value`, and return the descriptor.
- Always preserve the `this` context using `originalMethod.apply(this, args)` and support asynchronous methods by using `async/await` in the wrapper.
- Stack multiple decorators to combine functionality; they execute from bottom to top (closest to the method first).
- Place custom decorators in `src/presentation/decorators/` to maintain project consistency.

## Frequently Asked Questions

### What is the execution order when multiple decorators are applied to a single method?

When you stack multiple decorators on a method, they execute from bottom to top, meaning the decorator closest to the method definition runs first. For example, if you apply `@logRequest`, `@requireAuth`, and `@withErrorHandling` from top to bottom, the execution order is: `withErrorHandling` (innermost), then `requireAuth`, then `logRequest` (outermost), and finally the original method. This layering allows each decorator to wrap the next, creating a middleware pipeline where the innermost decorator has the final say before the actual business logic executes.

### How do I access the HTTP request object inside a custom decorator?

To access the HTTP request object inside a custom decorator, reference the controller instance via `this` after preserving context with `originalMethod.apply(this, args)`. In the `castrozan/tcc` codebase, controllers extend `OpenAPIRoute`, which provides framework-specific helpers like `getRequest()`. Inside your decorator wrapper, call `await this.getRequest()` to access the request object, then inspect headers, body, or query parameters as needed. This approach works because the decorator preserves the class context, allowing you to access all instance methods and properties of the controller.

### Can I use custom decorators with synchronous controller methods?

Yes, custom decorators work with both synchronous and asynchronous methods. When implementing the wrapper function in `descriptor.value`, you can omit the `async` keyword if the original method is synchronous. However, in the `castrozan/tcc` architecture, controller methods typically return Promises because they handle I/O operations. To ensure maximum compatibility, implement your wrapper as `async` and use `await originalMethod.apply(this, args)`, which works for both sync and async methods since awaiting a non-Promise value resolves immediately. This approach ensures your custom decorators for middleware functionality work consistently across all controller methods.

### Where should I place custom decorator files in the project structure?

Following the architecture established in `castrozan/tcc`, place custom decorator files in the `src/presentation/decorators/` directory. This location keeps middleware logic organized within the presentation layer, separate from business logic and infrastructure concerns. Create individual files for each decorator (e.g., [`logRequest.ts`](https://github.com/castrozan/tcc/blob/main/logRequest.ts), [`requireAuth.ts`](https://github.com/castrozan/tcc/blob/main/requireAuth.ts)) and export the decorator function as the default or a named export. Import these decorators into your controller files located in `src/presentation/controllers/`. This structure maintains consistency with the existing `withErrorHandling` decorator and ensures that middleware logic remains reusable, testable, and easy to locate.