---
url: >-
  https://docs.corbitaltech.dev/products/whatsmark-saas/customization/custom-module/routing-controllers.md
description: >-
  Technical guide for implementing web routes, API routes, and controllers in
  WhatsMarkSaaS custom modules
---

# Routing & Controllers

::: warning Minimum Version Requirement
This feature requires **WhatsMarkSaaS v2.0.0 or higher**. Ensure your installation meets this requirement before proceeding.
:::

## Overview

This guide covers route definition and controller implementation for custom modules. You will learn to create web routes, API endpoints, apply middleware, and handle requests with tenant-aware controllers.

## Web Routes

Web routes are defined in `Routes/web.php` and handle browser-based requests.

### Basic Route Definition

```php
<?php

use Illuminate\Support\Facades\Route;
use Modules\OrderTracker\Http\Controllers\OrderTrackerController;

Route::middleware('web')->group(function () {
    Route::prefix('order-tracker')->group(function () {
        Route::get('/', [OrderTrackerController::class, 'index'])
            ->name('order-tracker.index');

        Route::get('/create', [OrderTrackerController::class, 'create'])
            ->name('order-tracker.create');

        Route::post('/store', [OrderTrackerController::class, 'store'])
            ->name('order-tracker.store');

        Route::get('/{id}', [OrderTrackerController::class, 'show'])
            ->name('order-tracker.show');

        Route::get('/{id}/edit', [OrderTrackerController::class, 'edit'])
            ->name('order-tracker.edit');

        Route::put('/{id}', [OrderTrackerController::class, 'update'])
            ->name('order-tracker.update');

        Route::delete('/{id}', [OrderTrackerController::class, 'destroy'])
            ->name('order-tracker.destroy');
    });
});
```

### Route Naming Conventions

Routes must follow consistent naming patterns:

| Pattern | Example | Description |
|---------|---------|-------------|
| `{module}.index` | `order-tracker.index` | List view |
| `{module}.create` | `order-tracker.create` | Create form |
| `{module}.store` | `order-tracker.store` | Store new record |
| `{module}.show` | `order-tracker.show` | Detail view |
| `{module}.edit` | `order-tracker.edit` | Edit form |
| `{module}.update` | `order-tracker.update` | Update record |
| `{module}.destroy` | `order-tracker.destroy` | Delete record |

**Route prefix convention:**

Use kebab-case matching the module alias defined in `module.json`:

```php
// module.json: "alias": "order-tracker"
Route::prefix('order-tracker')->group(function () {
    // Routes here
});
```

## API Routes

API routes are defined in `Routes/api.php` and handle JSON-based API requests.

### Basic API Route Definition

```php
<?php

use Illuminate\Support\Facades\Route;
use Modules\OrderTracker\Http\Controllers\Api\OrderApiController;

Route::middleware('api')->prefix('api')->group(function () {
    Route::prefix('order-tracker')->group(function () {

        // Public API endpoints (no authentication)
        Route::get('/status', [OrderApiController::class, 'status']);

        // Protected API endpoints (require authentication)
        Route::middleware('auth:sanctum')->group(function () {
            Route::get('/orders', [OrderApiController::class, 'index']);
            Route::get('/orders/{id}', [OrderApiController::class, 'show']);
            Route::post('/orders', [OrderApiController::class, 'store']);
            Route::put('/orders/{id}', [OrderApiController::class, 'update']);
            Route::delete('/orders/{id}', [OrderApiController::class, 'destroy']);
        });
    });
});
```

### API Versioning

Implement API versioning using route prefixes:

```php
Route::middleware('api')->prefix('api')->group(function () {
    // v1 routes
    Route::prefix('v1/order-tracker')->group(function () {
        Route::get('/orders', [OrderApiV1Controller::class, 'index']);
    });

    // v2 routes
    Route::prefix('v2/order-tracker')->group(function () {
        Route::get('/orders', [OrderApiV2Controller::class, 'index']);
    });
});
```

## Middleware Application

### Common Middleware

| Middleware | Purpose | Usage |
|------------|---------|-------|
| `web` | Browser requests, CSRF, sessions | Required for all web routes |
| `api` | API requests, JSON responses | Required for all API routes |
| `auth` | User authentication | Protect authenticated routes |
| `auth:sanctum` | API token authentication | Protect API endpoints |
| `tenant` | Tenant context verification | Ensure tenant isolation |

### Authentication Middleware

**Web authentication:**

