Skip to content

Routing & Controllers

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:

PatternExampleDescription
{module}.indexorder-tracker.indexList view
{module}.createorder-tracker.createCreate form
{module}.storeorder-tracker.storeStore new record
{module}.showorder-tracker.showDetail view
{module}.editorder-tracker.editEdit form
{module}.updateorder-tracker.updateUpdate record
{module}.destroyorder-tracker.destroyDelete 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

MiddlewarePurposeUsage
webBrowser requests, CSRF, sessionsRequired for all web routes
apiAPI requests, JSON responsesRequired for all API routes
authUser authenticationProtect authenticated routes
auth:sanctumAPI token authenticationProtect API endpoints
tenantTenant context verificationEnsure 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


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

© 2024 - Corbital Technologies. All rights reserved.