Md Mominul Islam | Software and Data Enginnering | SQL Server, .NET, Power BI, Azure Blog

while(!(succeed=try()));

LinkedIn Portfolio Banner

Latest

Home Top Ad

Responsive Ads Here

Post Top Ad

Responsive Ads Here

Thursday, August 28, 2025

Laravel Core Complete Course: Module 6 – Mastering Controllers & Middleware for Robust Web Applications

 

Table of Contents

  1. Introduction to Controllers & Middleware in Laravel

  2. Creating Controllers: From Basics to Advanced

    • What Are Controllers?

    • Creating a Basic Controller

    • Resource Controllers for CRUD Operations

    • Single-Action Controllers for Focused Tasks

    • Real-World Example: Building an E-commerce Product Controller

    • Pros, Cons, and Alternatives

    • Best Practices for Controllers

  3. Controller Methods & Dependency Injection

    • Understanding Controller Methods

    • Dependency Injection in Controllers

    • Real-World Example: Dependency Injection in a Task Manager

    • Pros, Cons, and Alternatives

    • Best Practices for Dependency Injection

  4. Middleware Overview

    • What Is Middleware?

    • How Middleware Works in Laravel

    • Types of Middleware: Global, Route, and Group

    • Real-World Example: Middleware in an API Authentication Flow

    • Pros, Cons, and Alternatives

    • Best Practices for Middleware

  5. Applying Middleware to Routes & Groups

    • Assigning Middleware to Routes

    • Using Middleware Groups

    • Real-World Example: Protecting Admin Routes in a CMS

    • Pros, Cons, and Alternatives

    • Best Practices for Middleware Application

  6. Creating Custom Middleware

    • Generating Custom Middleware

    • Custom Middleware for Real-World Scenarios

    • Real-World Example: Rate Limiting API Requests

    • Pros, Cons, and Alternatives

    • Best Practices for Custom Middleware

  7. Conclusion: Building Robust Laravel Applications

  8. Additional Resources & Further Reading


Introduction to Controllers & Middleware in Laravel

Laravel, a leading PHP framework, is renowned for its elegant syntax and robust features, making it a favorite for building scalable web applications. In Module 6 of our Laravel Core Complete Course, we dive into two critical components of Laravel’s MVC architecture: Controllers and Middleware. These tools are essential for handling HTTP requests, implementing business logic, and securing your application.

  • Controllers act as the traffic directors of your application, managing user requests and coordinating responses between models and views.

  • Middleware serves as a filtering mechanism, allowing you to inspect, modify, or block HTTP requests before they reach your controllers or after responses are generated.

This tutorial is designed to be interactive, practical, and real-world focused. We’ll build examples like an e-commerce product management system, a task manager, and an API with authentication, covering scenarios from beginner to advanced. Whether you’re building a simple blog or a complex SaaS platform, this guide will equip you with the knowledge to leverage controllers and middleware effectively.


Creating Controllers: From Basics to Advanced

What Are Controllers?

Controllers in Laravel are PHP classes that handle HTTP requests and orchestrate the flow of data between models (data layer) and views (presentation layer). They follow the Model-View-Controller (MVC) pattern, ensuring a clean separation of concerns. By centralizing request-handling logic, controllers keep your routes clean and your application maintainable.

Creating a Basic Controller

Let’s start with a simple controller to handle a user profile page.

Example 1: Basic User Profile Controller

  1. Generate a Controller: Use Laravel’s Artisan command to create a controller:

    php artisan make:controller UserController
  2. Define the Controller: The generated controller is located in app/Http/Controllers/UserController.php. Let’s add a method to display a user’s profile.

    namespace App\Http\Controllers;
    
    use Illuminate\Http\Request;
    
    class UserController extends Controller
    {
        public function showProfile()
        {
            return view('profile', ['name' => 'John Doe']);
        }
    }
  3. Create a Route: In routes/web.php, define a route to access the profile:

    use App\Http\Controllers\UserController;
    
    Route::get('/profile', [UserController::class, 'showProfile']);
  4. Create a View: In resources/views/profile.blade.php, add:

    <!DOCTYPE html>
    <html>
    <head>
        <title>User Profile</title>
    </head>
    <body>
        <h1>Welcome, {{ $name }}!</h1>
    </body>
    </html>
  5. Test the Application: Run php artisan serve and visit http://localhost:8000/profile. You’ll see “Welcome, John Doe!” displayed.

