Laravel Controllers: Complete Guide With Working Examples

Laravel controllers are where your application’s logic lives. Laravel controllers are where your application’s logic lives. Understanding Laravel controllers is the step that makes everything click – routes decide where a request goes, views decide what the user sees, and controllers handle everything in between.

If you’re still writing logic directly inside route closures, controllers are the next step. This guide covers everything you need – creating controllers, connecting them to routes, passing data to views, resource controllers, and the best practices that keep Laravel projects maintainable as they grow.

Prerequisites

  • Laravel 10 or higher installed
  • Basic PHP knowledge
  • Familiarity with Laravel routing basics

All examples use a fresh Laravel installation. Run php artisan serve and follow along.

For official documentation see the Laravel Controllers documentation.

What is a Laravel Controller?

A controller is a PHP class that groups related request handling logic. Instead of this:

// routes/web.php - logic in routes (don't do this)
Route::get('/users', function() {
    $users = User::all();
    return view('users.index', compact('users'));
});

Route::post('/users', function(Request $request) {
    // validate, create user, redirect...
});

Route::delete('/users/{id}', function($id) {
    // find user, delete, redirect...
});

You get this:

// routes/web.php - clean routes pointing to controller
Route::get('/users',      [UserController::class, 'index']);
Route::post('/users',     [UserController::class, 'store']);
Route::delete('/users/{id}', [UserController::class, 'destroy']);

All the logic moves into UserController where it’s organized, testable, and reusable. Routes become a clean map of your application rather than a wall of anonymous functions.

MVC in Laravel – How Controllers Fit

Laravel follows the MVC (Model-View-Controller) pattern:

  • Model – represents database tables and handles data. Example: User, Product, Order
  • View – Blade templates that display data to the user
  • Controller – receives requests, talks to models, passes data to views, returns responses

The request flow:

Browser request
    → Route (routes/web.php)
        → Controller method
            → Model (database query)
                → View (Blade template)
                    → Response to browser

Creating Your First Controller

Use Artisan to generate a controller – don’t create the file manually:

php artisan make:controller UserController

Output:

INFO  Controller [app/Http/Controllers/UserController.php] created successfully.

Open the generated file:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserController extends Controller
{
    // Your methods go here
}

Every controller extends the base Controller class and lives in the App\Http\Controllers namespace. Artisan handles this automatically – manually created files often miss the namespace and fail silently.

Adding Methods and Connecting to Routes

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserController extends Controller
{
    public function index()
    {
        $users = [
            ['name' => 'Kapil Verma', 'email' => 'kapil@example.com'],
            ['name' => 'John Smith',  'email' => 'john@example.com'],
        ];

        return view('users.index', compact('users'));
    }

    public function show($id)
    {
        // In a real app: $user = User::findOrFail($id);
        return view('users.show', ['userId' => $id]);
    }
}

Connect to routes in routes/web.php:

<?php

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

Route::get('/users',      [UserController::class, 'index']);
Route::get('/users/{id}', [UserController::class, 'show']);

Visiting /users calls UserController@index. Visiting /users/5 calls UserController@show with $id = 5.

Passing Data to Blade Views

Three ways to pass data from a controller to a view:

<?php

class ProductController extends Controller
{
    public function index()
    {
        $products = [
            ['name' => 'Laptop', 'price' => 999],
            ['name' => 'Mouse',  'price' => 29],
        ];

        // Method 1: compact() - most common
        return view('products.index', compact('products'));

        // Method 2: with() - readable for single variables
        // return view('products.index')->with('products', $products);

        // Method 3: array - explicit
        // return view('products.index', ['products' => $products]);
    }
}

In the Blade view (resources/views/products/index.blade.php):

@foreach($products as $product)
    <div>
        <strong>{{ $product['name'] }}</strong>
        — ${{ $product['price'] }}
    </div>
@endforeach

compact('products') is shorthand for ['products' => $products]. Use it when passing multiple variables – it keeps the return statement clean.

Resource Controllers

For standard CRUD operations, resource controllers generate all seven methods automatically:

php artisan make:controller PostController --resource

This generates a controller with seven methods already stubbed out:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PostController extends Controller
{
    // GET /posts - list all posts
    public function index() {}

    // GET /posts/create - show create form
    public function create() {}

    // POST /posts - store new post
    public function store(Request $request) {}

    // GET /posts/{post} - show single post
    public function show(string $id) {}

    // GET /posts/{post}/edit - show edit form
    public function edit(string $id) {}

    // PUT/PATCH /posts/{post} - update post
    public function update(Request $request, string $id) {}

    // DELETE /posts/{post} - delete post
    public function destroy(string $id) {}
}

Register all seven routes with one line:

// routes/web.php
Route::resource('posts', PostController::class);

Verify the routes were created:

php artisan route:list --name=posts

Output:

