← Back to list

Laravel Actions vs Resources Explained

If you have been building Laravel apps for a while, you have probably asked yourself at some point , should I put this in an Action or a…

Coder Manjeet in Towards Dev · 2026-05-19 13:01 · 1 claps · 6.7 min read paywalled
#laravel #php #coding #software-architecture #web-development
Open on Medium ↗
Wiki topics: 💻 · Programming 🌐 · Web Development 🏛️ · Architecture

Laravel Actions vs API Resources — Learn the real difference between business logic classes and API response transformers, with real-world code examples.

Laravel Actions vs API Resources — Learn the real difference between business logic classes and API response transformers, with real-world code examples.

Laravel Actions vs Resources Explained

If you have been building Laravel apps for a while, you have probably asked yourself at some point , should I put this in an Action or a Resource? I have seen this confusion in Laracasts threads, Reddit discussions, and even in codebases of experienced developers.

The two concepts sound vague, they both live in the app folder, and neither of them is a controller.

So what exactly is the difference?

Here is the honest answer: they solve completely different problems. Once that clicks, your Laravel architecture becomes dramatically cleaner and your codebase starts to feel like it was built by someone who actually knows what they are doing. Let me break this down for you in a way that sticks.

What is a Laravel Action

An Action is a class that encapsulates a single, well-defined unit of business logic. That is it. Nothing more. The whole philosophy, as Nuno Maduro describes it brilliantly, is that an Action is completely agnostic from HTTP or the console. It does not care how it was called or from where.

Think about it this way. You have a feature called “Create a To-Do”. That logic — validating input, wrapping in a DB transaction, firing an event, notifying users — should live in one focused class. Not scattered across a controller, a model, and a job. One class, one responsibility.

<?php

namespace App\Actions;
use App\Models\Todo;
use App\Models\User;
use App\Events\TodoCreated;
use Illuminate\Support\Facades\DB;

class CreateTodo
{
    public function handle(User $user, array $attributes): Todo
    {
        return DB::transaction(function () use ($user, $attributes) {

            $todo = Todo::create([
                'user_id'     => $user->id,
                'description' => $attributes['description'],
                'priority'    => $attributes['priority'] ?? 'normal',
            ]);

            broadcast(new TodoCreated($todo))->toOthers();

            return $todo;
        });
    }
}

Notice a few things here. The method is called handle() — this is the community-preferred convention because it mirrors how Jobs and Listeners work in Laravel. The Action accepts a User and an attributes array. It wraps the logic in DB::transaction(). It broadcasts an event. And it returns the created resource.

Now here is the magical part. You can call this Action from anywhere.

<?php

// From a Controller
public function store(StoreTodoRequest $request, CreateTodo $action): JsonResponse
{
    $todo = $action->handle($request->user(), $request->validated());
    return response()->json($todo, 201);
}

// From an Artisan Command
public function handle(CreateTodo $action): void
{
    $todo = $action->handle(
        User::find(1),
        ['description' => 'Seeded todo via CLI', 'priority' => 'high']
    );
    $this->info("Todo #{$todo->id} created.");
}

// From a Job
public function handle(CreateTodo $action): void
{
    $action->handle($this->user, $this->attributes);
}

Same logic. Three different contexts. Zero code duplication. That is the power of Actions.

Action Naming Conventions That Actually Make Sense

One of the most consistent patterns in the Laravel community when naming Actions is: Verb + Resource. This instantly communicates what the Action does without even opening the file.

app/
└── Actions/
    ├── CreateTodo.php
    ├── UpdateTodo.php
    ├── DeleteTodo.php
    ├── ProcessPayment.php
    └── GenerateInvoice.php

Actions start with a verb — Create, Update, Delete, Send, Process, Generate. This follows the Single Responsibility Principle strictly. Each file tells you exactly what it does. No ambiguity, no treasure hunt through a bloated service class.

What is a Laravel API Resource

A Resource in Laravel is a transformation layer between your Eloquent model and the JSON response your API returns. It lives in app/Http/Resources and extends JsonResource. Its only job is to control what data gets sent out of your application and how it looks.

Let us say you have a Todo model with fields like id, user_id, description, priority, created_at, and updated_at. You do not want to blindly dump all of that to the client. Maybe user_id is internal. Maybe you want created_at formatted differently. Maybe you want to include the user's name as a nested object. A Resource handles all of this cleanly.

<?php

namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class TodoResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id'          => $this->id,
            'description' => $this->description,
            'priority'    => $this->priority,
            'is_completed'=> $this->completed_at !== null,
            'created_at'  => $this->created_at->diffForHumans(),
            'author'      => [
                'id'   => $this->user->id,
                'name' => $this->user->name,
            ],
            'can_edit'    => $request->user()?->id === $this->user_id,
        ];
    }
}

Notice what this Resource is doing. It is hiding user_id and exposing a nested author object instead. It is formatting created_at as a human-readable string. It is adding a computed is_completed flag. It is even adding a permission-aware can_edit field based on the current authenticated user.

None of this business logic belongs in your model. None of it belongs in your controller. The Resource owns the output shape.

Using Conditional Attributes and Relationships

One of the most powerful features of Laravel Resources is conditional loading. The when() and whenLoaded() methods let you include data only when it is relevant, which is critical for performance.