Why Use a Basic Controller?

  • Beginner-Friendly: Simple to set up and understand.

  • Separation of Concerns: Keeps routing logic separate from business logic.

Resource Controllers for CRUD Operations

Resource controllers are ideal for handling CRUD (Create, Read, Update, Delete) operations. Laravel provides a single Artisan command to scaffold a controller with methods for standard operations.

Example 2: Resource Controller for a Blog Application

Let’s create a resource controller for managing blog posts.

  1. Generate a Resource Controller:

    php artisan make:controller PostController --resource

    This generates a controller with methods: index, create, store, show, edit, update, and destroy.

  2. Define Routes: In routes/web.php, register the resource controller:

    use App\Http\Controllers\PostController;
    
    Route::resource('posts', PostController::class);

    This automatically maps routes to controller methods:

    • GET /postsindex

    • GET /posts/createcreate

    • POST /postsstore

    • GET /posts/{post}show

    • GET /posts/{post}/editedit

    • PUT/PATCH /posts/{post}update

    • DELETE /posts/{post}destroy

  3. Implement the Controller: Update app/Http/Controllers/PostController.php to handle CRUD operations. Assume a Post model exists with a migration for a posts table (title, content, user_id).

    namespace App\Http\Controllers;
    
    use App\Models\Post;
    use Illuminate\Http\Request;
    
    class PostController extends Controller
    {
        public function index()
        {
            $posts = Post::all();
            return view('posts.index', compact('posts'));
        }
    
        public function create()
        {
            return view('posts.create');
        }
    
        public function store(Request $request)
        {
            $validated = $request->validate([
                'title' => 'required|string|max:255',
                'content' => 'required|string',
            ]);
    
            Post::create([
                'title' => $validated['title'],
                'content' => $validated['content'],
                'user_id' => auth()->id(),
            ]);
    
            return redirect()->route('posts.index')->with('success', 'Post created!');
        }
    
        public function show(Post $post)
        {
            return view('posts.show', compact('post'));
        }
    
        public function edit(Post $post)
        {
            return view('posts.edit', compact('post'));
        }
    
        public function update(Request $request, Post $post)
        {
            $validated = $request->validate([
                'title' => 'required|string|max:255',
                'content' => 'required|string',
            ]);
    
            $post->update($validated);
            return redirect()->route('posts.index')->with('success', 'Post updated!');
        }
    
        public function destroy(Post $post)
        {
            $post->delete();
            return redirect()->route('posts.index')->with('success', 'Post deleted!');
        }
    }
  4. Create Views:

    • resources/views/posts/index.blade.php:

      <h1>Blog Posts</h1>
      @if (session('success'))
          <div class="alert alert-success">{{ session('success') }}</div>
      @endif
      <a href="{{ route('posts.create') }}">Create New Post</a>
      <ul>
          @foreach ($posts as $post)
              <li>
                  {{ $post->title }} 
                  <a href="{{ route('posts.show', $post) }}">View</a>
                  <a href="{{ route('posts.edit', $post) }}">Edit</a>
                  <form action="{{ route('posts.destroy', $post) }}" method="POST">
                      @csrf
                      @method('DELETE')
                      <button type="submit">Delete</button>
                  </form>
              </li>
          @endforeach
      </ul>
    • resources/views/posts/create.blade.php:

      <h1>Create Post</h1>
      <form action="{{ route('posts.store') }}" method="POST">
          @csrf
          <input type="text" name="title" placeholder="Title">
          <textarea name="content" placeholder="Content"></textarea>
          <button type="submit">Save</button>
      </form>
    • Similar views for show and edit.

  5. Test the Application: Run php artisan migrate to set up the database, then visit http://localhost:8000/posts. You can create, view, edit, and delete posts.

Why Use Resource Controllers?

  • Efficiency: Scaffolds CRUD routes and methods automatically.

  • Consistency: Follows RESTful conventions, making your API predictable.

  • Scalability: Handles standard operations for any resource (e.g., posts, products, users).

Single-Action Controllers for Focused Tasks

Single-action controllers are used for handling a single task, keeping your codebase lean and focused. They’re ideal for one-off actions like sending a notification or generating a report.

Example 3: Single-Action Controller for Sending Welcome Emails

  1. Generate a Single-Action Controller:

    php artisan make:controller SendWelcomeEmailController --invokable
  2. Define the Controller: In app/Http/Controllers/SendWelcomeEmailController.php:

    namespace App\Http\Controllers;
    
    use App\Models\User;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Mail;
    
    class SendWelcomeEmailController extends Controller
    {
        public function __invoke(Request $request, User $user)
        {
            Mail::to($user->email)->send(new \App\Mail\WelcomeEmail($user));
            return redirect()->back()->with('success', 'Welcome email sent!');
        }
    }
  3. Create a Mailable:

    php artisan make:mail WelcomeEmail

    In app/Mail/WelcomeEmail.php:

    namespace App\Mail;
    
    use App\Models\User;
    use Illuminate\Bus\Queueable;
    use Illuminate\Mail\Mailable;
    use Illuminate\Queue\SerializesModels;
    
    class WelcomeEmail extends Mailable
    {
        use Queueable, SerializesModels;
    
        public $user;
    
        public function __construct(User $user)
        {
            $this->user = $user;
        }
    
        public function build()
        {
            return $this->subject('Welcome to Our Platform!')
                        ->view('emails.welcome');
        }
    }

    In resources/views/emails/welcome.blade.php:

    <h1>Welcome, {{ $user->name }}!</h1>
    <p>Thank you for joining our platform.</p>
  4. Define a Route: In routes/web.php:

    use App\Http\Controllers\SendWelcomeEmailController;
    
    Route::post('/send-welcome-email/{user}', SendWelcomeEmailController::class);
  5. Test the Application: Create a form to trigger the email, test with a user, and verify the email is sent.

Why Use Single-Action Controllers?

  • Simplicity: Perfect for tasks with a single responsibility.

  • Clarity: Makes the codebase easier to understand by isolating specific actions.

Real-World Example: Building an E-commerce Product Controller

Let’s build a resource controller for an e-commerce platform to manage products, including advanced features like filtering and pagination.

  1. Generate the Controller and Model:

    php artisan make:controller ProductController --resource --model=Product
    php artisan make:model Product -m
  2. Define the Migration: In database/migrations/xxxx_create_products_table.php:

    Schema::create('products', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->text('description')->nullable();
        $table->decimal('price', 8, 2);
        $table->integer('stock');
        $table->timestamps();
    });
  3. Implement the Controller: In app/Http/Controllers/ProductController.php:

    namespace App\Http\Controllers;
    
    use App\Models\Product;
    use Illuminate\Http\Request;
    
    class ProductController extends Controller
    {
        public function index(Request $request)
        {
            $query = Product::query();
    
            if ($request->has('search')) {
                $query->where('name', 'like', '%' . $request->search . '%');
            }
    
            $products = $query->paginate(10);
            return view('products.index', compact('products'));
        }
    
        public function create()
        {
            return view('products.create');
        }
    
        public function store(Request $request)
        {
            $validated = $request->validate([
                'name' => 'required|string|max:255',
                'description' => 'nullable|string',
                'price' => 'required|numeric|min:0',
                'stock' => 'required|integer|min:0',
            ]);
    
            Product::create($validated);
            return redirect()->route('products.index')->with('success', 'Product added!');
        }
    
        public function show(Product $product)
        {
            return view('products.show', compact('product'));
        }
    
        public function edit(Product $product)
        {
            return view('products.edit', compact('product'));
        }
    
        public function update(Request $request, Product $product)
        {
            $validated = $request->validate([
                'name' => 'required|string|max:255',
                'description' => 'nullable|string',
                'price' => 'required|numeric|min:0',
                'stock' => 'required|integer|min:0',
            ]);
    
            $product->update($validated);
            return redirect()->route('products.index')->with('success', 'Product updated!');
        }
    
        public function destroy(Product $product)
        {
            $product->delete();
            return redirect()->route('products.index')->with('success', 'Product deleted!');
        }
    }
  4. Create Views:

    • resources/views/products/index.blade.php:

      <h1>Products</h1>
      <form action="{{ route('products.index') }}" method="GET">
          <input type="text" name="search" placeholder="Search products...">
          <button type="submit">Search</button>
      </form>
      @if (session('success'))
          <div class="alert alert-success">{{ session('success') }}</div>
      @endif
      <a href="{{ route('products.create') }}">Add Product</a>
      <table>
          <thead>
              <tr>
                  <th>Name</th>
                  <th>Price</th>
                  <th>Stock</th>
                  <th>Actions</th>
              </tr>
          </thead>
          <tbody>
              @foreach ($products as $product)
                  <tr>
                      <td>{{ $product->name }}</td>
                      <td>{{ $product->price }}</td>
                      <td>{{ $product->stock }}</td>
                      <td>
                          <a href="{{ route('products.show', $product) }}">View</a>
                          <a href="{{ route('products.edit', $product) }}">Edit</a>
                          <form action="{{ route('products.destroy', $product) }}" method="POST">
                              @csrf
                              @method('DELETE')
                              <button type="submit">Delete</button>
                          </form>
                      </td>
                  </tr>
              @endforeach
          </tbody>
      </table>
      {{ $products->links() }}
    • Similar views for create, show, and edit.

  5. Test the Application: Run migrations, start the server, and test CRUD operations with search and pagination.

Why This Example?

  • Real-World Relevance: Mimics a typical e-commerce product management system.

  • Advanced Features: Incorporates search and pagination for scalability.

Pros, Cons, and Alternatives for Controllers

Pros:

  • Organization: Centralizes request-handling logic, improving code maintainability.

  • Reusability: Resource controllers reduce repetitive code for CRUD operations.

  • Scalability: Single-action controllers keep focused tasks isolated.

Cons:

  • Complexity: Resource controllers can become bloated for complex resources.

  • Overhead: Single-action controllers may lead to many small classes for large apps.

  • Learning Curve: Beginners may struggle with resource controller conventions.

Alternatives:

  • Closure-Based Routes: Define logic directly in routes/web.php for simple apps.

    Route::get('/profile', function () {
        return view('profile', ['name' => 'John Doe']);
    });
  • Invokable Classes: Use invokable classes outside controllers for specific tasks.

  • Other Frameworks: Symfony or CodeIgniter offer similar controller patterns but lack Laravel’s elegance.

Best Practices for Controllers

  • Keep Controllers Lean: Move business logic to services or repositories.

  • Use Resource Controllers for CRUD: Leverage Laravel’s scaffolding for standard operations.

  • Name Methods Clearly: Use descriptive names like store or update for clarity.

  • Validate Input: Always validate requests before processing.

  • Follow RESTful Conventions: Ensure routes and methods align with REST standards.


Controller Methods & Dependency Injection

Understanding Controller Methods

Controller methods handle specific HTTP requests (e.g., GET, POST) and return responses (views, JSON, redirects). Common methods include:

  • index: List all resources.

  • store: Create a new resource.

  • show: Display a single resource.

  • update: Update an existing resource.

  • destroy: Delete a resource.

Dependency Injection in Controllers

Dependency injection (DI) allows you to inject dependencies (e.g., services, repositories) into controllers via the constructor or method signatures. Laravel’s service container resolves these dependencies automatically.

Example 4: Dependency Injection in a User Controller

  1. Create a Service: Create a UserService to handle user-related logic:

    namespace App\Services;
    
    use App\Models\User;
    
    class UserService
    {
        public function getActiveUsers()
        {
            return User::where('is_active', true)->get();
        }
    }
  2. Inject the Service into a Controller: In app/Http/Controllers/UserController.php:

    namespace App\Http\Controllers;
    
    use App\Services\UserService;
    use Illuminate\Http\Request;
    
    class UserController extends Controller
    {
        protected $userService;
    
        public function __construct(UserService $userService)
        {
            $this->userService = $userService;
        }
    
        public function index()
        {
            $users = $this->userService->getActiveUsers();
            return view('users.index', compact('users'));
        }
    }
  3. Create a View: In resources/views/users/index.blade.php:

    <h1>Active Users</h1>
    <ul>
        @foreach ($users as $user)
            <li>{{ $user->name }}</li>
        @endforeach
    </ul>
  4. Define a Route: In routes/web.php:

    use App\Http\Controllers\UserController;
    
    Route::get('/users', [UserController::class, 'index']);
  5. Test the Application: Ensure the User model has an is_active column, seed the database, and visit /users.

Why Use Dependency Injection?

  • Modularity: Separates business logic from controllers.

  • Testability: Makes it easier to mock dependencies in tests.

  • Reusability: Services can be used across multiple controllers.

Real-World Example: Dependency Injection in a Task Manager