```php
Route::middleware(['web', 'auth'])->group(function () {
    Route::prefix('order-tracker')->group(function () {
        Route::get('/', [OrderTrackerController::class, 'index'])
            ->name('order-tracker.index');
    });
});
```

**API authentication:**

```php
Route::middleware(['api', 'auth:sanctum'])->prefix('api')->group(function () {
    Route::prefix('order-tracker')->group(function () {
        Route::get('/orders', [OrderApiController::class, 'index']);
    });
});
```

### Tenant Middleware

Apply tenant middleware to ensure tenant-scoped operations:

```php
Route::middleware(['web', 'auth', 'tenant'])->group(function () {
    Route::prefix('order-tracker')->group(function () {
        Route::get('/', [OrderTrackerController::class, 'index'])
            ->name('order-tracker.index');
    });
});
```

### Custom Middleware

Create custom middleware in `Http/Middleware/`:

```php
<?php

namespace Modules\OrderTracker\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class CheckOrderPermission
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        if (! $request->user()->can('manage-orders')) {
            abort(403, 'Unauthorized action.');
        }

        return $next($request);
    }
}
```

**Register custom middleware:**

```php
Route::middleware(['web', 'auth', CheckOrderPermission::class])->group(function () {
    Route::prefix('order-tracker')->group(function () {
        // Protected routes
    });
});
```

## Controllers

### Basic Controller Structure

```php
<?php

namespace Modules\OrderTracker\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Modules\OrderTracker\Models\Order;

class OrderTrackerController extends Controller
{
    /**
     * Display a listing of orders.
     */
    public function index()
    {
        $orders = Order::where('tenant_id', tenant_id())
            ->latest()
            ->paginate(20);

        return view('ordertracker::index', compact('orders'));
    }

    /**
     * Show the form for creating a new order.
     */
    public function create()
    {
        return view('ordertracker::create');
    }

    /**
     * Store a newly created order.
     */
    public function store(Request $request)
    {
        $validated = $request->validate([
            'order_number' => 'required|string|unique:orders',
            'total' => 'required|numeric|min:0',
            'status' => 'required|string|in:pending,processing,completed',
        ]);

        $order = Order::create([
            'tenant_id' => tenant_id(),
            'order_number' => $validated['order_number'],
            'total' => $validated['total'],
            'status' => $validated['status'],
        ]);

        return redirect()
            ->route('order-tracker.show', $order->id)
            ->with('success', 'Order created successfully.');
    }

    /**
     * Display the specified order.
     */
    public function show(int $id)
    {
        $order = Order::where('tenant_id', tenant_id())
            ->findOrFail($id);

        return view('ordertracker::show', compact('order'));
    }

    /**
     * Show the form for editing the specified order.
     */
    public function edit(int $id)
    {
        $order = Order::where('tenant_id', tenant_id())
            ->findOrFail($id);

        return view('ordertracker::edit', compact('order'));
    }

    /**
     * Update the specified order.
     */
    public function update(Request $request, int $id)
    {
        $order = Order::where('tenant_id', tenant_id())
            ->findOrFail($id);

        $validated = $request->validate([
            'total' => 'required|numeric|min:0',
            'status' => 'required|string|in:pending,processing,completed',
        ]);

        $order->update($validated);

        return redirect()
            ->route('order-tracker.show', $order->id)
            ->with('success', 'Order updated successfully.');
    }

    /**
     * Remove the specified order.
     */
    public function destroy(int $id)
    {
        $order = Order::where('tenant_id', tenant_id())
            ->findOrFail($id);

        $order->delete();

        return redirect()
            ->route('order-tracker.index')
            ->with('success', 'Order deleted successfully.');
    }
}
```

### API Controller Structure

```php
<?php

namespace Modules\OrderTracker\Http\Controllers\Api;

use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Routing\Controller;
use Modules\OrderTracker\Models\Order;

class OrderApiController extends Controller
{
    /**
     * Display a listing of orders.
     */
    public function index(): JsonResponse
    {
        $orders = Order::where('tenant_id', tenant_id())
            ->latest()
            ->paginate(20);

        return response()->json($orders);
    }

    /**
     * Store a newly created order.
     */
    public function store(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'order_number' => 'required|string|unique:orders',
            'total' => 'required|numeric|min:0',
            'status' => 'required|string|in:pending,processing,completed',
        ]);

        $order = Order::create([
            'tenant_id' => tenant_id(),
            'order_number' => $validated['order_number'],
            'total' => $validated['total'],
            'status' => $validated['status'],
        ]);

        return response()->json($order, 201);
    }

    /**
     * Display the specified order.
     */
    public function show(int $id): JsonResponse
    {
        $order = Order::where('tenant_id', tenant_id())
            ->findOrFail($id);

        return response()->json($order);
    }

    /**
     * Update the specified order.
     */
    public function update(Request $request, int $id): JsonResponse
    {
        $order = Order::where('tenant_id', tenant_id())
            ->findOrFail($id);

        $validated = $request->validate([
            'total' => 'numeric|min:0',
            'status' => 'string|in:pending,processing,completed',
        ]);

        $order->update($validated);

        return response()->json($order);
    }

    /**
     * Remove the specified order.
     */
    public function destroy(int $id): JsonResponse
    {
        $order = Order::where('tenant_id', tenant_id())
            ->findOrFail($id);

        $order->delete();

        return response()->json(null, 204);
    }
}
```

