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
| DTO | Array |
|---|---|
| Type-safe | No type safety |
| Structured | Unstructured |
| Easy to maintain | Hard to track |
| IDE autocomplete | Limited support |
| Cleaner architecture | Messy 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 🚀

Leave a Reply