Let’s build a task manager where a TaskService handles task creation and notifications.

  1. Create a Task Model and Migration:

    php artisan make:model Task -m

    In database/migrations/xxxx_create_tasks_table.php:

    Schema::create('tasks', function (Blueprint $table) {
        $table->id();
        $table->string('title');
        $table->text('description')->nullable();
        $table->boolean('completed')->default(false);
        $table->foreignId('user_id')->constrained()->onDelete('cascade');
        $table->timestamps();
    });
  2. Create a Task Service: In app/Services/TaskService.php:

    namespace App\Services;
    
    use App\Models\Task;
    use Illuminate\Support\Facades\Mail;
    use App\Mail\TaskCreated;
    
    class TaskService
    {
        public function createTask(array $data, $user)
        {
            $task = Task::create([
                'title' => $data['title'],
                'description' => $data['description'],
                'user_id' => $user->id,
            ]);
    
            Mail::to($user->email)->send(new TaskCreated($task));
    
            return $task;
        }
    }
  3. Create a Mailable:

    php artisan make:mail TaskCreated

    In app/Mail/TaskCreated.php:

    namespace App\Mail;
    
    use App\Models\Task;
    use Illuminate\Bus\Queueable;
    use Illuminate\Mail\Mailable;
    use Illuminate\Queue\SerializesModels;
    
    class TaskCreated extends Mailable
    {
        use Queueable, SerializesModels;
    
        public $task;
    
        public function __construct(Task $task)
        {
            $this->task = $task;
        }
    
        public function build()
        {
            return $this->subject('New Task Created')
                        ->view('emails.task-created');
        }
    }

    In resources/views/emails/task-created.blade.php:

    <h1>New Task: {{ $task->title }}</h1>
    <p>{{ $task->description }}</p>
  4. Create a Resource Controller:

    php artisan make:controller TaskController --resource

    In app/Http/Controllers/TaskController.php:

    namespace App\Http\Controllers;
    
    use App\Models\Task;
    use App\Services\TaskService;
    use Illuminate\Http\Request;
    
    class TaskController extends Controller
    {
        protected $taskService;
    
        public function __construct(TaskService $taskService)
        {
            $this->taskService = $taskService;
        }
    
        public function index()
        {
            $tasks = Task::where('user_id', auth()->id())->get();
            return view('tasks.index', compact('tasks'));
        }
    
        public function store(Request $request)
        {
            $validated = $request->validate([
                'title' => 'required|string|max:255',
                'description' => 'nullable|string',
            ]);
    
            $this->taskService->createTask($validated, auth()->user());
            return redirect()->route('tasks.index')->with('success', 'Task created!');
        }
    }
  5. Define Routes: In routes/web.php:

    use App\Http\Controllers\TaskController;
    
    Route::resource('tasks', TaskController::class)->middleware('auth');
  6. Create Views:

    • resources/views/tasks/index.blade.php:

      <h1>My Tasks</h1>
      @if (session('success'))
          <div class="alert alert-success">{{ session('success') }}</div>
      @endif
      <a href="{{ route('tasks.create') }}">Create Task</a>
      <ul>
          @foreach ($tasks as $task)
              <li>{{ $task->title }} ({{ $task->completed ? 'Completed' : 'Pending' }})</li>
          @endforeach
      </ul>
    • resources/views/tasks/create.blade.php:

      <h1>Create Task</h1>
      <form action="{{ route('tasks.store') }}" method="POST">
          @csrf
          <input type="text" name="title" placeholder="Task Title">
          <textarea name="description" placeholder="Description"></textarea>
          <button type="submit">Save</button>
      </form>
  7. Test the Application: Set up authentication (e.g., Laravel Breeze), run migrations, and test task creation with email notifications.

Why This Example?

  • Real-World Application: Task managers are common in project management tools.

  • Dependency Injection: Demonstrates injecting a service to handle complex logic.

  • Integration: Combines controllers, services, and mailables.

Pros, Cons, and Alternatives for Dependency Injection

Pros:

  • Loose Coupling: Dependencies are injected, not hard-coded, improving flexibility.

  • Testability: Easier to mock services in unit tests.

  • Reusability: Services can be reused across controllers or other parts of the app.

Cons:

  • Complexity: Adds overhead for small applications.

  • Learning Curve: Beginners may find DI confusing.

  • Overhead: Requires additional classes for simple logic.

