Understanding DTOs in Laravel

Written by

in

As Laravel applications grow, managing and validating data between layers becomes increasingly difficult. One powerful solution is using DTOs (Data Transfer Objects).

DTOs help developers create clean, structured, and maintainable applications by organizing data transfer across different layers.

In this guide, you’ll learn:

  • What DTOs are
  • Why DTOs matter in Laravel
  • How to create DTOs
  • Best practices
  • Real-world examples

What is a DTO?

A Data Transfer Object (DTO) is an object used to carry data between different parts of an application.

DTOs contain only data and no business logic.

Instead of passing raw arrays everywhere, DTOs provide a structured and type-safe approach.


Why Use DTOs in Laravel?

Using DTOs provides several benefits:

1. Cleaner Code

DTOs organize request data into structured objects.

2. Type Safety

Properties can have strict types.

3. Better Maintainability

Changes become easier to manage.

4. Improved Readability

Developers immediately understand expected data.

5. Separation of Concerns

Controllers stay lightweight and clean.


Without DTOs

Example using raw request arrays:

public function store(Request $request)
{
User::create([
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt($request->password),
]);
}

This approach becomes messy in large applications.


With DTOs

Using a DTO:

$userDTO = new UserDTO(
$request->name,
$request->email,
$request->password
);

Much cleaner and easier to maintain.


Creating a DTO in Laravel

Step 1: Create DTO Folder

Recommended structure:

app/
├── DTOs/
│ └── UserDTO.php

Step 2: Create UserDTO

File:

app/DTOs/UserDTO.php

Code:

<?php

namespace App\DTOs;

class UserDTO
{
public string $name;

public string $email;

public string $password;

public function __construct(
string $name,
string $email,
string $password
) {
$this->name = $name;
$this->email = $email;
$this->password = $password;
}
}

Step 3: Use DTO in Controller

Example:

<?php

namespace App\Http\Controllers;

use App\DTOs\UserDTO;
use App\Models\User;
use Illuminate\Http\Request;

class UserController extends Controller
{
public function store(Request $request)
{
$userDTO = new UserDTO(
$request->name,
$request->email,
bcrypt($request->password)
);

User::create([
'name' => $userDTO->name,
'email' => $userDTO->email,
'password' => $userDTO->password,
]);

return response()->json([
'message' => 'User created successfully'
]);
}
}

Better Approach: Static Factory Method

A cleaner implementation:

<?php

namespace App\DTOs;

use Illuminate\Http\Request;

class UserDTO
{
public function __construct(
public string $name,
public string $email,
public string $password
) {}

public static function fromRequest(Request $request): self
{
return new self(
$request->name,
$request->email,
bcrypt($request->password)
);
}
}

Usage:

$userDTO = UserDTO::fromRequest($request);

DTOs with Form Requests

DTOs work perfectly with Laravel Form Requests.

Example:

public function store(StoreUserRequest $request)
{
$userDTO = UserDTO::fromRequest($request);

User::create([
'name' => $userDTO->name,
'email' => $userDTO->email,
'password' => $userDTO->password,
]);
}

This creates a clean architecture:

Request Validation → DTO → Service → Repository → Database

DTO vs Array

DTOArray
Type-safeNo type safety
StructuredUnstructured
Easy to maintainHard to track
IDE autocompleteLimited support
Cleaner architectureMessy in large apps

DTO vs Model

Many developers confuse DTOs with Models.

Model

Represents database tables and business relationships.

DTO

Represents data transfer between layers.

DTOs should not contain database logic.


Advanced DTO Techniques

Immutable DTOs

Prevent accidental data modification.

Example:

readonly class UserDTO
{
public function __construct(
public string $name,
public string $email
) {}
}

Available in PHP 8.1+.


Nested DTOs

DTOs can contain other DTOs.

Example:

class AddressDTO
{
public function __construct(
public string $city,
public string $country
) {}
}

Collection DTOs

Useful for API responses and bulk operations.


Using Spatie Laravel Data Package

A popular package for DTO handling:

composer require spatie/laravel-data

Example:

use Spatie\LaravelData\Data;

class UserData extends Data
{
public function __construct(
public string $name,
public string $email
) {}
}

Benefits:

  • Validation
  • Transformation
  • API resources
  • Type safety

Best Practices for DTOs

Keep DTOs Simple

Avoid business logic.

Use Typed Properties

Improves reliability.

Use Static Factory Methods

Makes DTO creation cleaner.

Combine with Services

DTOs work best with service layers.

Use Readonly DTOs

Prevent accidental mutations.


Real-World Use Cases

DTOs are commonly used in:

  • REST APIs
  • SaaS applications
  • Payment systems
  • External API integrations
  • Enterprise applications

Common Mistakes to Avoid

1. Putting Business Logic in DTOs

DTOs should only transport data.

2. Overusing DTOs

Simple applications may not need them.

3. Mixing DTOs with Eloquent Models

Keep responsibilities separate.


Frequently Asked Questions

Are DTOs necessary in Laravel?

Not always, but they improve structure in medium and large applications.

Do DTOs improve performance?

Not significantly. Their main benefit is maintainability.

Should DTOs replace Form Requests?

No. Form Requests handle validation; DTOs handle structured data transfer.

Can DTOs work with APIs?

Absolutely. DTOs are excellent for API request and response handling.


Final Thoughts

DTOs are a powerful architectural tool for Laravel developers.

They help create:

  • Cleaner controllers
  • Better structured applications
  • Maintainable codebases
  • Scalable systems

When combined with repositories and service layers, DTOs make Laravel applications significantly more professional and easier to manage.


Conclusion

If you’re building serious Laravel applications, learning DTOs is a valuable investment.

Start with small DTO implementations and gradually introduce them into larger parts of your application.

Clean architecture leads to better software.

Happy Coding 🚀

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *