Writing Clean Laravel Code That Scales
Practical patterns for keeping controllers small, business logic testable, and Laravel applications maintainable as they grow.
Keep controllers focused
Controllers should translate an HTTP request into an application action and return a response. When validation, pricing rules, persistence, and notifications all live in one controller method, every change becomes risky.
Move reusable business operations into focused action or service classes. The controller remains easy to read, while the action can be tested without constructing a full HTTP request.
final class CreateOrder
{
public function handle(Customer $customer, array $data): Order
{
return DB::transaction(
fn () => $customer->orders()->create($data)
);
}
}Use Form Requests for input
A Form Request keeps authorization and validation close to the request boundary. Your controller receives trusted, normalized data through the validated method instead of repeatedly reading raw input.
- Centralize validation rules and custom messages
- Keep authorization decisions out of controllers
- Pass only validated data into your application layer
Model relationships, not hidden workflows
Eloquent models are a natural home for relationships, casts, scopes, and small state-related behavior. Large workflows involving several models, external services, or multiple side effects belong in dedicated application classes.
This boundary prevents models from becoming untestable god objects while preserving Laravel's expressive domain APIs.
Make side effects explicit
Events, listeners, jobs, and notifications are useful when they make asynchronous work or domain reactions clear. Avoid hiding critical business steps behind a long chain of observers that developers cannot discover from the use case.
- Dispatch slow work to queued jobs
- Use domain events for meaningful completed actions
- Keep transactions around related database changes
- Test both the application action and dispatched side effects
