Laravel Controller Tutorial with Example

This tutorial covers how controllers work in Laravel 12: how to create one, define methods, connect them to routes, pass URL parameters, handle form input, apply middleware, and use resource controllers for CRUD operations.
Version note: The routing syntax and controller structure shown here applies to Laravel 9 through Laravel 12. The main difference from Laravel 8 is how middleware is defined inside a controller — covered in the middleware section below.
What is a Laravel Controller?
A controller is a PHP class that handles incoming HTTP requests. When a user hits a URL, Laravel’s router matches it to a controller method, which runs the appropriate logic, querying the database, processing a form, returning a view, or sending a JSON response.
Think of it as the traffic director in the MVC pattern: routes tell Laravel where to send a request, and the controller decides what to do with it.
Laravel Controllers find in app/Http/Controllers by default.
A practical example: a UserController might have one method to list users, another to show a single profile, one to save a new user, and one to delete, all grouped in a single, focused file.
How to Create a Controller in Laravel 12
From your project root, run:
php artisan make:controller LoginController
This creates app/Http/Controllers/LoginController.php with a bare class stub ready for your methods.
Basic Controller Structure
The generated file looks like this:
<?php
namespace App\Http\Controllers;
class LoginController extends Controller
{
//
}It extends Controller – the base class at app/Http/Controllers/Controller.php. In Laravel 12 this is optional, but it is still a good default to keep because it is where you would add shared methods across all your controllers.
Now add a public method:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Response;
class LoginController extends Controller
{
/**
* Show the login page.
*/
public function show(): Response
{
return response('Hello from the Login Controller');
}
}The : Response return type hint is not required, but it is good practice, it makes the code self-documenting and helps static analysis tools catch mistakes early.
Read this: Laravel Interview Questions for Experienced Developers
Connecting a Controller Method to a Route
Open routes/web.php and register a route pointing to the show() method:
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\LoginController;
Route::get('/login', [LoginController::class, 'show']);The array syntax [LoginController::class, 'show'] is the standard way to reference a controller method in Laravel 8 and later. The first element is the fully qualified class name, the second is the method name.
Visit http://localhost/login (or your equivalent local URL) and you will see the response.
Returning a View
Most laravel controller methods return a Blade view rather than a plain string:
<?php
namespace App\Http\Controllers;
use Illuminate\View\View;
class LoginController extends Controller
{
/**
* Show the login form.
*/
public function show(): View
{
return view('auth.login');
}
}This renders the Blade template at resources/views/auth/login.blade.php. You can pass data to the view as a second argument:
return view('auth.login', ['title' => 'Sign In']);Inside the Blade file, $title is then available automatically.
Passing URL Parameters to a Controller
To capture a value from the URL – such as a user ID – define a route parameter with curly braces, then declare it as an argument in the method:
Route:
Route::get('/user/{id}', [LoginController::class, 'show']);Controller method:
public function show(string $id): Response
{
return response('User ID: ' . $id);
}Visiting /user/42 passes 42 into $id.
Multiple parameters work the same way – just make sure their order in the method signature matches the order in the URI:
// Route
Route::get('/post/{category}/{id}', [PostController::class, 'show']);
// Controller method
public function show(string $category, string $id): Response
{
// both values are available here
}Check our detailed guide on: Laravel REST API with Sanctum: Complete Tutorial
Resource Laravel Controllers (CRUD Example)
When building a standard CRUD feature, a resource controller gives you all seven actions wired up automatically.
Generate one:
php artisan make:controller PostController --resource
Register it with a single route declaration:
use App\Http\Controllers\PostController;
Route::resource('posts', PostController::class);That one line registers seven routes:
| HTTP Verb | URI | Controller Method | Named Route | Purpose |
|---|---|---|---|---|
| GET | /posts | index() | posts.index | List all posts |
| GET | /posts/create | create() | posts.create | Show create form |
| POST | /posts | store() | posts.store | Save a new post |
| GET | /posts/{post} | show() | posts.show | Show a single post |
| GET | /posts/{post}/edit | edit() | posts.edit | Show edit form |
| PUT/PATCH | /posts/{post} | update() | posts.update | Save changes to a post |
| DELETE | /posts/{post} | destroy() | posts.destroy | Delete a post |
Verify registered routes at any time:
php artisan route:list
Limiting Resource Routes
If your feature only needs some of these, use only() or except():
// Only these two routes
Route::resource('posts', PostController::class)->only(['index', 'show']);
// All routes except delete
Route::resource('posts', PostController::class)->except(['destroy']);API Resource Controllers
For APIs, skip the create and edit routes (which exist only to render HTML forms) using apiResource:
php artisan make:controller PostController --api
Route::apiResource('posts', PostController::class);Register multiple at once:
Route::apiResources([
'posts' => PostController::class,
'comments' => CommentController::class,
]);Applying Middleware to Controllers
In Laravel 12 (Recommended)
Laravel 12 uses the HasMiddleware interface to define middleware inside a controller. This replaces the $this->middleware() constructor call used in Laravel 8:
<?php
namespace App\Http\Controllers;
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
class LoginController extends Controller implements HasMiddleware
{
/**
* Define middleware for this controller's actions.
*/
public static function middleware(): array
{
return [
'auth', // applies to all methods
new Middleware('verified', only: ['store']), // only the store method
new Middleware('throttle:5,1', except: ['show']), // all except show
];
}
public function show(): Response { /* ... */ }
public function store(Request $request): RedirectResponse { /* ... */ }
}On the Route (Alternative)
You can also apply middleware directly to the route definition, useful for one-off cases:
Route::get('/login', [LoginController::class, 'show'])->middleware('guest');Or on a resource route targeting specific methods:
Route::resource('posts', PostController::class)
->middleware(['auth', 'verified']);Migrating from Laravel 8? If your controllers use
$this->middleware('auth')inside__construct(), replace that with theHasMiddlewareinterface shown above. The constructor approach is deprecated in newer releases.
Dependency Injection in Controllers
Laravel’s service container resolves type-hinted dependencies automatically – both in constructors and in methods.
Constructor Injection
Use this when a dependency is needed across multiple methods in the same controller:
<?php
namespace App\Http\Controllers;
use App\Services\UserService;
use Illuminate\View\View;
class UserController extends Controller
{
public function __construct(
protected UserService $userService
) {}
public function index(): View
{
$users = $this->userService->getAll();
return view('users.index', compact('users'));
}
public function show(string $id): View
{
$user = $this->userService->findOrFail($id);
return view('users.show', compact('user'));
}
}Method Injection
Use this when a dependency is only needed for a single method:
public function store(Request $request, UserService $userService): RedirectResponse
{
$userService->create($request->validated());
return redirect('/users');
}Combining Injected Dependencies with Route Parameters
If a method needs both a type-hinted dependency and a route parameter, list the dependencies first, the route parameter name must match what is defined in the route:
// Route
Route::put('/user/{id}', [UserController::class, 'update']);
// Controller method — Request is injected, $id comes from the route
public function update(Request $request, string $id): RedirectResponse
{
// update logic...
return redirect('/users');
}Common Errors and How to Fix Them
Target class [App\Http\Controllers\LoginController] does not exist
The class name in the use statement in web.php does not match the actual file. Check for typos in the namespace or class name. Also run composer dump-autoload if you created the file manually instead of with Artisan.
405 Method Not Allowed
The HTTP verb your request is using (GET, POST, etc.) does not match what the route expects. Check your form’s method attribute and confirm the route is using the right verb (Route::post, Route::put, etc.).
Route [posts.index] not defined
The named route does not exist — either it was not registered, or the name is wrong. Run php artisan route:list --name=posts to see all routes with “posts” in the name.
Class "App\Http\Controllers\LoginController" not found after moving the file
The file’s namespace declaration must match its directory. If the file is in app/Http/Controllers/Auth/, the namespace must be App\Http\Controllers\Auth, not App\Http\Controllers. Run composer dump-autoload after fixing the namespace.
