API & Architecture

Action-Based Architecture in Laravel 13

Senior Editor
Sep 28, 2026
6 min read

Transform bloated controllers and fragmented services into single-responsibility Action classes that are clean, testable, and reusable.

What is an Action?

An Action represents a single discrete business capability in your domain. Rather than having a CourseController handle validation, database transactions, email notifications, and cache busting in a 200-line method, an Action handles the business logic directly.

Structure of a Clean Action

Actions typically live in app/Actions/{Domain}/ and expose an execute() or __invoke() method:

PHP
namespace App\Actions\Courses;

use App\Models\Course;
use App\Models\User;
use Illuminate\Support\Facades\DB;

class EnrollUserInCourse
{
    public function execute(User $user, Course $course): void
    {
        DB::transaction(function () use ($user, $course) {
            $user->enrolledCourses()->syncWithoutDetaching([$course->id]);
            
            // Dispatch domain event or notification
        });
    }
}

Thin Controllers with Actions

With actions, your controller becomes lightweight and purely responsible for HTTP parsing:

PHP
public function store(EnrollRequest $request, Course $course, EnrollUserInCourse $action)
{
    $action->execute($request->user(), $course);

    return back()->with('status', 'Enrolled successfully!');
}

Testing Actions in Isolation

Testing actions with Pest is straightforward because they do not require HTTP requests or mocked controllers:

PHP
test('user can enroll in a course', function () {
    $user = User::factory()->create();
    $course = Course::factory()->create();

    app(EnrollUserInCourse::class)->execute($user, $course);

    expect($user->enrolledCourses)->toHaveCount(1);
});
Topics in this article

Related Knowledge Articles

Keep controllers pristine by moving complex input validation, authorization rules, and sanitized data casting into dedicated Form Request classes.
Prevent path traversal, MIME spoofing, and unauthorized download exposure when serving learning resources.
Implement fine-grained resource authorization using Laravel Policies, Gate definitions, and Inertia authorization props.