Alternatives:

  • Facade Pattern: Use Laravel facades (e.g., Mail::send()) for simpler tasks.

    Mail::to($user->email)->send(new TaskCreated($task));
  • Static Methods: Use static methods in helper classes, though less testable.

  • Other Frameworks: Symfony’s DI container is similar but more explicit.

Best Practices for Dependency Injection

  • Inject Only Necessary Dependencies: Avoid injecting unused services.

  • Use Interfaces: Define interfaces for services to allow swapping implementations.

  • Keep Constructors Clean: Limit constructor arguments to essential dependencies.

  • Leverage Laravel’s Service Container: Let Laravel resolve dependencies automatically.


Middleware Overview

What Is Middleware?

Middleware in Laravel acts as a bridge between HTTP requests and responses, allowing you to inspect, filter, or modify requests before they reach controllers or after responses are generated. Think of middleware as gatekeepers that enforce rules like authentication, rate limiting, or logging.

How Middleware Works in Laravel

Middleware is executed in a stack:

  • Before Middleware: Runs before the controller (e.g., checking authentication).

  • After Middleware: Runs after the controller generates a response (e.g., adding headers).

Laravel includes built-in middleware like auth, throttle, and csrf, but you can create custom middleware for specific needs.

Types of Middleware: Global, Route, and Group

  • Global Middleware: Applied to all requests (defined in app/Http/Kernel.php).

  • Route Middleware: Applied to specific routes or controllers.

  • Group Middleware: Applied to a group of routes for shared functionality.

Example 5: Using Built-In Authentication Middleware

  1. Apply Middleware to a Route: In routes/web.php:

    Route::get('/dashboard', function () {
        return view('dashboard');
    })->middleware('auth');
  2. Test the Application: Without logging in, visiting /dashboard redirects to the login page. After logging in, the dashboard is accessible.

Why Use Middleware?

  • Security: Enforce authentication or authorization.

  • Reusability: Apply logic across multiple routes.

  • Flexibility: Customize request/response handling.

Real-World Example: Middleware in an API Authentication Flow

Let’s use Laravel’s auth:api middleware to secure an API endpoint.

  1. Set Up API Routes: In routes/api.php:

    use App\Http\Controllers\ProductController;
    
    Route::middleware('auth:api')->get('/products', [ProductController::class, 'index']);
  2. Test with an API Client: Use a tool like Postman to send a GET request to /api/products with a valid API token. Without a token, you’ll get a 401 Unauthorized response.

Why This Example?

  • API Security: Demonstrates protecting API endpoints, common in modern apps.

  • Real-World Use: APIs are critical for mobile apps and third-party integrations.

Pros, Cons, and Alternatives for Middleware

Pros:

  • Centralized Logic: Keeps request-handling logic out of controllers.

  • Reusability: Apply middleware to multiple routes or groups.

  • Extensibility: Easy to create custom middleware for specific needs.

Cons:

  • Complexity: Overusing middleware can make debugging harder.

  • Performance: Too many middleware layers can slow down requests.

  • Learning Curve: Understanding middleware execution order takes time.

Alternatives:

  • Controller Logic: Handle checks within controllers, though less reusable.

    public function index(Request $request)
    {
        if (!auth()->check()) {
            return redirect('/login');
        }
        // Logic here
    }
  • Gates and Policies: Use Laravel’s authorization features for fine-grained control.

  • Other Frameworks: Express.js (Node.js) uses similar middleware patterns.

Best Practices for Middleware

  • Keep Middleware Focused: Each middleware should have a single responsibility.

  • Use Meaningful Names: Name middleware descriptively (e.g., EnsureUserIsAdmin).

  • Optimize Performance: Avoid heavy logic in middleware to minimize latency.

  • Document Middleware: Clearly document custom middleware functionality.


Applying Middleware to Routes & Groups

Assigning Middleware to Routes

You can assign middleware to individual routes or controllers using the middleware method.

Example 6: Restricting Access to Admin Routes

  1. Define a Route with Middleware: In routes/web.php:

    use App\Http\Controllers\AdminController;
    
    Route::get('/admin', [AdminController::class, 'index'])->middleware('auth');
  2. Create the Controller:

    php artisan make:controller AdminController

    In app/Http/Controllers/AdminController.php:

    namespace App\Http\Controllers;
    
    class AdminController extends Controller
    {
        public function index()
        {
            return view('admin.index');
        }
    }
  3. Create a View: In resources/views/admin/index.blade.php:

    <h1>Admin Dashboard</h1>
    <p>Welcome, {{ auth()->user()->name }}!</p>
  4. Test the Application: Only authenticated users can access /admin.

