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
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:
// 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
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:
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:
Route::middleware(['web', 'auth'])->group(function () {
Route::prefix('order-tracker')->group(function () {
Route::get('/', [OrderTrackerController::class, 'index'])
->name('order-tracker.index');
});
});API authentication:
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:
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
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:
Route::middleware(['web', 'auth', CheckOrderPermission::class])->group(function () {
Route::prefix('order-tracker')->group(function () {
// Protected routes
});
});Controllers β
Basic Controller Structure β
<?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
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
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:
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:
return redirect()
->route('order-tracker.index')
->with('success', 'Order created successfully.');Redirect with errors:
return redirect()
->back()
->withErrors(['error' => 'Unable to create order.'])
->withInput();View response:
return view('ordertracker::index', [
'orders' => $orders,
'statistics' => $statistics,
]);API Responses β
Success response (200):
return response()->json([
'message' => 'Order retrieved successfully.',
'data' => $order,
], 200);Created response (201):
return response()->json([
'message' => 'Order created successfully.',
'data' => $order,
], 201);No content response (204):
return response()->json(null, 204);Error response (4xx):
return response()->json([
'message' => 'Order not found.',
'errors' => [],
], 404);Validation error response (422):
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:
// Correct - tenant-scoped query
$orders = Order::where('tenant_id', tenant_id())
->get();
// Incorrect - exposes all tenant data
$orders = Order::all();Using Tenant Helper β
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 β
php artisan route:cacheClear Route Cache β
php artisan route:clearImportant: Clear route cache after adding or modifying routes.
Testing Routes β
List All Routes β
php artisan route:listFilter Module Routes β
php artisan route:list --path=order-trackerCheck Specific Route β
php artisan route:list --name=order-tracker.indexNext Steps β
- Views & Translations - Create Blade templates and translations
- Hooks & Events - Integrate with hook system
- Helper Functions - Use module utilities
This guide is for WhatsMarkSaaS v2.0.0+ custom module development.