Originally published on laravel.learnio.dev — part of the Laravel best practices course. View this lesson on the original site.
Keep controllers focused on HTTP orchestration
A controller should receive the request, delegate work, and return a response. It should not carry all business rules.
Controller responsibilities in Laravel
Good controller responsibilities:
- accept input from route parameters and request data,
- call domain or application services,
- return a view, JSON payload, or redirect.
Poor controller responsibilities:
- complex business calculations,
- multi-step persistence rules,
- formatting logic duplicated across many actions.
Method injection and route parameters
Laravel resolves controller dependencies through the container.
use Illuminate\Http\Request;
public function update(Request $request, string $id)
{
// Request is injected, id comes from route
}
Keep request dependencies first and route parameters after them.
Thin controller example
use App\Http\Requests\StoreOrderRequest;
use App\Services\CreateOrderService;
class OrderController extends Controller
{
public function store(
StoreOrderRequest $request,
CreateOrderService $service
) {
$order = $service->handle($request->validated());
return to_route('orders.show', ['order' => $order->id]);
}
}
Validation lives in the form request, business logic in the service, and controller stays readable.
Bad way vs better way
Bad way (fat controller):
public function store(Request $request)
{
$data = $request->validate([
'email' => ['required', 'email'],
'total' => ['required', 'numeric'],
]);
// pricing rules, discounts, inventory checks, and persistence all here
// ...
return redirect('/orders');
}
Better way (thin controller and delegated logic):
public function store(StoreOrderRequest $request, CreateOrderService $service)
{
$order = $service->handle($request->validated());
return to_route('orders.show', ['order' => $order->id]);
}
Why better:
- business rules become reusable from jobs and commands,
- controller tests focus on HTTP behaviour,
- complex logic gets isolated and easier to maintain.
Return types and response clarity
Be explicit about response intent:
return view(...)for HTML pages,return response()->json(...)for APIs,return to_route(...)orredirect()->route(...)for post-action navigation.
This keeps controller actions predictable for other developers.
Controller request flow
flowchart LR
A[Route] --> B[Controller action]
B --> C[Validation and service call]
C --> D[View JSON or redirect response]
Next, you will look at single action controllers for endpoints that only need one focused operation.
From Laravel best practices — Routing, Controllers & Request Flow