# Presentation Layer Error Handling Decorator: How It Works in the TypeScript Dummy Apps

> Learn how the presentation layer error handling decorator in TypeScript provides a consistent JSON response for all errors, including validation and runtime exceptions.

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

---

**The `withErrorHandling` decorator wraps controller methods in a unified try/catch block to ensure all errors—whether Zod validation failures or runtime exceptions—return a consistent JSON envelope with appropriate status codes.**

The `castrozan/tcc` repository implements a robust error handling strategy across its dummy applications (professionals and equipments) using a TypeScript decorator that intercepts exceptions at the presentation layer. This pattern centralizes error management, allowing controllers to focus strictly on request handling and business logic delegation while guaranteeing standardized API responses.

## Decorator Implementation in [`handleErrors.ts`](https://github.com/castrozan/tcc/blob/main/handleErrors.ts)

The core logic resides in [`professionals-dummy-app/src/presentation/decorators/handleErrors.ts`](https://github.com/castrozan/tcc/blob/main/professionals-dummy-app/src/presentation/decorators/handleErrors.ts) (with an identical copy in the equipments app). The `withErrorHandling` function conforms to the TypeScript decorator signature for method decorators, accepting the target object, property key, and property descriptor.

```typescript
export function withErrorHandling(
    targetOrFn: object,
    propertyKey?: string,
    descriptor?: PropertyDescriptor
): PropertyDescriptor {
    const originalMethod = descriptor.value;
    descriptor.value = async function (...args: unknown[]): Promise<object> {
        try {
            return await originalMethod.apply(this, args);
        } catch (error) {
            console.error(`Error in ${targetOrFn.constructor.name}.${propertyKey}:`, error);

            if (error instanceof z.ZodError) {
                // Validation errors from Zod schemas
                return {
                    success: false,
                    message: 'Validation failed',
                    errors: error.errors
                };
            }

            // Generic runtime errors
            return {
                success: false,
                message: error.message || 'An unexpected error occurred',
                statusCode: error.statusCode || 500
            };
        }
    };
    return descriptor;
}

```

The decorator mutates the `descriptor.value` to inject a wrapper function that preserves the original method's context via `apply(this, args)`. This ensures the controller instance (`this`) remains accessible within the wrapped method.

## Error Classification and Response Shapes

The presentation layer error handling decorator distinguishes between two primary failure categories, returning distinct JSON structures for each.

### Zod Validation Errors

When input validation fails against a Zod schema (thrown by `this.getValidatedData()` in controllers), the decorator catches the `z.ZodError` and returns a structured response containing the specific validation issues:

```typescript
{
    success: false,
    message: 'Validation failed',
    errors: error.errors  // Array of ZodIssue objects
}

```

### Generic Runtime Errors

For all other exceptions—including domain errors from use cases or unexpected runtime failures—the decorator falls back to a generic envelope that extracts the error message and optional HTTP status code:

```typescript
{
    success: false,
    message: error.message || 'An unexpected error occurred',
    statusCode: error.statusCode || 500
}

```

## Real-World Usage in Controllers

In [`professionals-dummy-app/src/presentation/controllers/professionals/UpdateProfessionalController.ts`](https://github.com/castrozan/tcc/blob/main/professionals-dummy-app/src/presentation/controllers/professionals/UpdateProfessionalController.ts), the decorator is applied directly above the `handle` method using the `@withErrorHandling` syntax. This controller extends `OpenAPIRoute` and uses Zod for request validation.

```typescript
export class UpdateProfessionalController extends OpenAPIRoute {
    // OpenAPI schema configuration omitted for brevity

    @withErrorHandling
    async handle(): Promise<object> {
        const data = await this.getValidatedData();  // May throw ZodError
        const { id, name, role, bio, imageUrl, hierarchy } = data.body;

        const useCase = new UpdateProfessionalUseCase(professionalRepository);
        const professional = await useCase.execute({ id, name, role, bio, imageUrl, hierarchy });

        return {
            success: true,
            result: {
                id: professional.id,
                name: professional.name,
                // ... additional fields
            }
        };
    }
}

```

When the HTTP request triggers `handle()`, the decorator intercepts exceptions from three potential sources: schema validation, use case execution, or unexpected runtime errors. The client always receives a predictable JSON structure regardless of where the failure originated.

## Architectural Benefits of the Presentation Layer Error Handling Decorator

**Separation of Concerns**: By extracting error management into a reusable decorator, controllers remain focused on request parsing, use case invocation, and response shaping. The decorator handles logging, error classification, and HTTP status mapping independently.

**Cross-Cutting Concern Implementation**: This pattern represents Aspect-Oriented Programming (AOP) in TypeScript. The error handling concern cuts across all API routes, and the decorator provides a declarative way to apply it consistently without code duplication.

**Contract Consistency**: Every endpoint adheres to the same error-response contract (`{ success, message?, statusCode?, errors? }`), simplifying frontend error handling and automated API documentation generation.

**Extensibility**: Additional error classifications—such as custom domain errors or authentication failures—can be added to the catch block in [`handleErrors.ts`](https://github.com/castrozan/tcc/blob/main/handleErrors.ts) without modifying individual controller files.

## Implementing the Decorator in New Controllers

To apply the error handling decorator to a new controller, import `withErrorHandling` from the decorators barrel file and annotate the `handle` method:

```typescript
import { withErrorHandling } from 'presentation/decorators';
import { z } from 'zod';
import { SomeUseCase } from 'application/use-cases/some';

export class CreateSomethingController extends OpenAPIRoute {
    schema = { /* OpenAPI schema with Zod validation */ };

    @withErrorHandling
    async handle() {
        const data = await this.getValidatedData();  // throws ZodError on invalid input
        const useCase = new SomeUseCase();
        const result = await useCase.execute(data.body);
        return { success: true, result };
    }
}

```

The decorator ensures that any thrown exception—whether from Zod validation or the use case layer—returns a properly formatted error response to the client.

## Summary

- 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) wraps async controller methods to centralize exception handling.
- It distinguishes between **Zod validation errors** (returning detailed validation arrays) and **generic runtime errors** (returning message and status code).
- Controllers apply the decorator using `@withErrorHandling` to ensure consistent JSON error envelopes across all OpenAPI routes.
- This pattern implements **Aspect-Oriented Programming** principles, keeping controllers thin and error handling logic maintainable in a single location.
- The same implementation exists in both the professionals and equipments dummy applications for architectural consistency.

## Frequently Asked Questions

### What is the difference between how the decorator handles Zod errors versus runtime errors?

The decorator checks if the caught error is an instance of `z.ZodError`. For validation failures, it returns `{ success: false, message: 'Validation failed', errors: error.errors }` with a 200 status (implied) but explicit failure flag. For runtime errors, it returns `{ success: false, message: error.message, statusCode: error.statusCode || 500 }`, defaulting to HTTP 500 when no specific status is attached to the error object.

### Can I use the `withErrorHandling` decorator on non-async methods?

The current implementation in [`handleErrors.ts`](https://github.com/castrozan/tcc/blob/main/handleErrors.ts) explicitly wraps the method with an `async` wrapper: `descriptor.value = async function (...)`. While you can apply the decorator to synchronous methods, the wrapper will still return a Promise. To avoid potential issues, the decorator should only be applied to async controller methods designed to return Promises.

### How do I add support for custom domain errors in the decorator?

Open [`professionals-dummy-app/src/presentation/decorators/handleErrors.ts`](https://github.com/castrozan/tcc/blob/main/professionals-dummy-app/src/presentation/decorators/handleErrors.ts) and add additional `instanceof` checks within the catch block before the generic fallback. For example, check for a custom `DomainError` class to return specific status codes or message formats, then return a specialized response object before the final generic return statement.

### Where is the error handling decorator exported from?

The decorator is exported from the barrel file at [`professionals-dummy-app/src/presentation/decorators/index.ts`](https://github.com/castrozan/tcc/blob/main/professionals-dummy-app/src/presentation/decorators/index.ts), allowing you to import it via `import { withErrorHandling } from 'presentation/decorators'`. The same export structure exists in the equipments dummy app at [`equipments-dummy-app/src/presentation/decorators/index.ts`](https://github.com/castrozan/tcc/blob/main/equipments-dummy-app/src/presentation/decorators/index.ts).