GET|HEAD   posts ............. posts.index › PostController@index
POST       posts ............. posts.store › PostController@store
GET|HEAD   posts/create ...... posts.create › PostController@create
GET|HEAD   posts/{post} ...... posts.show › PostController@show
PUT|PATCH  posts/{post} ...... posts.update › PostController@update
DELETE     posts/{post} ...... posts.destroy › PostController@destroy
GET|HEAD   posts/{post}/edit . posts.edit › PostController@edit

If you only need some of these routes use only() or except():

// Only index and show
Route::resource('posts', PostController::class)
    ->only(['index', 'show']);

// Everything except destroy
Route::resource('posts', PostController::class)
    ->except(['destroy']);

Handling Form Input With Request

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ContactController extends Controller
{
    public function store(Request $request)
    {
        // Validate input
        $validated = $request->validate([
            'name'    => 'required|string|max:100',
            'email'   => 'required|email',
            'message' => 'required|string|min:10',
        ]);

        // $validated only contains the fields that passed
        // Safe to use - no raw $_POST needed

        // In a real app: ContactMessage::create($validated);

        return redirect()
            ->back()
            ->with('success', 'Message sent successfully.');
    }
}

Never access $_POST directly in Laravel. The Request object handles input sanitization, CSRF verification, and validation in one place. The validate() method automatically redirects back with errors if validation fails – no if/else needed.

Returning Different Response Types

<?php

class ApiController extends Controller
{
    // Return a view
    public function index()
    {
        return view('home');
    }

    // Return JSON - for API endpoints
    public function data()
    {
        return response()->json([
            'status' => 'ok',
            'data'   => ['key' => 'value'],
        ]);
    }

    // Redirect to another route
    public function store(Request $request)
    {
        // ... save data ...
        return redirect()->route('home')->with('success', 'Saved.');
    }

    // Return a string (useful for debugging)
    public function debug()
    {
        return 'Controller is working.';
    }

    // Return with HTTP status code
    public function notFound()
    {
        return response()->json(['error' => 'Not found'], 404);
    }
}

Debugging Controllers

When a controller method isn’t behaving as expected, these tools diagnose the problem quickly:

<?php

public function index()
{
    $users = User::all();

    // dd() - dump and die, stops execution and shows the value
    dd($users);

    // ddd() - same but with a nicer UI
    ddd($users);

    // dump() - shows the value but continues execution
    dump($users);

    return view('users.index', compact('users'));
}

Check which routes are registered when a URL returns 404:

php artisan route:list

Clear cached routes when route changes don’t take effect:

php artisan route:clear
php artisan config:clear

Best Practices

  • One responsibility per method – each controller method does one thing. If a method is over 30 lines it probably needs to be split or moved to a service class.
  • Keep controllers thin – business logic belongs in service classes or models, not controllers. Controllers receive requests and return responses. That’s it.
  • Use resource controllers for CRUD – don’t invent custom method names when the standard seven cover your use case.
  • Validate in controllers – or in Form Request classes for complex validation. Never trust raw input.
  • Return typed responsesview() for web, response()->json() for APIs, redirect() after form submissions.

Frequently Asked Questions

What is the difference between a controller and a route in Laravel?

A route maps a URL and HTTP method to a handler. A controller is the handler. Routes are defined in routes/web.php and point to controller methods. The route decides which controller method runs – the controller method decides what happens when it runs.

Should I use resource controllers or regular controllers?

Use resource controllers for standard CRUD operations – anything with create, read, update, delete on a model. Use regular controllers for anything that doesn’t map cleanly to CRUD – authentication flows, API endpoints with custom logic, or single-purpose controllers with one or two methods.

Why does my controller return a blank page?

Most likely the view path is wrong. return view('users.index') looks for resources/views/users/index.blade.php. Dots map to directory separators. Check that the file exists at exactly that path and has the .blade.php extension. Also check that the variable names passed with compact() match what the Blade template expects.

What is the difference between a controller and a middleware?

A controller handles what happens when a request is processed. Middleware handles what happens before or after the controller runs – authentication checks, logging, rate limiting, CORS headers. Middleware filters requests. Controllers process them.

How do I share data across all controller views?

Use View::share() in a service provider or use view composers. In AppServiceProvider::boot():

<?php

use Illuminate\Support\Facades\View;

public function boot(): void
{
    View::share('siteName', config('app.name'));
}

Now $siteName is available in every Blade template without passing it from each controller method.


Summary: Laravel Controllers Best Practices

Laravel controllers organize your application’s request handling logic into clean, reusable classes. The key points:

  • Always use Artisan to create controllers – php artisan make:controller handles namespace and boilerplate automatically
  • Resource controllers give you seven standard CRUD methods and routes in one command
  • Keep controllers thin – receive input, call services or models, return responses
  • Use Request validation – never access $_POST directly
  • Debug with dd() and php artisan route:list when things don’t work as expected

The next logical step after controllers is understanding middleware – how to protect routes, authenticate users, and run logic before controllers execute. The Laravel middleware guide covers all of that. For putting controllers into practice with a complete working application, the Laravel CRUD guide builds a full create, read, update, delete feature using resource controllers from start to finish.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top