# How DTOs Are Structured and Validated in the Application Layer: A Complete Guide

> Learn how to structure DTOs as TypeScript classes and validate them using Zod schemas in the presentation layer for efficient data transfer in your application.

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

---

**DTOs in this architecture are simple TypeScript classes that carry data from controllers to use-cases, while validation is handled separately by Zod schemas in the presentation layer.**

In the `castrozan/tcc` repository, the application layer follows a clean architecture pattern where **Data Transfer Objects (DTOs)** serve as typed containers for moving data between boundaries. This approach ensures that domain logic remains isolated from infrastructure concerns while maintaining type safety across the stack.

## What Are DTOs in the Application Layer?

DTOs are lightweight, serializable objects designed exclusively for transporting data. In this codebase, they contain no business logic, no validation rules, and no persistence code. Their sole responsibility is to guarantee the shape of data as it travels from the presentation layer (HTTP controllers) to the use-case layer and eventually to the repository layer.

## How DTOs Are Structured in the castrozan/tcc Codebase

### TypeScript Class Implementation

Each DTO is implemented as a concrete TypeScript class with explicitly typed properties. The constructor handles assignment, ensuring that all required fields are present at instantiation.

Here is the structure of `CreateProfessionalDto` from [`professionals-dummy-app/src/application/dtos/professionals/CreateProfessionalDto.ts`](https://github.com/castrozan/tcc/blob/main/professionals-dummy-app/src/application/dtos/professionals/CreateProfessionalDto.ts):

```typescript
export class CreateProfessionalDto {
    name: string;
    role: string;
    bio: string | null;
    imageUrl: string | null;
    hierarchy: number | null;

    constructor(
        name: string,
        role: string,
        bio: string | null,
        imageUrl: string | null,
        hierarchy: number | null
    ) {
        this.name = name;
        this.role = role;
        this.bio = bio;
        this.imageUrl = imageUrl;
        this.hierarchy = hierarchy;
    }
}

```

### Property Declaration Patterns

The DTOs consistently use:
- **Explicit type annotations** for all properties (`string`, `number | null`, etc.)
- **Constructor parameter assignment** to ensure immutability at creation
- **Nullable types** (`| null`) for optional fields rather than optional parameters, maintaining explicit data contracts

Similar patterns appear in `UpdateProfessionalDto`, `CreateEquipmentDto`, and `UpdateEquipmentDto` within their respective application directories.

## How Validation Works Outside the DTO

### Zod Schema Validation in Controllers

Validation is intentionally decoupled from DTOs and implemented in the presentation layer using **Zod schemas**. The controllers define strict validation rules that incoming HTTP requests must satisfy before a DTO is ever instantiated.

In `CreateProfessionalController` from [`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), the validation schema is defined as:

```typescript
schema = {
    request: {
        body: {
            content: {
                'application/json': {
                    schema: z.object({
                        name: z.string().min(1, { message: 'Name is required' }),
                        role: z.string().min(1, { message: 'Role is required' }),
                        bio: z.string().optional(),
                        imageUrl: z.string().url({ message: 'Invalid URL' }).optional(),
                        hierarchy: z.number().nullable()
                    })
                }
            }
        }
    }
};

```

### The Validation Flow

The complete data flow ensures that validation happens at the boundary before domain logic processes the data:

1. **Client** sends a JSON request to the HTTP endpoint
2. **Controller** parses the request using the Zod schema, enforcing constraints like minimum string lengths and URL formats
3. **DTO instantiation** occurs only after validation succeeds, using the sanitized values
4. **Use-case** receives the DTO and orchestrates domain logic
5. **Repository** persists the data, receiving the DTO or domain entity as appropriate

Here is how the controller instantiates the DTO and passes it to the use-case:

```typescript
const createProfessionalUseCase = new CreateProfessionalUseCase(professionalRepository);
const professional = await createProfessionalUseCase.execute({
    name,
    role,
    bio,
    imageUrl,
    hierarchy
});

```

## Domain-Level Validation vs. DTO Validation

While DTOs carry data and controllers validate request formats, **domain entities** enforce business invariants. For example, in [`equipments-dummy-app/src/domain/entities/equipments/Equipment.ts`](https://github.com/castrozan/tcc/blob/main/equipments-dummy-app/src/domain/entities/equipments/Equipment.ts), the entity likely contains validation logic specific to business rules (such as `Equipment.validate()` methods) that ensure the equipment state remains consistent regardless of which DTO created it.

This creates a clear separation of concerns:
- **Controllers** validate that incoming data matches expected types and formats
- **DTOs** transport validated data without transformation
- **Domain entities** enforce business rules and invariants

## Summary

- DTOs in the application layer are simple TypeScript classes with typed properties and constructors, containing no business logic
- Validation is performed by Zod schemas in the presentation layer (controllers) before DTOs are instantiated
- The separation allows DTOs to be reused across HTTP, CLI, and test entry points without duplicating validation code
- Domain entities handle business-level validation independently of DTO structure
- Key files include [`CreateProfessionalDto.ts`](https://github.com/castrozan/tcc/blob/main/CreateProfessionalDto.ts), [`CreateEquipmentDto.ts`](https://github.com/castrozan/tcc/blob/main/CreateEquipmentDto.ts), and [`CreateProfessionalController.ts`](https://github.com/castrozan/tcc/blob/main/CreateProfessionalController.ts)

## Frequently Asked Questions

### Why don't DTOs contain validation logic in this architecture?

DTOs are kept as thin data containers to maintain the Single Responsibility Principle. By decoupling validation from the DTO structure, the same DTO can be reused across different entry points (HTTP controllers, CLI commands, or test fixtures) without forcing each consumer to satisfy validation rules that might differ by context. Validation belongs at the boundary where data enters the system.

### How does Zod validation differ from DTO structure?

Zod schemas define runtime validation rules (such as minimum string length, URL format, or number constraints) that execute when HTTP requests arrive. DTOs, conversely, are compile-time TypeScript contracts that ensure type safety once data has passed validation. The Zod schema acts as a gatekeeper; the DTO acts as a transport vessel.

### Can DTOs be reused across different entry points?

Yes. Because DTOs contain no validation logic and no HTTP-specific code, they can be instantiated by controllers, command-line interfaces, or test suites alike. This reusability is a primary benefit of the clean architecture approach used in the `castrozan/tcc` repository, where the application layer remains independent of presentation concerns.

### Where should business validation rules be implemented?

Business validation rules belong in the **domain layer**, specifically within entity classes. For example, complex invariants that ensure an equipment item maintains valid state transitions should live in the `Equipment` entity (as seen in [`equipments-dummy-app/src/domain/entities/equipments/Equipment.ts`](https://github.com/castrozan/tcc/blob/main/equipments-dummy-app/src/domain/entities/equipments/Equipment.ts)), not in the DTO or controller. This ensures business rules are enforced regardless of which presentation layer creates the entity.