Originally published on laravel.learnio.dev — part of the Laravel best practices course. View this lesson on the original site.

Use one class for one endpoint responsibility

Single action controllers are invokable classes with one __invoke() method. They are useful when a route performs one focused operation.

Create an invokable controller

php artisan make:controller PublishOrderController --invokable

Laravel creates a controller class with a single handler method.

Register the route without a method string

use App\Http\Controllers\PublishOrderController;
use Illuminate\Support\Facades\Route;

Route::patch('/orders/{order}/publish', PublishOrderController::class)
    ->middleware(['auth'])
    ->name('orders.publish');

You pass only the class name because Laravel calls __invoke() automatically.

Example controller shape

use App\Models\Order;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class PublishOrderController extends Controller
{
    public function __invoke(Request $request, Order $order): RedirectResponse
    {
        // domain action or service call here
        $order->update(['status' => 'published']);

        return to_route('orders.show', ['order' => $order->id]);
    }
}

When this is a strong choice

  • the endpoint maps to one clear action,
  • you want explicit route intent in web.php or api.php,
  • you want smaller controller files with less unrelated code.

Bad way vs better way

Bad way (one large controller with many unrelated actions):

class OrderController extends Controller
{
    public function publish(...) {}
    public function archive(...) {}
    public function resendInvoice(...) {}
    public function syncWithErp(...) {}
}

Better way (one action per focused endpoint when complexity grows):

Route::patch('/orders/{order}/publish', PublishOrderController::class);
Route::patch('/orders/{order}/archive', ArchiveOrderController::class);
Route::post('/orders/{order}/resend-invoice', ResendOrderInvoiceController::class);

Why better:

  • each class has one responsibility,
  • route-to-behaviour mapping is easier to read,
  • tests stay focused and faster to maintain.

Keep naming predictable

Use action-oriented names:

  • PublishOrderController
  • ArchiveOrderController
  • ResendInvoiceController

This helps developers understand intent without opening each file.

Invokable controller flow

flowchart LR
  A[Route points to class] --> B[Laravel calls __invoke]
  B --> C[Single focused action]
  C --> D[Redirect JSON or view response]

Next, you will combine this with dependency injection patterns so complex actions can stay thin and testable.

From Laravel best practices — Routing, Controllers & Request Flow