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.