public function toArray(Request $request): array
{
    return [
        'id'          => $this->id,
        'description' => $this->description,
        // Only include this for admin users
        'internal_notes' => $this->when(
            $request->user()?->isAdmin(),
            $this->internal_notes
        ),
        // Only include tags if they were eagerly loaded
        'tags' => TagResource::collection(
            $this->whenLoaded('tags')
        ),
        // Only include on detailed views
        'comments_count' => $this->when(
            $request->routeIs('todos.show'),
            $this->comments()->count()
        ),
    ];
}

Using whenLoaded() is especially important for avoiding N+1 query problems. If your controller does not eager load tags, the Resource simply skips it rather than triggering an extra query for every item in the collection.

The Key Difference: A Clear Mental Model

Here is the clearest way to think about this:

| Concern        | Actions                            | API Resources                     |
| -------------- | ---------------------------------- | --------------------------------- |
| Purpose        | Business logic execution           | Data transformation for output    |
| Where it lives | app/Actions/                       | app/Http/Resources/               |
| Called from    | Controllers, Jobs, Commands, Tests | Controllers only (response layer) |
| HTTP awareness | None — fully agnostic              | Yes — aware of the request        |
| Returns        | Model, array, void                 | JSON-ready array structure        |
| Naming         | CreateTodo, SendEmail              | TodoResource, UserResource        |
| Responsibility | What the app does                  | What the app says                 |

The simplest mental model: Actions are about doing. Resources are about showing. An Action creates a Todo. A Resource decides how that Todo looks in the API response.

Putting It All Together in a Real Controller

Here is how a clean, modern Laravel 13 controller looks when Actions and Resources are used together correctly:

<?php

namespace App\Http\Controllers;
use App\Actions\CreateTodo;
use App\Actions\UpdateTodo;
use App\Http\Requests\StoreTodoRequest;
use App\Http\Requests\UpdateTodoRequest;
use App\Http\Resources\TodoResource;
use App\Models\Todo;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;

class TodoController extends Controller
{
    public function index(): AnonymousResourceCollection
    {
        $todos = Todo::with('user', 'tags')
            ->where('user_id', auth()->id())
            ->latest()
            ->paginate(15);
        return TodoResource::collection($todos);
    }
    public function store(StoreTodoRequest $request, CreateTodo $action): TodoResource
    {
        $todo = $action->handle($request->user(), $request->validated());
        return new TodoResource($todo->load('user'));
    }
    public function show(Todo $todo): TodoResource
    {
        $this->authorize('view', $todo);
        return new TodoResource($todo->load(['user', 'tags', 'comments']));
    }
    public function update(UpdateTodoRequest $request, Todo $todo, UpdateTodo $action): TodoResource
    {
        $this->authorize('update', $todo);
        $todo = $action->handle($todo, $request->validated());
        return new TodoResource($todo);
    }
}

Look at how thin this controller is. It handles authorization. It delegates business logic to Actions. It wraps responses in Resources. That is its entire job. The controller does not know how a Todo is created. It does not know how a Todo is formatted. It just orchestrates.

When to Use Actions, When to Use Resources

Use an Action when:

  • You have business logic that could be reused from multiple entry points (HTTP, CLI, Jobs)
  • The operation involves multiple steps — DB writes, event firing, notifications
  • You want to keep your controller thin and testable in isolation
  • You have logic that would otherwise be duplicated across controllers

Use a Resource when:

  • You are building an API and returning JSON responses
  • You want to hide sensitive model fields like password or secret_token
  • You need to format, rename, or compute fields before sending them to the client
  • You want to conditionally include nested relationships based on what was loaded

Use both together when:

  • You are building a proper REST API — which is almost always
  • An Action creates or modifies data and a Resource shapes the response
  • You want a codebase that is readable, testable, and maintainable by a team

A Common Mistake to Avoid

I see this pattern way too often in production Laravel apps:

// Please do not do this
public function store(Request $request): JsonResponse
{
    $todo = Todo::create([
        'user_id'     => auth()->id(),
        'description' => $request->description,
    ]);

    // Firing events in the controller
    event(new TodoCreated($todo));
    // Formatting in the controller
    return response()->json([
        'id'          => $todo->id,
        'description' => $todo->description,
        'created'     => $todo->created_at->toDateString(),
    ]);
}

This controller is doing three jobs at once. It is handling business logic, side effects, and response formatting. When your requirements change — and they always do — you end up touching the controller for every single change. Extract the business logic into an Action. Extract the response formatting into a Resource. Your future self will thank you.

Final Thoughts

Laravel gives you the tools. Actions and Resources are not magic — they are just well-placed classes with focused responsibilities. The framework does not enforce this architecture on you, which means it is up to you as the artisan to apply it intentionally.

If you watched Nuno Maduro walk through Action classes, you already understand the philosophy. Actions keep your logic portable and testable. Resources keep your API responses consistent and safe.

Start with one small feature in your next project. Extract the logic into an Action. Wrap the response in a Resource. Feel the difference. Then never go back.

References:

If this post helped you understand the distinction, drop a clap, it genuinely helps more developers discover it. Got a pattern you use in your own projects for handling Actions and Resources? Drop it in the comments below, I read every single one.

Follow me on Medium at my handle — Coder Manjeet, for more practical Laravel deep-dives every week.

You can also catch me on X — sharing quick Laravel tips, code snippets, and the occasional hot take on PHP architecture.


메타데이터
post_id
43837a135028
slug
laravel-actions-vs-resources-explained-43837a135028
url
https://towardsdev.com/laravel-actions-vs-resources-explained-43837a135028
canonical_url
https://towardsdev.com/laravel-actions-vs-resources-explained-43837a135028
author_url
https://medium.com/@codermanjeet
status
ok
fetched_at
2026-06-11 05:11:55