Laravel Controllers Explained: Types, Examples & Best Practices (2026 Guide)
Learn every type of Laravel controller — resource, invokable, single-action & API — with real code examples and when to use each
1. Introduction: The Role of the Controller
In the modern Laravel 13.x ecosystem, a controller serves as the vital intermediary between your application's routes and its core business logic. Based on fundamental architecture concepts, the controller's primary responsibility is to receive an incoming HTTP request, coordinate the necessary logic, and return a response.
While Laravel allows you to define logic directly in route closures, moving that logic into dedicated controller classes provides several professional benefits:
- Code Cleanliness: Keeps your code
routes/web.phporroutes/api.phpfiles from becoming cluttered "junk drawers". - Separation of Concerns: Ensures that the routing layer only handles traffic, while the controller handles the "how" of the request.
- Scalability: Organised classes are significantly easier to navigate as your application grows from ten routes to hundreds.
- Testability: Dedicated classes are much simpler to target with automated unit and feature tests.
By embracing this organisation early, you move away from chaotic scripting toward the "thin controller" philosophy—a hallmark of elite Laravel development.
2. The Golden Rule: Keeping Controllers "Thin"
One of the most frequent mistakes I see in production environments is the "Fat Controller". These are classes overstuffed with database queries, complex calculations, and third-party API calls. To build a maintainable app, your controllers must remain thin, acting only as a conductor for an orchestra of other services.
The Dangers of "Fat Controllers"
- Hard-to-Test Logic: When business rules are trapped inside a controller method, you cannot easily test them in isolation.
- Repeated Code: Logic defined in a controller is difficult to reuse elsewhere, leading to the same code being copied across multiple classes.
- Endless Growth: Without boundaries, a single controller can easily balloon into thousands of lines, becoming a technical debt nightmare.
The "Fix-it" Table: Logic Distribution
Use this matrix to decide if a piece of code belongs in your controller or needs a new home:
Task Type | What stays in the Controller | Where to move Business Logic |
Primary Goal | Coordination (Request/Response) | Execution (The "Work") |
Data Logic | Validating the request shape | Repositories or Eloquent Models |
Complex Rules | Triggering a service call | Service Classes or Actions |
Side Effects | Returning a View or JSON | Jobs (for background tasks) |
Laravel 13.x provides specialised controller types to help you enforce this focused organisation from the start.
3. Type 1: Basic Controllers (Manual Organization)
Basic controllers are the bread and butter of Laravel organisation. They allow you to group related logic—like everything involving a "User"—into one place.
To generate a basic controller, use the Artisan command:
php artisan make:controller UserController
Example: Defining and Routing
In a basic controller, you define public methods to handle specific actions. Here is a UserController response to a profile request:
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\View\View;
class UserController extends Controller
{
public function show(string $id): View
{
return view('user.profile', [
'user' => User::findOrFail($id)
]);
}
}
You then map this method to the following:
use App\Http\Controllers\UserController;
Route::get('/user/{id}', [UserController::class, 'show']);
Senior Developer Tip (Laravel 13.x Middleware): In previous versions, middleware was often assigned in the constructor. In Laravel 13.x, the professional standard is to implement the HasMiddleware interface and use the static middleware() method or use attributes directly on the class or methods.
While basic controllers are flexible, some actions are so specialised they deserve their own dedicated class.
4. Type 2: Single Action (Invokable) Controllers
Sometimes you have a task that is particularly complex, such as a multi-step ProvisionServer process or an action. If you find yourself struggling to name the method (should it be handle()? execute()? doIt()?), you should use an Invokable Controller.
These controllers use the PHP magic method __invoke() to handle a single responsibility. This solves the "naming struggle" and follows the Single Responsibility Principle.
Routing Tip: When routing to an invokable controller, omit the method name entirely. Laravel automatically looks for the __invoke method.
Example Route:
Route::post('/server/provision', ProvisionServer::class);
Artisan Command:
php artisan make:controller ProvisionServer --invokable
Transitioning from these specialised single actions to standardised data management brings us to the CRUD powerhouse: resource controllers.
5. Type 3: Resource Controllers (The CRUD Powerhouse)
Resource controllers treat Eloquent models as "resources" that require standard actions. Instead of manually defining seven different routes for a blog, you can generate them all with one line of code.
Expert Pro-tip: When generating a controller for a specific model, use the --model flag to have Laravel automatically type-hint your methods:
php artisan make:controller PhotoController --model=Photo --resource
The 7 Standard Resource Actions
Using Route::resource('photos', PhotoController::class) maps, the following:
HTTP Verb | URI | Action (Method) | Route Name | Purpose |
GET |
|
|
| Display a list of all photos. |
GET |
|
|
| Show the form to upload a new photo. |
POST |
|
|
| Save the new photo to the database. |
GET |
|
|
| Display a specific photo. |
GET |
|
|
| Show the edit form for a photo. |
PUT/PATCH |
|
|
| Update the photo in the database. |
DELETE |
|
|
| Delete the photo. |
Advanced Resource Features
- API Resources: Use
Route::apiResource()to exclude thecreateandeditroutes (APIs don't need HTML forms). - Nested Resources: Use dot notation (e.g.,
photos.comments) to handle child items. - Singleton Resources: Use
Route::singleton()for items like a user'sprofile. You can use the.creatable()method nuance (e.g.,->creatable()) if the singleton should allow creation and deletion routes as well.
Even the best routes require robust professional patterns like dependency injection and validation to remain "thin."
6. Professional Patterns: Injection and Validation
To keep your controllers focused on orchestration, you must master how the Laravel Service Container provides the tools your methods need.
Dependency Injection (DI)
Laravel automatically "injects" objects into your controller.
Injection Type | Description | Best Use Case |
Constructor Injection | Defined in the | Services or repositories used by all actions. |
Method Injection | Type-hinted directly in the method. | Request-specific needs (e.g., |
The Practical DI Checklist
When working with dependency injection, follow these four rules from the "Half Shell" best practices:
- Inject, Don't Instantiate: Always type-hint your services and the Request object; never use the
newkeyword inside a controller method. - Order Matters: Always place your injected dependencies before your route parameters in the method signature.
- Bind Interfaces: If a concrete class cannot be auto-resolved, bind the interface in a Service Provider.
- Stay Orchestration-Focused: Ensure the injected collaborator does the heavy lifting while the controller merely calls its methods.
Form Request Validation
Professional developers move validation out of the controller entirely. Use the command php artisan make:request StorePostRequest to create a dedicated validation class.
3 Keys to Human-Readable Errors
Within your Form Request class, you can override default error messages to make them user-friendly:
- The
messages()Method: Return an array of custom text (e.g.,'title.required' => 'A title is needed to save a post'). - The
attributes()Method: Swap technical field names for human-friendly ones (e.g.,'user_id' => 'author'). - The
:attributePlaceholder: Use this to dynamically inject the human-friendly name from yourattributes()method.
7. Decision Matrix: Which Controller Should I Use?
Scenario | Recommended Type | Reasoning |
Standard CRUD (Blog, Products) | Resource | Follows RESTful conventions; predictable and standard. |
Complex Action (Deployment, Payment) | Invokable | Solves the "naming struggle" for single, complex tasks. |
Simple Contact Form / Static Pages | Basic | Best for grouping 2-3 non-resourceful actions together. |
Settings / User Profile | Singleton | Ideal for "one-of-a-kind" items where no ID is needed in the URI. |
Final Summary
Mastering controllers in Laravel 13.x is about discipline rather than just syntax. While the framework doesn't strictly "enforce" a specific architecture, following these patterns—keeping controllers thin, centralising validation in form requests, and leveraging dependency injection—leads to cleaner code and much happier development teams. Choose the right tool for your specific scenario, and your application will remain scalable, testable, and maintainable for years to come.