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(...) or redirect()->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