Using Middleware Groups

Middleware groups bundle multiple middleware for shared functionality, such as protecting web routes or APIs.

Example 7: Applying Middleware Groups

  1. Define a Middleware Group: In app/Http/Kernel.php, add a custom group:

    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
        'api' => [
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
        'admin' => [
            'auth',
            \App\Http\Middleware\EnsureUserIsAdmin::class,
        ],
    ];
  2. Apply the Group to Routes: In routes/web.php:

    Route::middleware('admin')->group(function () {
        Route::get('/admin', [AdminController::class, 'index']);
        Route::get('/admin/settings', [AdminController::class, 'settings']);
    });
  3. Test the Application: Only authenticated admin users can access the admin routes.

Real-World Example: Protecting Admin Routes in a CMS

Let’s build a CMS where only admins can access certain routes.

  1. Create a Custom Middleware:

    php artisan make:middleware EnsureUserIsAdmin

    In app/Http/Middleware/EnsureUserIsAdmin.php:

    namespace App\Http\Middleware;
    
    use Closure;
    use Illuminate\Http\Request;
    use Symfony\Component\HttpFoundation\Response;
    
    class EnsureUserIsAdmin
    {
        public function handle(Request $request, Closure $next): Response
        {
            if (!auth()->user()->is_admin) {
                return redirect('/home')->with('error', 'Unauthorized');
            }
            return $next($request);
        }
    }
  2. Register the Middleware: In app/Http/Kernel.php:

    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'admin' => \App\Http\Middleware\EnsureUserIsAdmin::class,
        // Other middleware
    ];
  3. Apply to Routes: In routes/web.php:

    Route::middleware(['auth', 'admin'])->group(function () {
        Route::get('/admin', [AdminController::class, 'index']);
    });
  4. Test the Application: Add an is_admin column to the users table, set it for a test user, and verify only admins can access /admin.

Why This Example?

  • Real-World Use: CMS platforms often require role-based access.

  • Middleware Groups: Demonstrates grouping middleware for efficiency.

Pros, Cons, and Alternatives for Middleware Assignment

Pros:

  • Flexibility: Apply middleware to specific routes or groups.

  • Reusability: Middleware groups reduce repetitive code.

  • Security: Easily enforce access control across routes.

Cons:

  • Complexity: Managing multiple middleware groups can be confusing.

  • Order Sensitivity: Middleware execution order matters and can cause issues.

  • Maintenance: Overusing groups can make debugging harder.

Alternatives:

  • Controller-Based Checks: Perform checks within controllers, though less reusable.

  • Policies and Gates: Use Laravel’s authorization system for fine-grained control.

  • Other Frameworks: Express.js allows similar middleware assignment but with less structure.

Best Practices for Middleware Application

  • Use Groups for Shared Logic: Group related middleware for clarity.

  • Apply Middleware Sparingly: Avoid applying unnecessary middleware to routes.

  • Test Middleware Order: Ensure middleware executes in the correct order.

  • Document Middleware Usage: Clearly document which routes use which middleware.


Creating Custom Middleware

Generating Custom Middleware

Custom middleware allows you to implement specific logic, such as rate limiting, logging, or custom authentication.

Example 8: Creating a Logging Middleware

  1. Generate Middleware:

    php artisan make:middleware LogRequests
  2. Define the Middleware: In app/Http/Middleware/LogRequests.php:

    namespace App\Http\Middleware;
    
    use Closure;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Log;
    
    class LogRequests
    {
        public function handle(Request $request, Closure $next)
        {
            Log::info('Request Logged', [
                'url' => $request->url(),
                'method' => $request->method(),
                'user' => auth()->user()?->email,
            ]);
    
            return $next($request);
        }
    }
  3. Register the Middleware: In app/Http/Kernel.php:

    protected $routeMiddleware = [
        'log' => \App\Http\Middleware\LogRequests::class,
        // Other middleware
    ];
  4. Apply to Routes: In routes/web.php:

    Route::get('/dashboard', function () {
        return view('dashboard');
    })->middleware('log');
  5. Test the Application: Check the logs in storage/logs/laravel.log to verify request logging.

Custom Middleware for Real-World Scenarios

Let’s create a middleware to restrict access based on user subscription status.