## Request Validation

### Form Request Classes

Create dedicated request validation classes for complex validation logic:

```php
<?php

namespace Modules\OrderTracker\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StoreOrderRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return $this->user()->can('create-orders');
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'order_number' => 'required|string|unique:orders',
            'total' => 'required|numeric|min:0',
            'status' => 'required|string|in:pending,processing,completed',
            'items' => 'required|array|min:1',
            'items.*.product_id' => 'required|exists:products,id',
            'items.*.quantity' => 'required|integer|min:1',
            'items.*.price' => 'required|numeric|min:0',
        ];
    }

    /**
     * Get custom messages for validator errors.
     */
    public function messages(): array
    {
        return [
            'order_number.required' => 'Order number is required.',
            'items.required' => 'Order must contain at least one item.',
        ];
    }
}
```

**Usage in controller:**

```php
use Modules\OrderTracker\Http\Requests\StoreOrderRequest;

public function store(StoreOrderRequest $request)
{
    $validated = $request->validated();

    $order = Order::create([
        'tenant_id' => tenant_id(),
        ...$validated
    ]);

    return redirect()
        ->route('order-tracker.show', $order->id)
        ->with('success', 'Order created successfully.');
}
```

## Response Patterns

### Web Responses

**Redirect with flash message:**

```php
return redirect()
    ->route('order-tracker.index')
    ->with('success', 'Order created successfully.');
```

**Redirect with errors:**

```php
return redirect()
    ->back()
    ->withErrors(['error' => 'Unable to create order.'])
    ->withInput();
```

**View response:**

```php
return view('ordertracker::index', [
    'orders' => $orders,
    'statistics' => $statistics,
]);
```

### API Responses

**Success response (200):**

```php
return response()->json([
    'message' => 'Order retrieved successfully.',
    'data' => $order,
], 200);
```

**Created response (201):**

```php
return response()->json([
    'message' => 'Order created successfully.',
    'data' => $order,
], 201);
```

**No content response (204):**

```php
return response()->json(null, 204);
```

**Error response (4xx):**

```php
return response()->json([
    'message' => 'Order not found.',
    'errors' => [],
], 404);
```

**Validation error response (422):**

```php
return response()->json([
    'message' => 'Validation failed.',
    'errors' => $validator->errors(),
], 422);
```

## Tenant-Aware Operations

### Critical Tenant Scoping

All database queries must be scoped to the current tenant:

```php
//  Correct - tenant-scoped query
$orders = Order::where('tenant_id', tenant_id())
    ->get();

//  Incorrect - exposes all tenant data
$orders = Order::all();
```

### Using Tenant Helper

```php
use function App\Helpers\tenant_id;

public function index()
{
    $tenantId = tenant_id();

    $orders = Order::where('tenant_id', $tenantId)
        ->latest()
        ->paginate(20);

    return view('ordertracker::index', compact('orders'));
}
```

## Route Caching

### Generate Route Cache

```bash
php artisan route:cache
```

### Clear Route Cache

```bash
php artisan route:clear
```

**Important:** Clear route cache after adding or modifying routes.

## Testing Routes

### List All Routes

```bash
php artisan route:list
```

### Filter Module Routes

```bash
php artisan route:list --path=order-tracker
```

### Check Specific Route

```bash
php artisan route:list --name=order-tracker.index
```

## Next Steps {#next-steps-routing-controllers}

* **[Views & Translations](./views-translations.md)** - Create Blade templates and translations
* **[Hooks & Events](./hooks-events.md)** - Integrate with hook system
* **[Helper Functions](./helper-functions.md)** - Use module utilities

***

*This guide is for WhatsMarkSaaS v2.0.0+ custom module development.*