Example 9: Subscription Middleware

  1. Generate Middleware:

    php artisan make:middleware EnsureUserIsSubscribed
  2. Define the Middleware: In app/Http/Middleware/EnsureUserIsSubscribed.php:

    namespace App\Http\Middleware;
    
    use Closure;
    use Illuminate\Http\Request;
    use Symfony\Component\HttpFoundation\Response;
    
    class EnsureUserIsSubscribed
    {
        public function handle(Request $request, Closure $next): Response
        {
            if (!auth()->user()->is_subscribed) {
                return redirect('/subscribe')->with('error', 'Please subscribe to access this feature.');
            }
            return $next($request);
        }
    }
  3. Register and Apply: In app/Http/Kernel.php:

    protected $routeMiddleware = [
        'subscribed' => \App\Http\Middleware\EnsureUserIsSubscribed::class,
    ];

    In routes/web.php:

    Route::middleware('subscribed')->group(function () {
        Route::get('/premium-content', function () {
            return view('premium');
        });
    });
  4. Test the Application: Add an is_subscribed column to the users table, test with subscribed and unsubscribed users.

Real-World Example: Rate Limiting API Requests

Let’s create a custom middleware to limit API requests per user, useful for preventing abuse in public APIs.

  1. Generate Middleware:

    php artisan make:middleware RateLimitApi
  2. Define the Middleware: In app/Http/Middleware/RateLimitApi.php:

    namespace App\Http\Middleware;
    
    use Closure;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Cache;
    use Symfony\Component\HttpFoundation\Response;
    
    class RateLimitApi
    {
        public function handle(Request $request, Closure $next, $limit = 60, $window = 60): Response
        {
            $key = 'api:' . $request->user()->id;
            $requests = Cache::get($key, 0);
    
            if ($requests >= $limit) {
                return response()->json(['error' => 'Rate limit exceeded'], 429);
            }
    
            Cache::put($key, $requests + 1, $window);
            return $next($request);
        }
    }
  3. Register the Middleware: In app/Http/Kernel.php:

    protected $routeMiddleware = [
        'api.limit' => \App\Http\Middleware\RateLimitApi::class,
    ];
  4. Apply to API Routes: In routes/api.php:

    Route::middleware('api.limit:100,60')->get('/data', function () {
        return response()->json(['data' => 'Protected API']);
    });
  5. Test the Application: Use Postman to send multiple requests and verify the rate limit (100 requests per minute).

Why This Example?

  • Real-World Relevance: Rate limiting is critical for API security.

  • Advanced Features: Uses Laravel’s Cache facade for state management.

Pros, Cons, and Alternatives for Custom Middleware

Pros:

  • Customization: Tailor middleware to specific application needs.

  • Reusability: Apply custom middleware across routes or groups.

  • Control: Fine-tune request and response handling.

Cons:

  • Complexity: Custom middleware requires careful testing to avoid bugs.

  • Maintenance: Increases codebase size and maintenance overhead.

  • Performance: Heavy middleware logic can impact performance.

Alternatives:

  • Built-In Middleware: Use Laravel’s throttle middleware for rate limiting.

    Route::middleware('throttle:60,1')->get('/data', function () {
        return response()->json(['data' => 'Protected API']);
    });
  • Service Classes: Handle logic in services instead of middleware for complex tasks.

  • Other Frameworks: Express.js allows custom middleware but lacks Laravel’s structure.

Best Practices for Custom Middleware

  • Single Responsibility: Each middleware should handle one task.

  • Use Parameters: Allow middleware to accept parameters for flexibility (e.g., rate limit values).

  • Handle Errors Gracefully: Return meaningful responses for failed checks.

  • Test Thoroughly: Write unit tests for custom middleware to ensure reliability.


Conclusion: Building Robust Laravel Applications

In Module 6: Controllers & Middleware, we’ve explored how to harness Laravel’s controllers and middleware to build robust, secure, and maintainable web applications. From basic controllers to resource and single-action controllers, dependency injection, and custom middleware, you’ve learned techniques to handle real-world scenarios like e-commerce platforms, task managers, and APIs. By following best practices and understanding pros, cons, and alternatives, you can create scalable applications that adhere to Laravel’s elegant philosophy.

No comments:

Post a Comment

Thanks for your valuable comment...........
Md. Mominul Islam

Post Bottom Ad

Responsive Ads Here