🚀 Getting Started

📅 Last Updated: 2025-12-25 | Last Documented: ✅ Complete

Welcome to the Laravel 12 Base Project documentation! This guide will help you get up and running quickly.

Quick Setup

1. Install Dependencies

# Install PHP dependencies composer install # Install Node.js dependencies (for streaming service) cd streaming-service npm install cd ..

2. Configure Environment

# Copy environment file cp .env.example .env # Generate application key php artisan key:generate # Configure database in .env file DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=your_database DB_USERNAME=your_username DB_PASSWORD=your_password

3. Run Migrations

# Run database migrations php artisan migrate # Seed database (optional) php artisan db:seed

4. Start Services

# Start Laravel development server php artisan serve # Start streaming service (in another terminal) cd streaming-service npm start

Next Steps

💡 Tip: Use the Ctrl+E or / keyboard shortcut to quickly search the documentation!

📋 Project Overview

📅 Last Updated: 2025-12-25 | Last Documented: ✅ Complete
✨ Enhanced Documentation: This documentation has been enhanced with detailed explanations, comprehensive examples, step-by-step guides, and Laravel-style formatting. Each section includes practical examples, parameter descriptions, and real-world usage scenarios.

This is a Laravel 12 base project with a modular architecture, featuring a Node.js streaming service for media handling. The project follows Laravel 11+ conventions and best practices.

Key Technologies

Laravel 12 PHP 8.2+ Express.js MySQL JWT Auth Tailwind CSS Vite

Project Structure

laravel12-base/
├── app/ - Application core
│ ├── Console/Commands/ - Artisan commands
│ ├── Http/ - Controllers, Middleware, Requests, Resources
│ ├── Models/ - Eloquent models
│ ├── Services/ - Business logic
│ ├── Enums/ - PHP enums
│ └── Helpers/ - Helper functions
├── modules/ - Modular packages
│ ├── Media/ - Media management
│ ├── MediaExpress/ - Express media upload
│ ├── Notification/ - Notification system
│ └── Verification/ - Email/SMS verification
├── streaming-service/ - Node.js streaming service
├── routes/ - Route definitions
├── bootstrap/ - Application bootstrap
└── config/ - Configuration files

Features

  • Modular Architecture - Custom modules for Media, Notifications, Verification
  • Multi-user System - Admin, Supervisor, Client, Guest user types
  • Media Streaming - Express.js service for video/audio streaming
  • JWT Authentication - Token-based API authentication
  • Multi-language Support - Translatable models with astrotomic/laravel-translatable
  • CRUD Generators - Artisan commands for rapid development
  • API Documentation - Postman collection generator
  • Service Layer Pattern - Organized business logic
  • Pipeline Pattern - Request processing pipelines
  • Observer Pattern - Model event observers

🏗️ Architecture

📅 Last Updated: 2025-12-25 | Last Documented: ✅ Complete

Laravel 12 Structure

This project uses Laravel 12 with the new streamlined structure:

Note: Laravel 11+ removed app/Http/Kernel.php and app/Console/Kernel.php. Configuration is now in bootstrap/app.php.

Bootstrap Configuration

Application configuration is centralized in bootstrap/app.php:

// Routes configuration ->withRouting( using: function () { Route::group([ 'prefix' => 'api', 'middleware' => ['api', 'set_locale', 'cors'], ], function () { Route::prefix('guest')->group(base_path('routes/Api/guest.php')); Route::prefix('admin')->group(base_path('routes/Api/admin.php')); Route::prefix('client')->group(base_path('routes/Api/client.php')); }); }, commands: __DIR__.'/../routes/console.php', health: '/up' ) // Middleware registration ->withMiddleware(function (Middleware $middleware) { $middleware->append([ DetectUserType::class, SetLocale::class, HandleCors::class, GzipMiddleware::class ]); $middleware->alias([ 'user_type' => CheckUserType::class, 'active' => ActiveMiddleware::class, 'permission' => PermissionMiddleware::class, ]); })

Service Providers

Service providers are registered in bootstrap/providers.php (not config/app.php):

return [ App\Providers\AppServiceProvider::class, App\Providers\NotificationServiceProvider::class, App\Providers\RouteServiceProvider::class, App\Providers\TelescopeServiceProvider::class, Modules\MediaExpress\MediaExpressServiceProvider::class, ];

Console Scheduling

Scheduled commands are defined in bootstrap/app.php using withSchedule():

->withSchedule(function (Schedule $schedule) { $schedule->command('media:clean-orphaned') ->everyTwoMinutes() ->withoutOverlapping(); })

Layered Architecture

Request Flow
  1. Route → Defines endpoint and middleware
  2. Middleware → Authentication, authorization, validation
  3. Controller → Handles HTTP request/response
  4. Request → Form request validation
  5. Service → Business logic layer
  6. Model → Database interaction
  7. Resource → API response transformation

📦 Modules

📅 Last Updated: 2025-12-25 | Last Documented: ✅ Complete

The project uses a modular architecture with custom Laravel packages located in the modules/ directory. Each module is a self-contained package that can be easily maintained and extended.

🔗 Related: See Architecture for project structure, Services for business logic integration.

Module Structure

Each module follows this structure:

ModuleName/
├── composer.json - Package definition
├── config/ - Configuration files
├── database/migrations/ - Database migrations
├── routes/ - Module routes (api.php)
└── src/ - Source code
├── Models/ - Eloquent models
├── Services/ - Business logic
├── Http/Controllers/ - Controllers
├── Traits/ - Reusable traits
└── *ServiceProvider.php - Service provider

Available Modules

1. Media Module

Location: modules/Media/

Purpose: Comprehensive media file management system with upload, storage, retrieval, and automatic cleanup.

Features:
  • ✅ File upload handling (single & multiple)
  • ✅ Media model with polymorphic relationships
  • HasMedia trait for easy model integration
  • ✅ Automatic file cleanup on model deletion
  • ✅ Orphaned media cleanup job
  • ✅ Video thumbnail extraction job
  • ✅ Hash-based media identification
  • ✅ Collection-based media organization
  • ✅ Single and multiple media support per collection
How to Use:

Step 1: Add HasMedia Trait to Your Model

namespace App\Models; use Illuminate\Database\Eloquent\Model; use Modules\Media\Traits\HasMedia; class Product extends Model { use HasMedia { HasMedia::setAttribute as setMediaAttribute; HasMedia::getAttribute as getMediaAttribute; } // Define media collections // Key = collection name, Value = isMultiple (true/false) public array $media_keys = [ 'image' => false, // Single image 'gallery' => true, // Multiple images 'video' => false, // Single video 'documents' => true, // Multiple documents ]; protected $guarded = []; public function setAttribute($key, $value) { if (method_exists($this, 'isMediaKey') && $this->isMediaKey($key)) { return $this->setMediaAttribute($key, $value); } return parent::setAttribute($key, $value); } public function getAttribute($key) { if (method_exists($this, 'isMediaKey') && $this->isMediaKey($key)) { return $this->getMediaAttribute($key); } return parent::getAttribute($key); } }

Step 2: Upload Media via API

// POST /api/media/upload // Form Data: // - file: (file) - Single or multiple files // - collection: (string) - Collection name (optional, defaults to 'default') // Example: Upload single image POST /api/media/upload Content-Type: multipart/form-data file: [image.jpg] collection: image // Response: { "status": "success", "data": { "id": 1, "hash": "abc123def456..." }, "message": "Media uploaded successfully" }

Step 3: Attach Media to Model

use App\Models\Product; // Create product and attach media by hash $product = Product::create([ 'name' => 'Laptop', 'price' => 999.99, // Attach media using hash from upload response 'image' => 'abc123def456...', // Single media (hash string) 'gallery' => [ // Multiple media (array of hashes) 'hash1...', 'hash2...', 'hash3...' ] ]); // Or attach after creation $product->image = 'abc123def456...'; $product->save(); // Or use attachMediaByHash method $product->attachMediaByHash('abc123def456...', 'image'); $product->attachMediaByHash(['hash1', 'hash2'], 'gallery');

Step 4: Access Media URLs

// Get media URL (single collection) $imageUrl = $product->image; // Returns: "https://example.com/storage/uploads/image/abc123.jpg" // Get media URLs (multiple collection) $galleryUrls = $product->gallery; // Returns: ["https://...", "https://...", "https://..."] // Get all media URLs $allMedia = $product->media_urls; // Returns: [ // 'image' => 'https://...', // 'gallery' => ['https://...', 'https://...'], // 'video' => 'https://...', // 'documents' => ['https://...', 'https://...'] // ] // Get media with IDs $imageWithId = $product->getAttributeWithId('image'); // Returns: ['id' => 1, 'url' => 'https://...'] // Access media relationship directly $mediaItems = $product->media; foreach ($mediaItems as $media) { echo $media->url(); // Get full URL echo $media->path; // Get storage path echo $media->mime_type; echo $media->size; }

Step 5: Update Media

// Update single media (replaces existing) $product->image = 'new_hash_abc123...'; $product->save(); // Update multiple media (replaces all) $product->gallery = ['new_hash1', 'new_hash2', 'new_hash3']; $product->save(); // Add to existing multiple media $currentGallery = $product->gallery ?? []; $product->gallery = array_merge($currentGallery, ['new_hash4']); $product->save();

Step 6: Delete Media

// Delete specific media by ID DELETE /api/media/{media_id} // Clear media from collection (set to null) $product->image = null; $product->save(); // Media is automatically deleted when model is deleted $product->delete(); // All associated media files are deleted

API Endpoints:

GET /api/media - List all media POST /api/media/upload - Upload single/multiple files POST /api/media/upload-bulk - Bulk upload (optimized) DELETE /api/media/{id} - Delete media POST /api/media/clear-orphaned - Clear orphaned media (admin)

Example: Complete Product with Media

// 1. Upload image $uploadResponse = Http::post('/api/media/upload', [ 'file' => $imageFile, 'collection' => 'image' ]); $imageHash = $uploadResponse->json()['data']['hash']; // 2. Upload gallery images $galleryHashes = []; foreach ($galleryFiles as $file) { $response = Http::post('/api/media/upload', [ 'file' => $file, 'collection' => 'gallery' ]); $galleryHashes[] = $response->json()['data']['hash']; } // 3. Create product with media $product = Product::create([ 'name' => 'Product Name', 'price' => 99.99, 'image' => $imageHash, 'gallery' => $galleryHashes ]); // 4. Access media in API response return response()->json([ 'id' => $product->id, 'name' => $product->name, 'image' => $product->image, // Single URL 'gallery' => $product->gallery, // Array of URLs 'all_media' => $product->media_urls // All collections ]);

2. MediaExpress Module

Location: modules/MediaExpress/

Purpose: Integration with Express.js streaming service for advanced media handling, streaming, and large file uploads.

Features:
  • ✅ Express.js service integration
  • ✅ Large file upload support
  • ✅ Media streaming capabilities
  • ✅ Hash-based file identification
  • ✅ Bulk upload optimization
  • ✅ Metadata management
How to Use:

Step 1: Configure Express Service

// config/media-express.php return [ 'express' => [ 'enabled' => true, 'url' => env('EXPRESS_URL', 'http://localhost:3000'), 'timeout' => 30, 'api_key' => env('EXPRESS_API_KEY'), ], 'use_media_model' => true, // Use Media module's model 'endpoints' => [ 'upload' => '/api/media/upload', 'delete' => '/api/media/delete', 'info' => '/api/media/info', 'list' => '/api/media/list', ], ];

Step 2: Upload via Express Service

use Modules\MediaExpress\Services\ExpressFileService; $expressService = app(ExpressFileService::class); // Upload single file $media = $expressService->upload( model: $product, // Optional: attach to model file: $uploadedFile, // UploadedFile instance collection: 'images' // Collection name ); // Upload multiple files (bulk) $mediaCollection = $expressService->uploadBulk( model: $product, files: [$file1, $file2, $file3], collection: 'gallery' ); // Get media info by hash $mediaInfo = $expressService->getMediaInfo('abc123def456...'); // Returns: ['id', 'hash', 'path', 'disk', 'mime_type', 'size', 'original_name']

Step 3: Use in Controller

namespace App\Http\Controllers; use Modules\MediaExpress\Services\ExpressFileService; use Illuminate\Http\Request; class ProductController extends Controller { public function store(Request $request, ExpressFileService $expressService) { $product = Product::create($request->only(['name', 'price'])); // Upload image via Express service if ($request->hasFile('image')) { $media = $expressService->upload( $product, $request->file('image'), 'image' ); // Attach to product $product->image = $media->hash; $product->save(); } return response()->json($product); } }

API Endpoints:

POST /api/media-express/upload - Upload file via Express GET /api/media-express/info/{hash} - Get media info DELETE /api/media-express/{id} - Delete media via Express

3. Notification Module

Location: modules/Notification/

Purpose: Comprehensive notification system for users and admins with database broadcasting and real-time updates.

Features:
  • ✅ User notifications (client, supervisor)
  • ✅ Admin notifications (super admin)
  • ✅ Database broadcasting channel
  • ✅ Notification service with pagination
  • ✅ Mark as read functionality
  • ✅ Delete notifications
  • ✅ User type-based notification filtering
How to Use:

Step 1: Send Notification to User

use App\Models\User; use Modules\Notification\Models\Notification; use Illuminate\Notifications\Notification as LaravelNotification; // Create custom notification class OrderShippedNotification extends LaravelNotification { public function toDatabase($notifiable) { return [ 'title' => 'Order Shipped', 'message' => 'Your order #12345 has been shipped!', 'type' => 'order', 'data' => [ 'order_id' => 12345, 'tracking_number' => 'TRACK123' ] ]; } } // Send notification $user = User::find(1); $user->notify(new OrderShippedNotification());

Step 2: Send Admin Notification

use Modules\Notification\Models\AdminNotification; // Create admin notification class NewOrderNotification extends LaravelNotification { public function toDatabase($notifiable) { return [ 'title' => 'New Order Received', 'message' => 'A new order has been placed', 'type' => 'order', 'data' => ['order_id' => 12345] ]; } } // Send to admin $admin = User::where('user_type', 'super_admin')->first(); $admin->notify(new NewOrderNotification());

Step 3: Get User Notifications via API

// GET /api/notifications // Headers: Authorization: Bearer {token} // Response (for client): { "status": "success", "data": { "client_notifications": { "data": [ { "id": 1, "title": "Order Shipped", "message": "Your order has been shipped!", "type": "order", "read_at": null, "created_at": "2024-01-15T10:30:00.000000Z" } ], "current_page": 1, "per_page": 10 } }, "message": "Notifications retrieved successfully" } // Response (for admin): { "status": "success", "data": { "super_admin_notifications": { "data": [...] } } }

Step 4: Mark Notification as Read

// POST /api/notifications/{id}/read // Headers: Authorization: Bearer {token} // Response: { "status": "success", "data": null, "message": "Notification read successfully" }

Step 5: Delete Notification

// DELETE /api/notifications/{id} // Headers: Authorization: Bearer {token} // Response: { "status": "success", "data": null, "message": "Notification deleted successfully" }

Step 6: Use Notification Service

use Modules\Notification\Services\NotificationService; $notificationService = app(NotificationService::class); // Get notifications (returns JsonResponse) $response = $notificationService->getNotifications(); // Mark as read $response = $notificationService->readNotification($notificationId); // Delete notification $response = $notificationService->deleteNotification($notificationId);

Step 7: Broadcast Notifications (Real-time)

// In your notification class use Modules\Notification\Broadcasting\DatabaseChannel; class OrderNotification extends LaravelNotification { public function via($notifiable) { return [DatabaseChannel::class]; } public function toDatabase($notifiable) { return [ 'title' => 'New Order', 'message' => 'You have a new order', 'type' => 'order', 'data' => ['order_id' => 123] ]; } } // Frontend: Listen to broadcast // Using Laravel Echo or WebSockets Echo.private(`user.${userId}`) .notification((notification) => { console.log('New notification:', notification); // Update UI });

API Endpoints:

GET /api/notifications - Get user notifications (paginated) POST /api/notifications/{id}/read - Mark notification as read DELETE /api/notifications/{id} - Delete notification

Example: Complete Notification Flow

// 1. Create notification class class PaymentReceivedNotification extends LaravelNotification { protected $payment; public function __construct($payment) { $this->payment = $payment; } public function toDatabase($notifiable) { return [ 'title' => 'Payment Received', 'message' => "Payment of {$this->payment->amount} received", 'type' => 'payment', 'data' => [ 'payment_id' => $this->payment->id, 'amount' => $this->payment->amount ] ]; } } // 2. Send notification $user = User::find($payment->user_id); $user->notify(new PaymentReceivedNotification($payment)); // 3. User retrieves notifications via API // GET /api/notifications // 4. User marks as read // POST /api/notifications/1/read // 5. User deletes notification // DELETE /api/notifications/1

4. Verification Module

Location: modules/Verification/

Purpose: Email and SMS verification system with token-based validation, expiration, and attempt limiting.

Features:
  • ✅ Email verification
  • ✅ SMS/Phone verification
  • ✅ Token-based verification
  • ✅ Code expiration (configurable)
  • ✅ Attempt limiting (max attempts)
  • ✅ Resend functionality
  • ✅ Verification events and listeners
  • ✅ Notification resolver pattern
  • ✅ Rate limiting/throttling
How to Use:

Step 1: Configure Verification

// config/verification.php return [ 'supported_types' => ['email', 'phone'], 'code_length' => 6, 'expires_in_minutes' => 10, 'max_attempts' => 5, ];

Step 2: Send Verification Code

use Modules\Verification\Services\VerificationService; $verificationService = app(VerificationService::class); // Generate unique token $token = Str::random(32); // Send email verification $verification = $verificationService->send( type: 'email', target: 'user@example.com', token: $token ); // Send phone verification $verification = $verificationService->send( type: 'phone', target: '+1234567890', token: $token, phone_code: '+1' // Optional phone country code ); // Response: Verification model with code, expires_at, etc.

Step 3: Send via API

// POST /api/verification/send // Body: { "type": "email", // 'email' or 'phone' "target": "user@example.com", "token": "unique_token_here", "phone_code": "+1" // Optional, for phone verification } // Response: { "status": "success", "data": null, "message": "Verification code sent successfully." }

Step 4: Verify Code

use Modules\Verification\Services\VerificationService; $verificationService = app(VerificationService::class); try { $result = $verificationService->verify( type: 'email', code: '123456', // Code sent to user token: $token // Same token from send() ); // Returns: // [ // 'is_verified' => true, // 'token' => '...', // 'target' => 'user@example.com', // 'phone_code' => null // If phone verification // ] } catch (ValidationException $e) { // Handle validation errors: // - Code expired // - Invalid code // - Max attempts exceeded // - Already verified }

Step 5: Verify via API

// POST /api/verification/verify // Body: { "type": "email", "code": "123456", "token": "unique_token_here" } // Success Response: { "status": "success", "data": { "is_verified": true, "token": "unique_token_here", "target": "user@example.com" } } // Error Response (invalid code): { "status": "fail", "message": "Invalid verification code." } // Error Response (expired): { "status": "fail", "message": "Verification code has expired." } // Error Response (max attempts): { "status": "fail", "message": "Maximum verification attempts exceeded, try to resend the code." }

Step 6: Resend Verification Code

use Modules\Verification\Services\VerificationService; $verificationService = app(VerificationService::class); // Resend using same token $verification = $verificationService->resend($token); // If expired, new code is generated automatically // If not expired, same code is resent

Step 7: Resend via API

// POST /api/verification/resend // Body: { "type": "email", "target": "user@example.com", "token": "unique_token_here" } // Response: { "status": "success", "data": null, "message": "Verification code sent successfully." }

Step 8: Get User by Token

use Modules\Verification\Services\VerificationService; $verificationService = app(VerificationService::class); // Get user associated with verification token $user = $verificationService->getUserByToken($token); // Use in registration/verification flow if ($user && $user->email_verified_at === null) { $user->email_verified_at = now(); $user->save(); }

Step 9: Listen to Verification Events

use Modules\Verification\Events\VerificationCompleted; use Illuminate\Support\Facades\Event; // Listen to verification completed event Event::listen(VerificationCompleted::class, function ($event) { $verification = $event->verification; // Update user verification status if ($verification->type === 'email') { $user = User::where('email', $verification->target)->first(); if ($user) { $user->email_verified_at = now(); $user->save(); } } // Or use the listener class // Modules\Verification\Listeners\VerificationCompletedListener });

Step 10: Complete Registration Flow Example

// 1. User registers $user = User::create([ 'name' => 'John Doe', 'email' => 'john@example.com', 'password' => Hash::make('password'), 'email_verified_at' => null // Not verified yet ]); // 2. Generate verification token $token = Str::random(32); // 3. Send verification code $verificationService = app(VerificationService::class); $verificationService->send( type: 'email', target: $user->email, token: $token ); // 4. Store token in session or return to frontend session(['verification_token' => $token]); // 5. User enters code, verify it try { $result = $verificationService->verify( type: 'email', code: $request->input('code'), token: session('verification_token') ); // 6. Mark user as verified $user->email_verified_at = now(); $user->save(); // 7. Clear token session()->forget('verification_token'); return response()->json(['message' => 'Email verified successfully']); } catch (ValidationException $e) { return response()->json([ 'message' => $e->getMessage() ], 422); }

API Endpoints:

POST /api/verification/send - Send verification code POST /api/verification/verify - Verify code POST /api/verification/resend - Resend verification code

Using Facade (Alternative):

use Modules\Verification\Facades\VerificationFacade; // Send verification VerificationFacade::send('email', 'user@example.com', $token); // Verify code $result = VerificationFacade::verify('email', '123456', $token); // Resend VerificationFacade::resend($token);

Module Registration

Modules are registered in composer.json as path repositories and auto-loaded via PSR-4:

"repositories": [ { "type": "path", "url": "modules/Media", "options": { "symlink": true } }, { "type": "path", "url": "modules/MediaExpress", "options": { "symlink": true } }, { "type": "path", "url": "modules/Notification", "options": { "symlink": true } }, { "type": "path", "url": "modules/Verification", "options": { "symlink": true } } ], "autoload": { "psr-4": { "Modules\\": "modules/" } }

Each module's service provider is registered in bootstrap/providers.php:

return [ // ... other providers \Modules\Media\MediaServiceProvider::class, \Modules\MediaExpress\MediaExpressServiceProvider::class, \Modules\Notification\NotificationServiceProvider::class, \Modules\Verification\VerificationServiceProvider::class, ];

⚙️ Services

📅 Last Updated: 2025-12-25 | Last Documented: ✅ Complete

Services contain business logic and are organized by context (App, Dashboard, Shared, Utilities).

🔗 Related: See Search, Filter & Sort for query capabilities, Architectural Patterns for design patterns used.

Service Structure

app/Services/
├── App/ - Application services
│ └── Client/ - Client-facing services
├── Dashboard/ - Admin dashboard services
├── Shared/ - Shared services
└── Utilities/ - Utility services

Base Service Classes

ClientCRUDService (Read-Only)

Location: app/Services/App/Client/ClientCRUDService.php

Purpose: Base service for read-only client-facing operations. Designed for authenticated client users who can only view data, not modify it.

⚠️ Important: ClientCRUDService only provides index() and show() methods. Clients cannot create, update, or delete resources.
Available Methods:
  • index() - List resources with filtering, searching, pagination
  • show() - Show single resource details
How index() Works:

The index() method provides a powerful listing endpoint with multiple features:

public function index(Request $request, $with, $resource): JsonResponse { // 1. Reset query with scoped query (applies global scopes) $this->query = $this->scopedQuery(); // 2. Eager load relationships (if provided) if (!empty($with)) { $this->query->with($with); } // 3. Apply custom scopes from request $this->applyScopes($request); // 4. Apply filters from request $this->applyFilters($request); // 5. Apply search across searchable fields $this->applySearch($request); // 6. Apply sorting $this->applySorting($request); // 7. Select specific columns (if provided) $columns = $request->get('columns', ['*']); $this->query->select($columns); // 8. Apply pagination $result = $this->applyPagination($request); // 9. Return formatted response if ($request->get('paginate', false)) { $resource::wrap($this->model->getTable()); return json($resource::collection($result)->response()->getData(), ...); } return json($resource::collection($result), ...); }
How show() Works:
public function show($id, $load, $resource): JsonResponse { // 1. Resolve model using scoped query (respects global scopes) $model = $this->resolveModelOrFail($id); // 2. Eager load relationships (if provided) if (!empty($load)) { $model->load($load); } // 3. Return formatted response return json($resource::make($model), __('Data retrieved successfully')); }
Request Parameters for index():
Parameter Type Description Example
filters object Filter by field values (must be in searchableFields) {"is_active": true, "category_id": 5}
search string Search across all searchable fields "laptop"
sort object Sort by field(s) (asc/desc) {"name": "asc", "price": "desc"}
per_page integer Items per page (default: 15) 20
paginate boolean Return pagination metadata (default: false) true
columns array Select specific columns (default: ['*']) ["id", "name", "price"]
Example API Request:
GET /api/client/products?filters[is_active]=true&filters[category_id]=5&search=laptop&sort[name]=asc&per_page=20&paginate=true // Response: { "status": "success", "data": { "products": [ { "id": 1, "name": "Laptop Pro", "price": 999.99, "is_active": true } ], "current_page": 1, "per_page": 20, "total": 1 }, "message": "Index response with pagination" }
How to Implement ClientCRUDService:
namespace App\Services\App\Client\Products; use App\Services\App\Client\ClientCRUDService; use App\Models\Product; class ProductService extends ClientCRUDService { public function __construct() { parent::__construct(new Product()); } /** * Override scopedQuery to apply default filters * This ensures clients only see active products */ protected function scopedQuery() { return $this->model->query() ->where('is_active', true) // Only active products ->where('is_published', true); // Only published products } /** * Override applyGlobalScopes to add global scopes */ protected function applyGlobalScopes(Builder $query): void { // Apply any global scopes here // Example: $query->where('tenant_id', auth()->user()->tenant_id); } /** * Override applyScopes to handle custom scope parameters */ protected function applyScopes(Request $request): void { // Example: Apply custom scopes from request if ($request->has('featured')) { $this->query->featured(); } if ($request->has('in_stock')) { $this->query->inStock(); } } }

Guest Services (Read-Only)

Purpose: Guest services work exactly like ClientCRUDService but for unauthenticated users. They also only provide index() and show() methods.

ℹ️ Note: Guest services are typically used for public-facing endpoints like public product listings, blog posts, etc. The implementation is identical to ClientCRUDService.
Example Guest Service:
namespace App\Services\App\Guest\Products; use App\Services\App\Client\ClientCRUDService; // Same base class use App\Models\Product; class ProductService extends ClientCRUDService { public function __construct() { parent::__construct(new Product()); } protected function scopedQuery() { // Guests can only see published, active products return $this->model->query() ->where('is_active', true) ->where('is_published', true) ->where('is_public', true); // Additional public check } }

DashboardCRUDService (Full CRUD)

Location: app/Services/Dashboard/DashboardCRUDService.php

Purpose: Base service for admin dashboard operations with full CRUD capabilities. Designed for admin users who can create, read, update, and delete resources.

Available Methods:
  • index() - List resources with filtering, searching, pagination
  • show() - Show single resource details
  • save() - Create or update resource (used by both store and update)
  • destroy() - Delete resource
How index() Works:

Same as ClientCRUDService, but admins can see all records (including inactive/unpublished).

How show() Works:

Same as ClientCRUDService, but admins can see all records.

How save() Works (Create/Update):

The save() method is a unified method that handles both creating new records and updating existing ones. It uses Laravel's Pipeline pattern for processing.

public function save($request, $id = null, ?Closure $customSaveLogic = null, $resource = null, $load = []): JsonResponse { DB::beginTransaction(); try { // 1. Resolve model (create new or find existing) if ($id instanceof Model) { $this->model = $id; // Model instance provided } else { $this->model = $id ? $this->resolveModelOrFail($id) // Update existing : new $this->model; // Create new } // 2. Execute save logic if ($customSaveLogic !== null) { // Use custom save logic if provided $customSaveLogic($request, $this->model); } else { // Use default pipeline-based save $this->runSavePipeline($request); } // 3. Commit transaction DB::commit(); // 4. Eager load relationships (if provided) if (!empty($load)) { $this->model->load($load); } // 5. Return formatted response return json( $resource ? $resource::make($this->model) : null, __('Data saved successfully') ); } catch (\Exception $e) { DB::rollBack(); // Rollback on error return $this->handleException($e); } }
Save Pipeline Stages:

The save() method uses a pipeline with these stages:

  1. BeforeSave - Pre-save hooks and validations
  2. FillModel - Fill model attributes from request
  3. SaveModel - Persist model to database
  4. SyncRelations - Sync relationships (many-to-many, etc.)
  5. AfterSave - Post-save hooks and side effects
protected function getSavePipes(): array { return [ \App\Pipelines\Base\Save\BeforeSave::class, // 1. Before save hooks \App\Pipelines\Base\Save\FillModel::class, // 2. Fill attributes \App\Pipelines\Base\Save\SaveModel::class, // 3. Save to DB \App\Pipelines\Base\Save\SyncRelations::class, // 4. Sync relations \App\Pipelines\Base\Save\AfterSave::class, // 5. After save hooks ]; }
How destroy() Works:
public function destroy($id): JsonResponse { try { // 1. Resolve model $model = $this->resolveModelOrFail($id); // 2. Execute before destroy hooks $this->beforeDestroy($model); // 3. Delete related records (if needed) $this->deleteRelations($model); // 4. Delete the model $model->delete(); // 5. Execute after destroy hooks $this->afterDestroy($model); // 6. Return success response return json(__('Data removed successfully')); } catch (\Exception $e) { return $this->handleException($e); } }
Custom Hooks (Override in Your Service):
class ProductService extends DashboardCRUDService { /** * Called before saving (create or update) */ public function beforeSave($request, $model) { // Example: Set default values if (!$model->exists) { $model->created_by = auth()->id(); } $model->updated_by = auth()->id(); } /** * Called after saving */ public function afterSave($request, $model) { // Example: Clear cache, send notifications, etc. Cache::forget("product_{$model->id}"); event(new ProductSaved($model)); } /** * Called before destroying */ public function beforeDestroy($model) { // Example: Check if can be deleted if ($model->orders()->count() > 0) { throw new \Exception('Cannot delete product with orders'); } } /** * Called after destroying */ public function afterDestroy($model) { // Example: Cleanup $model->media()->delete(); // Delete associated media } /** * Sync relationships (many-to-many, etc.) */ public function syncRelations($request, $model) { // Example: Sync categories if ($request->has('category_ids')) { $model->categories()->sync($request->category_ids); } } /** * Delete related records before deleting model */ public function deleteRelations($model) { // Example: Delete related records $model->variants()->delete(); $model->reviews()->delete(); } }
How to Implement DashboardCRUDService:
namespace App\Services\Dashboard\Admin\Products; use App\Services\Dashboard\DashboardCRUDService; use App\Models\Product; class ProductService extends DashboardCRUDService { public function __construct() { parent::__construct(new Product()); } /** * Override scopedQuery for admin (can see all products) */ protected function scopedQuery() { // Admins can see all products (no filtering) return $this->model->query(); } /** * Custom save logic (optional - uses pipeline by default) */ public function save($request, $id = null, ?Closure $customSaveLogic = null, $resource = null, $load = []) { // Option 1: Use default pipeline (recommended) return parent::save($request, $id, null, $resource, $load); // Option 2: Use custom logic return parent::save($request, $id, function($request, $model) { // Custom save logic here $model->fill($request->validated()); $model->save(); }, $resource, $load); } }
Example: Complete Product Service with All Features:
namespace App\Services\Dashboard\Admin\Products; use App\Services\Dashboard\DashboardCRUDService; use App\Models\Product; use Illuminate\Http\Request; use Illuminate\Database\Eloquent\Builder; class ProductService extends DashboardCRUDService { public function __construct() { parent::__construct(new Product()); } /** * Admin can see all products */ protected function scopedQuery(): Builder { return $this->model->query(); } /** * Apply custom scopes from request */ protected function applyScopes(Request $request): void { if ($request->has('featured')) { $this->query->featured(); } if ($request->has('low_stock')) { $this->query->where('stock', '<', 10); } } /** * Before save hook */ public function beforeSave($request, $model) { // Set timestamps if (!$model->exists) { $model->created_by = auth()->id(); } $model->updated_by = auth()->id(); // Generate slug if not provided if (!$model->slug && $model->name) { $model->slug = \Str::slug($model->name); } } /** * After save hook */ public function afterSave($request, $model) { // Clear cache \Cache::forget("product_{$model->id}"); // Sync media if provided if ($request->has('image')) { $model->image = $request->input('image'); $model->save(); } if ($request->has('gallery')) { $model->gallery = $request->input('gallery'); $model->save(); } } /** * Sync relationships */ public function syncRelations($request, $model) { // Sync categories (many-to-many) if ($request->has('category_ids')) { $model->categories()->sync($request->category_ids); } // Sync tags (many-to-many) if ($request->has('tag_ids')) { $model->tags()->sync($request->tag_ids); } } /** * Before destroy hook */ public function beforeDestroy($model) { // Check if product can be deleted if ($model->orders()->count() > 0) { throw new \Exception('Cannot delete product with existing orders'); } } /** * Delete related records */ public function deleteRelations($model) { // Delete variants $model->variants()->delete(); // Delete reviews $model->reviews()->delete(); } }

Creating a Service

Use the Artisan command to generate a service:

# Generate client service (read-only) php artisan make:service ProductService --path=app/Services/App/Client/Products/ # Generate dashboard service (full CRUD) php artisan make:service ProductService --path=app/Services/Dashboard/Admin/Products/

Service Comparison

Feature ClientCRUDService Guest Services DashboardCRUDService
index() ✅ Yes ✅ Yes ✅ Yes
show() ✅ Yes ✅ Yes ✅ Yes
store() ❌ No ❌ No ✅ Yes (via save())
update() ❌ No ❌ No ✅ Yes (via save())
destroy() ❌ No ❌ No ✅ Yes
Pipeline Support ❌ No ❌ No ✅ Yes
Custom Hooks ❌ No ❌ No ✅ Yes
Transaction Support ❌ No ❌ No ✅ Yes
Use Case Authenticated clients Public/unauthenticated Admin dashboard

🔍 Search, Filter, Sort, Pagination & Scopes

📅 Last Updated: 2025-12-25 | Last Documented: ✅ Complete
🔗 Related: This feature is used in Services (ClientCRUDService and DashboardCRUDService).

This section explains how to implement and use powerful query features in your API endpoints. All CRUD services (Client, Guest, Dashboard) support these features automatically.

💡 Tip: These features work automatically when you extend ClientCRUDService, GuestCRUDService, or DashboardCRUDService. You just need to configure your model correctly.

📋 Table of Contents

1. Setup Your Model

To enable search, filter, and sort functionality, you need to configure your model with the SearchAndFilterAbilities trait and define searchable fields.

Step 1: Add the Trait

namespace App\Models; use App\Traits\SearchAndFilterAbilities; use Illuminate\Database\Eloquent\Model; class Product extends Model { use SearchAndFilterAbilities; protected $guarded = []; }

Step 2: Define Searchable Fields

Add a $searchableFields array to your model. These are the fields that can be searched, filtered, and sorted.

class Product extends Model { use SearchAndFilterAbilities; /** * Fields that can be searched, filtered, and sorted * Only fields in this array will be processed */ public array $searchableFields = [ 'name', // Regular field - searchable, filterable, sortable 'description', // Translatable field - searchable, filterable, sortable 'sku', // Regular field 'price', // Regular field 'category_id', // Foreign key - filterable, sortable 'is_active', // Boolean - filterable, sortable 'is_featured', // Boolean - filterable, sortable 'created_at', // Date - filterable, sortable 'updated_at', // Date - filterable, sortable ]; /** * If using Translatable trait, define translated attributes * These will be searched in all locales */ public array $translatedAttributes = [ 'name', 'description', ]; }
⚠️ Important: Only fields listed in $searchableFields can be used for search, filter, and sort. This is a security feature to prevent unauthorized field access.

3. Filter Functionality

Filter allows users to filter results by specific field values. You can filter by multiple fields simultaneously.

How It Works

  1. User provides a filters object in the request
  2. For each filter field:
    • If field is a date → uses whereDate($field, $value)
    • If model has a scope method (e.g., scopeActive()) → uses the scope
    • Otherwise → uses where($field, $value) (exact match)
  3. All filters are combined with AND (all conditions must match)

API Usage

# Filter by single field GET /api/client/products?filters[category_id]=5 # Filter by multiple fields GET /api/client/products?filters[category_id]=5&filters[is_active]=true # Filter by date GET /api/client/products?filters[created_at]=2024-01-15 # Combine with search GET /api/client/products?search=laptop&filters[category_id]=5&filters[is_active]=true

Example Request & Response

# Request GET /api/admin/products?filters[category_id]=5&filters[is_active]=true&filters[is_featured]=1 # Response { "status": "success", "data": [ { "id": 1, "name": "Laptop Pro", "category_id": 5, "is_active": true, "is_featured": true } ], "message": "Data retrieved successfully" }

Filter Types

Field Type Filter Behavior Example
Date Fields Uses whereDate() for date comparison filters[created_at]=2024-01-15
Scope Methods Calls model scope method if exists (e.g., scopeActive()) filters[active]=true → calls scopeActive($query, true)
Regular Fields Uses exact match where($field, $value) filters[category_id]=5where('category_id', 5)
Boolean Fields Uses exact match (true/false or 1/0) filters[is_active]=true or filters[is_active]=1

Custom Scope Filters

You can create custom scope methods that will be automatically used when filtering:

class Product extends Model { /** * This scope will be called when filtering by 'active' * Usage: filters[active]=true */ public function scopeActive($query, $value) { return $query->where('is_active', $value); } /** * This scope will be called when filtering by 'featured' * Usage: filters[featured]=1 */ public function scopeFeatured($query, $value) { return $query->where('is_featured', $value); } /** * This scope will be called when filtering by 'in_stock' * Usage: filters[in_stock]=true */ public function scopeInStock($query, $value) { if ($value) { return $query->where('stock', '>', 0); } return $query->where('stock', '<=', 0); } /** * This scope will be called when filtering by 'price_range' * Usage: filters[price_range]=100-500 */ public function scopePriceRange($query, $value) { if (str_contains($value, '-')) { [$min, $max] = explode('-', $value); return $query->whereBetween('price', [$min, $max]); } return $query; } } // Usage in API: // GET /api/products?filters[active]=true&filters[featured]=1&filters[in_stock]=true&filters[price_range]=100-500

4. Sort Functionality

Sort allows users to order results by one or more fields in ascending or descending order.

How It Works

  1. User provides a sort object in the request
  2. For each sort field:
    • If field is translatable → uses a subquery to sort by translated value
    • If field is regular → uses orderBy($field, $direction)
  3. Multiple sorts are applied in order (first sort is primary, second is secondary, etc.)

API Usage

# Sort by single field (ascending) GET /api/client/products?sort[name]=asc # Sort by single field (descending) GET /api/client/products?sort[name]=desc # Sort by multiple fields GET /api/client/products?sort[price]=desc&sort[name]=asc # Sort by translatable field (automatically uses current locale) GET /api/client/products?sort[name]=asc # Combine with search and filter GET /api/client/products?search=laptop&filters[category_id]=5&sort[price]=desc&sort[name]=asc

Example Request & Response

# Request: Sort by price (descending), then by name (ascending) GET /api/admin/products?sort[price]=desc&sort[name]=asc # Response (sorted by price high to low, then alphabetically by name) { "status": "success", "data": [ { "id": 3, "name": "Laptop Pro", "price": 1999.99 }, { "id": 1, "name": "Laptop Air", "price": 1999.99 }, { "id": 2, "name": "Laptop Basic", "price": 999.99 } ], "message": "Data retrieved successfully" }

Sort Directions

Direction Description Example
asc or ASC Ascending order (A-Z, 0-9, oldest to newest) sort[name]=asc
desc or DESC Descending order (Z-A, 9-0, newest to oldest) sort[price]=desc

Sorting Translatable Fields

When sorting by translatable fields, the system automatically uses the current locale:

# If your app locale is 'en', this will sort by English name GET /api/products?sort[name]=asc # The system generates a subquery like: # ORDER BY (SELECT name FROM product_translations # WHERE locale = 'en' AND product_id = products.id LIMIT 1) ASC

6. Custom Scopes

Custom scopes allow you to apply predefined query filters that can be triggered via request parameters or used in your service.

How to Create Custom Scopes

Create scope methods in your model that can be called via the applyScopes() method in your service:

class Product extends Model { /** * Scope: Get only active products * Usage: Product::active()->get() */ public function scopeActive($query) { return $query->where('is_active', true); } /** * Scope: Get only featured products * Usage: Product::featured()->get() */ public function scopeFeatured($query) { return $query->where('is_featured', true); } /** * Scope: Get products in a specific category * Usage: Product::inCategory(5)->get() */ public function scopeInCategory($query, $categoryId) { return $query->where('category_id', $categoryId); } /** * Scope: Get products with low stock * Usage: Product::lowStock()->get() */ public function scopeLowStock($query) { return $query->where('stock', '<', 10); } }

Using Scopes in Service

Override applyScopes() in your service to use custom scopes based on request parameters:

class ProductService extends DashboardCRUDService { /** * Apply custom scopes based on request parameters */ protected function applyScopes(Request $request): void { // Apply 'active' scope if requested if ($request->has('active')) { $this->query->active(); } // Apply 'featured' scope if requested if ($request->has('featured')) { $this->query->featured(); } // Apply 'low_stock' scope if requested if ($request->has('low_stock')) { $this->query->lowStock(); } // Apply 'category' scope if requested if ($request->has('category')) { $this->query->inCategory($request->get('category')); } } }

API Usage

# Use active scope GET /api/admin/products?active=1 # Use featured scope GET /api/admin/products?featured=1 # Use multiple scopes GET /api/admin/products?active=1&featured=1&low_stock=1 # Combine with other features GET /api/admin/products?active=1&search=laptop&sort[price]=desc

7. Column Selection

Column selection allows you to specify which columns to retrieve, reducing data transfer and improving performance.

API Usage

# Select specific columns GET /api/admin/products?columns[]=id&columns[]=name&columns[]=price # Or as comma-separated (if your API supports it) GET /api/admin/products?columns=id,name,price # Default: all columns (*)

Example Request & Response

# Request: Only get id, name, and price GET /api/admin/products?columns[]=id&columns[]=name&columns[]=price # Response (only selected columns) { "status": "success", "data": [ { "id": 1, "name": "Laptop Pro", "price": 1999.99 }, { "id": 2, "name": "Laptop Air", "price": 1299.99 } ], "message": "Data retrieved successfully" }
⚠️ Note: Column selection affects the SELECT query. Make sure to include any columns you need for relationships, sorting, or filtering.

8. Complete Examples

Here are complete examples showing how to use all features together.

Example 1: Complete Model Setup

namespace App\Models; use App\Traits\SearchAndFilterAbilities; use Astrotomic\Translatable\Translatable; use Illuminate\Database\Eloquent\Model; class Product extends Model { use SearchAndFilterAbilities; use Translatable; protected $guarded = []; // Searchable, filterable, and sortable fields public array $searchableFields = [ 'name', // Translatable 'description', // Translatable 'sku', 'price', 'category_id', 'is_active', 'is_featured', 'stock', 'created_at', 'updated_at', ]; // Translatable fields public array $translatedAttributes = [ 'name', 'description', ]; // Custom scopes public function scopeActive($query) { return $query->where('is_active', true); } public function scopeFeatured($query) { return $query->where('is_featured', true); } public function scopeLowStock($query) { return $query->where('stock', '<', 10); } public function scopeInCategory($query, $categoryId) { return $query->where('category_id', $categoryId); } }

Example 2: Complete Service Setup

namespace App\Services\Dashboard\Admin\Products; use App\Services\Dashboard\DashboardCRUDService; use App\Models\Product; use Illuminate\Http\Request; class ProductService extends DashboardCRUDService { public function __construct() { parent::__construct(new Product()); } /** * Apply custom scopes based on request */ protected function applyScopes(Request $request): void { if ($request->has('active')) { $this->query->active(); } if ($request->has('featured')) { $this->query->featured(); } if ($request->has('low_stock')) { $this->query->lowStock(); } if ($request->has('category')) { $this->query->inCategory($request->get('category')); } } }

Example 3: Complex API Request

# Complex request with all features GET /api/admin/products? search=laptop& filters[category_id]=5& filters[is_active]=true& filters[created_at]=2024-01-15& sort[price]=desc& sort[name]=asc& active=1& featured=1& per_page=20& paginate=true& columns[]=id& columns[]=name& columns[]=price& columns[]=category_id # This request will: # 1. Search for "laptop" in name, description, sku, etc. # 2. Filter by category_id = 5 # 3. Filter by is_active = true # 4. Filter by created_at = 2024-01-15 # 5. Apply 'active' scope # 6. Apply 'featured' scope # 7. Sort by price (descending), then by name (ascending) # 8. Return 20 items per page with pagination metadata # 9. Only return id, name, price, and category_id columns

Example 4: Frontend Integration (JavaScript)

// Example: Building query parameters in JavaScript function buildProductQuery(filters) { const params = new URLSearchParams(); // Search if (filters.search) { params.append('search', filters.search); } // Filters if (filters.categoryId) { params.append('filters[category_id]', filters.categoryId); } if (filters.isActive !== undefined) { params.append('filters[is_active]', filters.isActive); } // Sort if (filters.sortBy) { params.append(`sort[${filters.sortBy}]`, filters.sortOrder || 'asc'); } // Pagination if (filters.perPage) { params.append('per_page', filters.perPage); } if (filters.page) { params.append('page', filters.page); } if (filters.includePagination) { params.append('paginate', 'true'); } // Scopes if (filters.active) { params.append('active', '1'); } if (filters.featured) { params.append('featured', '1'); } return params.toString(); } // Usage const query = buildProductQuery({ search: 'laptop', categoryId: 5, isActive: true, sortBy: 'price', sortOrder: 'desc', perPage: 20, page: 1, includePagination: true, active: true, featured: true }); fetch(`/api/admin/products?${query}`) .then(response => response.json()) .then(data => { console.log(data); });

Example 5: Quick Reference Table

Feature Parameter Example Description
Search search ?search=laptop Search across all searchable fields
Filter filters[field] ?filters[category_id]=5 Filter by specific field value
Sort sort[field] ?sort[price]=desc Sort by field (asc/desc)
Pagination per_page ?per_page=20 Items per page (default: 15)
Pagination Metadata paginate ?paginate=true Include pagination info in response
Columns columns[] ?columns[]=id&columns[]=name Select specific columns
Scopes scope_name ?active=1 Apply custom scope (if implemented)
✅ Summary: All these features work automatically when you:
  1. Add SearchAndFilterAbilities trait to your model
  2. Define $searchableFields array
  3. Extend ClientCRUDService, GuestCRUDService, or DashboardCRUDService
No additional code needed! Just use the query parameters in your API requests.

🛣️ Routes & API

📅 Last Updated: 2025-12-25 | Last Documented: ✅ Complete
🔗 Related: Use the API Explorer to test endpoints interactively, see Services for controller implementation.

Route Structure

Routes are organized by user type in routes/Api/:

routes/Api/
├── guest.php - Public routes
├── client.php - Authenticated client routes
└── admin.php - Admin routes

Route Prefixes

Route File URL Prefix Middleware Description
guest.php /api/guest Public No authentication required
client.php /api/client auth:api JWT authenticated clients
admin.php /api/admin auth:api, user_type:admin Admin-only routes

Common Route Patterns

// Guest routes (public) Route::get('/countries', [GuestController::class, 'countries']); // Client routes (authenticated) Route::middleware('auth:api')->group(function () { Route::get('/profile', [ProfileController::class, 'show']); Route::put('/profile', [ProfileController::class, 'update']); }); // Admin routes (admin only) Route::middleware(['auth:api', 'user_type:admin'])->group(function () { Route::apiResource('products', ProductController::class); });

Module Routes

Modules can define their own routes in modules/{Module}/routes/api.php:

// In module service provider public function boot() { $this->loadRoutesFrom(__DIR__ . '/../routes/api.php'); }

API Response Format

All API responses follow a consistent format using the json() helper:

// Success response return json($data, 'Operation successful', 'success', 200); // Error response return json(null, 'Error message', 'fail', 400); // Response structure: { "status": "success|fail", "message": "Response message", "data": { ... } }

🎬 Streaming Service

📅 Last Updated: 2025-12-25 | Last Documented: ✅ Complete
🔗 Related: See Media Module for Laravel integration, Routes & API for endpoint configuration.

The project includes a Node.js Express service for handling media streaming and uploads.

Service Overview

Location

streaming-service/

Technology Stack
  • Express.js - Web framework
  • Multer - File upload handling
  • MySQL2 - Database connection
  • Sharp - Image processing
  • JWT - Token validation

Installation & Setup

cd streaming-service npm install cp .env.example .env # Edit .env with your configuration

Configuration

Edit streaming-service/.env:

PORT=3000 LARAVEL_API_URL=http://localhost:8000 STORAGE_PATH=../laravel12-base/storage/app/public CORS_ORIGIN=http://localhost:8000,http://localhost:3000 DB_HOST=localhost DB_PORT=3306 DB_USER=root DB_PASSWORD= DB_NAME=your_database

Running the Service

npm run dev
npm start
npm run pm2:start npm run pm2:stop npm run pm2:restart
docker build -t media-streaming-service . docker run -p 3000:3000 --env-file .env media-streaming-service

API Endpoints

1. Upload Media

Endpoint: POST /upload

Headers:

Authorization: Bearer <your-laravel-token> Content-Type: multipart/form-data

Body:

file: File or File[] (required) collection: string (optional, default: 'default')

Response:

{ "status": "success", "message": "Media uploaded successfully", "data": { "id": 123, "hash": "abc123def456..." } }

2. Stream Media

Endpoint: GET /stream/:hash

Headers:

Authorization: Bearer <token> Range: bytes=0- (optional, for seeking)

Usage Example:

// JavaScript const streamUrl = `http://localhost:3000/stream/${mediaHash}`; const video = document.createElement('video'); video.src = streamUrl; video.setAttribute('crossorigin', 'anonymous');

3. Delete Media

Endpoint: DELETE /media/:hash

Headers:

Authorization: Bearer <token>

4. Health Check

Endpoint: GET /health

Integration with Laravel

The streaming service integrates with Laravel through:

  • Token Validation: Validates JWT tokens via Laravel API
  • Media Info: Fetches media information from Laravel database
  • File Storage: Uses Laravel's storage path for file access

Service Structure

streaming-service/src/
├── server.js - Main server file
├── config/ - Configuration
├── middleware/ - Auth, error handling
├── routes/ - Route handlers
│ ├── upload.js
│ ├── stream.js
│ └── delete.js
└── utils/ - Utilities
├── laravel.js - Laravel API client
├── database.js - DB connection
└── laravelEnv.js - Env loader

⚡ Artisan Commands

📅 Last Updated: 2025-12-25 | Last Documented: ✅ Complete
🔗 Related: Generated code uses Services pattern, see Development Guide for workflow.

The project includes powerful custom Artisan commands for rapid development. These commands follow Laravel's conventions and generate boilerplate code with best practices built-in.

💡 Tip: All commands use Laravel Prompts for interactive input, making them user-friendly and intuitive.
💡 Interactive Learning: Use the floating 🎮 button or click to access the Interactive Playground where you can build and test all Artisan commands!

Available Commands

1. CRUD Generator

Command: php artisan make:crud

The most powerful command in the project. Generates a complete CRUD (Create, Read, Update, Delete) setup with all necessary files in one go.

What It Generates

Based on the type you select, it creates:

  • Controller - Handles HTTP requests (Admin/Client/Guest variants)
  • Service - Business logic layer (extends base CRUD services)
  • Form Request - Validation rules (Admin only, for store/update)
  • Resource - API response transformation
  • DetailsResource - Detailed resource for show endpoints (when needed)
  • Model - Eloquent model (optional, with migration)
  • Translation Model - For translatable models (optional)
  • Routes - API resource routes (optional, auto-registered)
Interactive Prompts

When you run the command, you'll be prompted for:

  1. Entity Name - Singular form (e.g., "Product", "Category")
  2. CRUD Type - Choose from:
    • admin - Full CRUD (index, show, store, update, destroy)
    • client - Read-only (index, show only)
    • guest - Read-only (index, show only)
  3. Path - Where to place the files (defaults based on type)
  4. Create Model? - Whether to generate a model with migration
  5. Model Name - If creating model, specify the name
  6. Needs Translation? - Whether the model should be translatable
  7. Add Routes? - Whether to register API resource routes
  8. Route File - Which route file to use (defaults based on type)
  9. Place in Group? - Whether to place route inside middleware group
Example Usage
# Run the command php artisan make:crud # Interactive session example: # 1. Enter the name: Product # 2. Select type: admin # 3. Path (default: Api/Dashboard/Admin/): [Enter] # 4. Create model? Yes # 5. Model name: Product # 6. Needs translation? Yes # 7. Add routes? Yes # 8. Route file (default: Api/admin.php): [Enter] # 9. Place in group? Yes # Generated files: # - app/Http/Controllers/Api/Dashboard/Admin/Products/ProductController.php # - app/Http/Requests/Api/Dashboard/Admin/Products/ProductRequest.php # - app/Http/Resources/Api/Dashboard/Admin/Products/ProductResource.php # - app/Http/Resources/Api/Dashboard/Admin/Products/ProductDetailsResource.php # - app/Services/Dashboard/Admin/Products/ProductService.php # - app/Models/Product.php # - app/Models/ProductTranslation.php # - database/migrations/XXXX_create_products_table.php # - routes/Api/admin.php (route added)
CRUD Types Explained
Admin CRUD:
  • Full CRUD operations: index, show, store, update, destroy
  • Includes Form Request for validation
  • Extends DashboardCRUDService
  • Uses custom_controller.stub
  • Path: Api/Dashboard/Admin/
Client CRUD:
  • Read-only: index, show only
  • No Form Request (no write operations)
  • Extends ClientCRUDService
  • Uses client_controller.stub
  • Path: Api/App/Client/
  • Always includes DetailsResource for show endpoint
Guest CRUD:
  • Read-only: index, show only
  • Public access (no authentication required)
  • Extends ClientCRUDService
  • Uses guest_controller.stub
  • Path: Api/App/Guest/
Translation Support

If you enable translations, the command will:

  • Add Translatable trait to the model
  • Implement TranslatableContract interface
  • Create a translation model (e.g., ProductTranslation)
  • Add translation table to migration
  • Generate DetailsResource for showing translated fields
// Generated translatable model structure class Product extends Model implements TranslatableContract { use Translatable; public array $translatedAttributes = []; // Usage: $product->translate('en')->name; $product->name; // Current locale }
Route Registration

When you choose to add routes, the command will:

  • Automatically detect the correct route file based on type
  • Add the use statement for the controller
  • Register Route::apiResource() with appropriate methods
  • Optionally place it inside the last middleware group
// Admin routes (full CRUD) Route::apiResource('products', ProductController::class) ->only(['index', 'show', 'store', 'update', 'destroy']); // Client/Guest routes (read-only) Route::apiResource('products', ProductController::class) ->only(['index', 'show']);

2. Service Generator

Command: php artisan make:service {name?} {--path=} {--force}

Generates a service class following the service layer pattern. Services contain business logic and are organized by context.

Command Options
Option Description Example
name Service class name (singular, optional - will prompt if not provided) ProductService
--path= Destination directory (relative to project root) app/Services/App/Client/Products/
--force Overwrite if file already exists Flag (no value)
Usage Examples
# Interactive mode (will prompt for name and path) php artisan make:service # With name only (will prompt for path) php artisan make:service ProductService # With name and path php artisan make:service ProductService --path=app/Services/App/Client/Products/ # Force overwrite existing file php artisan make:service ProductService --path=app/Services/App/Client/Products/ --force
Generated Service Structure
namespace App\Services\App\Client\Products; use App\Services\App\Client\ClientCRUDService; use App\Models\Product; class ProductService extends ClientCRUDService { public function __construct() { parent::__construct(new Product()); } protected function scopedQuery() { return $this->model->query(); } }
Service Organization

Services are organized by context:

  • app/Services/App/Client/ - Client-facing services
  • app/Services/Dashboard/ - Admin dashboard services
  • app/Services/Shared/ - Shared services
  • app/Services/Utilities/ - Utility services

3. Enum Generator

Command: php artisan make:enum

Generates a PHP enum class with typed cases. Supports both string and integer-backed enums.

Interactive Prompts
  1. Enum Name - The class name (e.g., "UserType", "TransactionStatus")
  2. Data Type - Choose "string" or "int"
  3. States/Cases - Add multiple enum cases:
    • State name (uppercase, underscores allowed)
    • State value (must match data type)
Example Usage
# Run the command php artisan make:enum # Interactive session: # 1. Enum name: TransactionStatus # 2. Data type: string # 3. State name: PENDING # State value: pending # 4. Add another? Yes # 5. State name: COMPLETED # State value: completed # 6. Add another? Yes # 7. State name: FAILED # State value: failed # 8. Add another? No # Generated file: app/Enums/TransactionStatus.php
Generated Enum Example
namespace App\Enums; enum TransactionStatus: string { case PENDING = 'pending'; case COMPLETED = 'completed'; case FAILED = 'failed'; } // Usage: $status = TransactionStatus::PENDING; $value = $status->value; // 'pending' $name = $status->name; // 'PENDING'
Integer-Backed Enum Example
# For integer enums: enum Priority: int { case LOW = 1; case MEDIUM = 2; case HIGH = 3; case URGENT = 4; }
Validation Rules
  • State names must be uppercase letters and underscores only
  • Integer values must be valid integers
  • String values can be any string
  • Enum name will be automatically capitalized

4. Export Postman Collection

Command: php artisan export:postman

Exports all API routes to a Postman collection format (JSON). Perfect for API testing and documentation.

What It Does
  • Scans all registered API routes
  • Extracts route information (method, URI, middleware, etc.)
  • Generates a Postman Collection v2.1 format JSON file
  • Organizes routes by their prefixes (guest, client, admin)
  • Includes route parameters and query strings
Usage
# Export to Postman collection php artisan export:postman # Output: postman/collection.json
Collection Structure

The generated collection includes:

  • Collection metadata (name, description, schema version)
  • Folders organized by route prefixes
  • Request items with:
    • HTTP method (GET, POST, PUT, DELETE, etc.)
    • Full URL with parameters
    • Headers (including Authorization placeholder)
    • Request body examples (for POST/PUT)

5. Export API Documentation

Command: php artisan export:api-docs

Generates comprehensive API documentation from your routes. Creates markdown or HTML documentation.

Features
  • Documents all API endpoints
  • Includes request/response examples
  • Lists required parameters
  • Shows authentication requirements
  • Groups endpoints by functionality
Usage
# Generate API documentation php artisan export:api-docs # Output: postman/docs/api-documentation.md

6. Smart Factory Generator

Command: php artisan make:factory {model}

Generates a factory with intelligent, realistic fake data based on the model's structure and field names.

Smart Features
  • Field Name Detection - Analyzes column names to generate appropriate fake data
  • Realistic Data - Uses context-aware fakers (emails for email fields, names for name fields, etc.)
  • Relationship Support - Handles foreign keys and relationships
  • Localization - Supports multiple locales for translatable fields
  • Type Detection - Automatically detects field types (string, integer, boolean, etc.)
Usage
# Generate factory for a model php artisan make:factory Product # Generated: database/factories/ProductFactory.php
Example Generated Factory
use Illuminate\Database\Eloquent\Factories\Factory; class ProductFactory extends Factory { protected $model = Product::class; public function definition(): array { return [ 'name' => $this->faker->words(3, true), 'description' => $this->faker->paragraph(), 'price' => $this->faker->randomFloat(2, 10, 1000), 'stock' => $this->faker->numberBetween(0, 100), 'is_active' => $this->faker->boolean(80), 'category_id' => Category::factory(), 'created_at' => now(), 'updated_at' => now(), ]; } }
Field Name Patterns

The factory recognizes common patterns:

Field Pattern Generated Data
*_id Foreign key (creates related model)
email Valid email address
phone Phone number
name, title Words or sentence
description, content Paragraph text
price, amount Decimal number
is_*, has_* Boolean value
created_at, updated_at Current timestamp

7. Fill Request Command

Command: php artisan fill:request {path} {--model=} {--with-translation} {--force}

Automatically fills existing FormRequest classes with validation rules extracted from the model's database schema. Saves time by generating validation rules based on column types and constraints.

Command Options
Option Description Example
path Request file path (required) Api/Dashboard/Admin/Cities/CityRequest
--model= Model class name (auto-detected if not provided) City
--with-translation Include translatable fields even if not detected Flag (no value)
--force Overwrite existing rules and attributes Flag (no value)
What It Does
  • Analyzes the model's database table structure
  • Generates validation rules based on column types (string, integer, boolean, etc.)
  • Handles foreign keys with exists rules
  • Detects nullable columns and applies sometimes rule
  • Generates human-readable attribute labels
  • Supports translatable models with locale-based rules
  • Uses $rule variable for create/update logic (required on create, sometimes on update)
Usage Examples
# Fill request with auto-detected model php artisan fill:request Api/Dashboard/Admin/Cities/CityRequest # Specify model explicitly php artisan fill:request Api/Dashboard/Admin/Cities/CityRequest --model=City # Include translation fields php artisan fill:request Api/Dashboard/Admin/Cities/CityRequest --with-translation # Force overwrite existing rules php artisan fill:request Api/Dashboard/Admin/Cities/CityRequest --force
Generated Rules Example
public function rules(): array { $rule = getModel($this->route('city'), City::class) ? 'sometimes' : 'required'; $rules = [ 'country_id' => [$rule, 'exists:countries,id'], 'name' => ['sometimes', 'string', 'max:255'], 'is_active' => ['sometimes', 'boolean'], 'lat' => ['sometimes', 'numeric', 'between:-90,90'], 'lng' => ['sometimes', 'numeric', 'between:-180,180'], ]; // Translatable fields foreach (config('translatable.locales') as $locale) { $rules["{$locale}.name"] = [$rule, 'string', 'max:255']; } return $rules; }
Rule Generation Logic
Column Type/Pattern Generated Rules
*_id (foreign key) [$rule, 'exists:table,id']
string ['sometimes', 'string', 'max:255']
integer ['sometimes', 'numeric']
boolean or is_* ['sometimes', 'boolean']
decimal/float ['sometimes', 'numeric']
date/datetime ['sometimes', 'date']
json ['sometimes', 'array']
lat ['sometimes', 'numeric', 'between:-90,90']
lng ['sometimes', 'numeric', 'between:-180,180']

8. Fill Resource Command

Command: php artisan fill:resource {path} {--model=} {--force}

Automatically fills existing Resource classes with model attribute mapping. Generates the toArray() method based on the model's database columns and relationships.

Command Options
Option Description Example
path Resource file path (required) Api/Dashboard/Admin/Cities/CityResource
--model= Model class name (auto-detected if not provided) City
--force Overwrite existing toArray method Flag (no value)
What It Does
  • Scans the model's database table for all columns
  • Generates field mappings in toArray() method
  • Detects and includes relationships (BelongsTo, HasMany, HasOne)
  • Handles translatable models (uses HasTranslatedAttributes trait for DetailsResource)
  • Groups location fields (lat, lng) into a location object
  • Automatically finds both Resource and DetailsResource files if they exist
  • Uses null-safe operators (?->) for safe property access
Usage Examples
# Fill resource with auto-detected model php artisan fill:resource Api/Dashboard/Admin/Cities/CityResource # Specify model explicitly php artisan fill:resource Api/Dashboard/Admin/Cities/CityResource --model=City # Force overwrite existing toArray method php artisan fill:resource Api/Dashboard/Admin/Cities/CityResource --force
Generated Resource Example
public function toArray($request): array { return [ 'id' => @$this?->id, 'country_id' => @$this?->country_id, 'is_active' => @$this?->is_active, 'location' => [ 'lat' => @$this?->lat, 'lng' => @$this?->lng, ], 'country' => [ 'id' => @$this?->country?->id, 'name' => @$this?->country?->name, 'short_name' => @$this?->country?->short_name, ], 'created_at' => @$this?->created_at, ]; }
DetailsResource with Translations
use App\Traits\HasTranslatedAttributes; class CityDetailsResource extends JsonResource { use HasTranslatedAttributes; public function toArray(Request $request): array { $data = [ 'id' => @$this?->id, 'country_id' => @$this?->country_id, 'is_active' => @$this?->is_active, 'created_at' => @$this?->created_at, ]; // Translated fields are merged automatically return array_merge($data, $this->getTranslatedData()); } }
Features
  • Auto-detection: Finds both Resource and DetailsResource files
  • Relationship Support: Automatically includes BelongsTo relationships
  • Location Grouping: Combines lat/lng into location object
  • Translation Support: Handles translatable models correctly
  • Safe Access: Uses null-safe operators to prevent errors
  • Trait Integration: Automatically adds HasTranslatedAttributes trait when needed

4. Export Postman Collection (Enhanced)

Command: php artisan export:postman {--bearer=} {--basic=} {--with-docs} {--only-uri=} {--except-uri=}

Exports all API routes to a Postman collection format (JSON) with advanced filtering and authentication options.

Command Options
Option Description Example
--bearer= Bearer token to use on authenticated endpoints your-jwt-token
--basic= Basic auth credentials username:password
--with-docs Also generate markdown API docs alongside collection Flag (no value)
--only-uri= Comma-separated URI substrings to include api/admin,countries
--except-uri= Comma-separated URI substrings to exclude api/guest,test
Usage Examples
# Basic export php artisan export:postman # With bearer token php artisan export:postman --bearer=eyJ0eXAiOiJKV1QiLCJhbGc... # With docs generation php artisan export:postman --with-docs # Only admin routes php artisan export:postman --only-uri=api/admin # Exclude test routes php artisan export:postman --except-uri=test,debug # Combined options php artisan export:postman --bearer=token123 --with-docs --only-uri=api/admin,api/client
Configuration

Configure export behavior in config/api-postman.php:

  • filename - Output filename pattern (supports {timestamp}, {app})
  • output_path - Directory for collection file
  • base_url - API base URL
  • enable_formdata - Extract validation rules from FormRequests
  • structured - Organize routes into folders
  • crud_folders - Create folders for CRUD actions
💡 Pro Tip: Combine these commands for rapid development. Start with make:crud to generate the structure, use fill:request and fill:resource to auto-fill validation and response mapping, then use make:factory to create test data, and finally export:postman to test your API endpoints!

📦 Complete Project Inventory

This section provides a comprehensive overview of all components in the project.

Models (26 Total)

Core Models
  • User - User authentication and profile
  • Address - User addresses
  • Device - User devices for notifications
  • Wallet - User wallet system
  • Transaction - Wallet transactions
  • WithdrawRequest - Withdrawal requests
E-Commerce Models
  • Product - Products catalog
  • ProductTranslation - Product translations
  • ProductVariation - Product variations
  • ProductAttribute - Product attributes
  • VariationAttribute - Variation attributes
  • Category - Product categories
  • CategoryTranslation - Category translations
  • Attribute - Product attributes
  • AttributeTranslation - Attribute translations
  • Value - Attribute values
  • ValueTranslation - Value translations
Location Models
  • Country - Countries
  • CountryTranslation - Country translations
  • City - Cities
  • CityTranslation - City translations
Content Models
  • FAQ - Frequently asked questions
  • FAQTranslation - FAQ translations
  • StaticPage - Static pages (About, Terms, etc.)
  • StaticPageTranslation - Static page translations
  • Slider - Homepage sliders
  • ShowRoom - Showroom locations
  • ShowRoomTranslation - Showroom translations
Authorization Models
  • Role - User roles
  • RoleTranslation - Role translations
  • Permission - Permissions
  • PermissionTranslation - Permission translations

Enums (11 Total)

  • UserType - SUPER_ADMIN, SUPERVISOR, CLIENT, GUEST
  • TransactionStatus - Transaction status values
  • TransactionType - Transaction type values
  • WithdrawStatus - Withdrawal status values
  • PaymentMethod - Payment method types
  • VerificationToken - Verification token types
  • NotificationScenario - Notification scenarios
  • MediaType - Media file types
  • FAQType - FAQ categories
  • StaticPageType - Static page types
  • Continent - Continent enumeration

Services Structure

App Services (Client-facing)
  • AddressService - Address management
  • AuthenticationService - Client authentication
  • FAQService - FAQ retrieval
  • ShowRoomService - Showroom listing
  • StaticPageService - Static page retrieval
  • WalletService - Wallet operations
Dashboard Services (Admin)
  • AttributeService - Attribute management
  • CategoryService - Category management
  • CityService - City management
  • CountryService - Country management
  • FAQService - FAQ management
  • ProductService - Product management
  • ProductVariationService - Variation management
  • RoleService - Role management
  • ShowRoomService - Showroom management
  • SliderService - Slider management
  • StaticPageService - Static page management
  • SupervisorService - Supervisor management
  • ValueService - Value management
Shared Services
  • AuthenticationService - Shared auth logic
  • ProfileService - Profile management
Utility Services
  • FCMNotificationChannel - Firebase Cloud Messaging
  • RedisNotificationChannel - Redis notifications
  • NotifyOrchestrator - Notification orchestration
  • StateTransitionService - State machine transitions
  • ServiceGenerator - Service class generator

Controllers Structure

Admin Controllers (Dashboard)
  • AuthenticationController - Admin login/logout
  • ProfileController - Admin profile
  • AttributeController - Attributes CRUD
  • CategoryController - Categories CRUD
  • CityController - Cities CRUD
  • CountryController - Countries CRUD
  • FAQController - FAQs CRUD
  • ProductController - Products CRUD
  • ProductVariationController - Variations CRUD
  • RoleController - Roles CRUD
  • ShowRoomController - Showrooms CRUD
  • SliderController - Sliders CRUD
  • StaticPageController - Static pages CRUD
  • SupervisorController - Supervisors CRUD
  • ValueController - Values CRUD
Client Controllers
  • AuthenticationController - Client register/login/verify
  • ProfileController - Client profile management
  • AddressController - Addresses CRUD
  • WalletController - Wallet operations
  • FAQController - FAQ listing
  • ShowRoomController - Showroom listing
  • StaticPageController - Static page retrieval
Guest Controllers
  • GuestController - Public endpoints (countries, cities)

Middleware (6 Total)

  • DetectUserType - Detects user type from request (global)
  • SetLocale - Sets application locale (global)
  • GzipMiddleware - Response compression (global)
  • CheckUserType - Validates user type (route middleware)
  • ActiveMiddleware - Ensures user is active (route middleware)
  • PermissionMiddleware - Checks user permissions (route middleware)

Routes Structure

Admin Routes (/api/admin)
  • Authentication: login, logout
  • Profile: show, update, change-password, settings
  • Resources: roles, supervisors, countries, cities, static-pages, faqs, show-rooms, products, product-variations, attributes, values, sliders
Client Routes (/api/client)
  • Authentication: register, verify, resend-code, login, logout
  • Profile: show, update, change-phone, confirm-phone, change-email, confirm-email, resend-code, settings, delete-account
  • Wallet: show, charge, withdraw, transactions
  • Resources: addresses (full CRUD)
Guest Routes (/api/guest)
  • Public: countries, cities
  • Content: faqs (index), static-pages (index), show-rooms (index)

Modules (4 Total)

  • Media - Media file management
  • MediaExpress - Express.js integration
  • Notification - Notification system
  • Verification - Email/SMS verification

Artisan Commands (8 Total)

  • make:crud - Generate complete CRUD
  • make:service - Generate service class
  • make:enum - Generate enum class
  • export:postman - Export Postman collection
  • export:api-docs - Generate API documentation
  • make:smart-factory - Generate smart factory
  • fill:request - Fill FormRequest with rules
  • fill:resource - Fill Resource with mappings

🏛️ Architectural Patterns & Concepts

This section explains the core architectural patterns, traits, and concepts used throughout the project.

Dashboard Controller Pattern

The DashboardBaseController is an abstract base controller for all admin/dashboard CRUD operations. It provides a standardized way to handle full CRUD operations with minimal code.

Key Features

  • Automatic Model Resolution - Automatically resolves models from route parameters
  • Permission-Based Middleware - Automatically registers permission middleware per action
  • Create or Update Mode - Supports create_or_update mode with key columns
  • Request Injection - Automatically injects FormRequest classes
  • Resource Transformation - Uses separate resources for index and show

Controller Structure

class ProductController extends DashboardBaseController { public function __construct(ProductService $service) { $requirements = [ 'model' => Product::class, 'with' => ['category', 'media'], // Eager load for index 'load' => ['translations', 'media'], // Eager load for show 'service' => $service, 'storeRequest' => ProductRequest::class, 'updateRequest' => ProductRequest::class, 'indexResource' => ProductResource::class, 'showResource' => ProductDetailsResource::class, 'permissions' => [ 'index' => 'index-products', 'show' => 'show-products', 'store' => 'store-products', 'update' => 'update-products', 'destroy' => 'destroy-products', ], // Optional: Create or Update mode 'storeMode' => 'create_or_update', // or 'create' 'updateOrCreateKeyColumn' => 'slug', // or ['email', 'slug'] ]; parent::__construct($requirements); } }
💡 Interactive Learning: Click the or to open the Interactive Playground in a separate window where you can practice and learn!
🎮 Interactive Controller Builder
For multiple columns, use comma: email,slug
🧪 Interactive API Tester

Available Methods

Method Purpose Route
index() List all resources with filtering, searching, pagination GET /api/admin/products
show($model) Show single resource with eager loaded relations GET /api/admin/products/{id}

Create or Update Mode

The controller supports an intelligent create_or_update mode that automatically updates existing records if they match certain criteria, otherwise creates new ones.

Use Case: Useful when you want to allow upsert operations based on unique keys like email, slug, or combination of fields.
// In controller constructor 'storeMode' => 'create_or_update', 'updateOrCreateKeyColumn' => 'slug', // Single column // Or multiple columns 'updateOrCreateKeyColumn' => ['email', 'phone'], // Or translatable fields 'updateOrCreateKeyColumn' => ['en.name', 'ar.name'], // The controller will: // 1. Check if a record exists matching the key column(s) // 2. If exists, update it // 3. If not, create a new one

How It Works

  1. When store() is called, it checks storeMode
  2. If create_or_update, it extracts values from request for key columns
  3. For translatable fields, it checks all locales (e.g., en.name, ar.name)
  4. Queries the database using resolveModelByConditions()
  5. If found, passes the model to service->save() for update
  6. If not found, creates a new model

Dashboard Service Pattern

The DashboardCRUDService is the base service class for all admin CRUD operations. It provides advanced features like pipelines, filtering, searching, and sorting.

Key Features

  • Pipeline-Based Saving - Uses Laravel Pipeline for save operations
  • Advanced Filtering - Supports filtering by any searchable field
  • Full-Text Search - Searches across multiple fields including translations
  • Smart Sorting - Handles sorting for both regular and translatable fields
  • Pagination - Built-in pagination support
  • Transaction Support - All save operations wrapped in transactions
  • Custom Hooks - beforeSave, afterSave, beforeDestroy, afterDestroy

Service Structure

class ProductService extends DashboardCRUDService { public function __construct() { parent::__construct(new Product()); } // Override scoped query for default filtering protected function scopedQuery(): Builder { return $this->model->query() ->where('is_active', true); } // Override to add custom scopes protected function applyScopes(Request $request): void { if ($request->has('featured')) { $this->query->where('is_featured', true); } } // Custom save logic (optional) public function beforeSave($request, $model) { // Modify request data before saving return [ 'custom_field' => 'custom_value', // ... other fields ]; } public function afterSave($request, $model) { // Post-save operations (e.g., clear cache, send notifications) } public function syncRelations($request, $model) { // Sync many-to-many relationships if ($request->has('tags')) { $model->tags()->sync($request->tags); } } }

Index Method Features

The index() method supports powerful query parameters:

Parameter Type Description Example
search string Search across all searchable fields ?search=laptop
filters[field] mixed Filter by specific field ?filters[category_id]=5
sort[field] asc|desc Sort by field ?sort[name]=asc
per_page integer Items per page ?per_page=20
paginate boolean Return paginated response ?paginate=true
columns array Select specific columns ?columns[]=id&columns[]=name

Example API Requests

// Search products GET /api/admin/products?search=laptop // Filter by category and active status GET /api/admin/products?filters[category_id]=5&filters[is_active]=1 // Sort by name ascending GET /api/admin/products?sort[name]=asc // Combined: search, filter, sort, paginate GET /api/admin/products?search=laptop&filters[category_id]=5&sort[price]=desc&per_page=20&paginate=true // Select specific columns GET /api/admin/products?columns[]=id&columns[]=name&columns[]=price

Client Controller Pattern

The ClientBaseController is similar to DashboardBaseController but designed for read-only operations (index and show only).

Key Differences

  • Read-Only - Only index() and show() methods
  • No Form Requests - No store/update requests needed
  • Simpler Structure - Fewer requirements in constructor
  • Client-Facing - Designed for authenticated client users

Controller Structure

class FAQController extends ClientBaseController { public function __construct(FAQService $service) { $requirements = [ 'model' => FAQ::class, 'with' => ['category'], // Eager load for index 'load' => ['category'], // Eager load for show 'service' => $service, 'indexResource' => FAQResource::class, 'showResource' => FAQDetailsResource::class, 'permissions' => [ 'index' => 'view-faqs', 'show' => 'view-faqs', ], ]; parent::__construct($requirements); } }

Pipeline Pattern

The project uses Laravel's Pipeline pattern for processing save operations. This allows for clean, testable, and extensible code.

What is a Pipeline?

A pipeline is a series of processing steps (pipes) that data passes through. Each pipe can modify the data or perform side effects before passing it to the next pipe.

Save Pipeline Flow

When service->save() is called, the following pipeline executes:

  1. BeforeSave - Calls service->beforeSave() hook
  2. FillModel - Fills model with request data
  3. SaveModel - Saves the model to database
  4. SyncRelations - Calls service->syncRelations() hook
  5. AfterSave - Calls service->afterSave() hook

Pipeline Classes

// app/Pipelines/Base/Save/BeforeSave.php class BeforeSave { public function __invoke(array $payload, \Closure $next) { [$request, $model, $service] = $payload; // Call beforeSave hook if exists if (method_exists($service, 'beforeSave')) { $overrides = $service->beforeSave($request, $model); // Can return modified data to override fillable data if (is_array($overrides)) { $request->attributes->set('__fillableData', $overrides); } } return $next([$request, $model, $service]); } } // app/Pipelines/Base/Save/FillModel.php class FillModel { public function __invoke(array $payload, \Closure $next) { [$request, $model, $service] = $payload; // Get fillable data (from beforeSave or validated request) $fillable = $request->attributes->get('__fillableData', $request->validated()); // Fill the model $model->fill($fillable); return $next([$request, $model, $service]); } } // app/Pipelines/Base/Save/SaveModel.php class SaveModel { public function __invoke(array $payload, \Closure $next) { [$request, $model, $service] = $payload; // Save to database $model->save(); return $next([$request, $model, $service]); } } // app/Pipelines/Base/Save/SyncRelations.php class SyncRelations { public function __invoke(array $payload, \Closure $next) { [$request, $model, $service] = $payload; // Call syncRelations hook if exists if (method_exists($service, 'syncRelations')) { $service->syncRelations($request, $model); } return $next([$request, $model, $service]); } } // app/Pipelines/Base/Save/AfterSave.php class AfterSave { public function __invoke(array $payload, \Closure $next) { [$request, $model, $service] = $payload; // Call afterSave hook if exists if (method_exists($service, 'afterSave')) { $service->afterSave($request, $model); } return $next([$request, $model, $service]); } }

Customizing the Pipeline

You can override the pipeline in your service:

class ProductService extends DashboardCRUDService { protected function getSavePipes(): array { return [ \App\Pipelines\Base\Save\BeforeSave::class, \App\Pipelines\Base\Save\FillModel::class, \App\Pipelines\Base\Save\SaveModel::class, \App\Pipelines\Base\Save\SyncRelations::class, \App\Pipelines\Base\Save\AfterSave::class, // Add custom pipes here \App\Pipelines\Products\ProcessImages::class, \App\Pipelines\Products\UpdateInventory::class, ]; } }

Creating Custom Pipes

namespace App\Pipelines\Products; class ProcessImages { public function __invoke(array $payload, \Closure $next) { [$request, $model, $service] = $payload; // Process images if provided if ($request->has('images')) { // Your image processing logic $model->processImages($request->images); } return $next([$payload]); } }

SearchAndFilterAbilities Trait

The SearchAndFilterAbilities trait provides powerful search and filter capabilities to models.

How to Use

use App\Traits\SearchAndFilterAbilities; class Product extends Model { use SearchAndFilterAbilities; // Define which fields are searchable public array $searchableFields = [ 'name', // Regular field 'description', // Translatable field 'sku', 'price', 'category_id', 'is_active', 'created_at', ]; // Define translatable fields (if using Translatable trait) public array $translatedAttributes = [ 'name', 'description', ]; }

Trait Methods

Method Purpose Usage
scopeSearch($query, $search) Search across all searchable fields Product::search('laptop')->get()
scopeFilter($query, $filters) Filter by specific fields Product::filter(['category_id' => 5])->get()

How Search Works

  1. Checks if $searchableFields array exists on model
  2. For each searchable field:
    • If field is translatable → uses orWhereTranslationLike()
    • If field is regular → uses orWhere($field, 'LIKE', "%{$search}%")
  3. Combines all conditions with OR (searches across all fields)

How Filter Works

  1. Accepts an array of field => value pairs
  2. For each filter:
    • If field is a date → uses whereDate()
    • If model has a scope method (e.g., scopeActive()) → uses the scope
    • Otherwise → uses where($field, $value)
  3. Only filters fields that are in $searchableFields

Example Usage

// Search across all searchable fields $products = Product::search('laptop')->get(); // Filter by specific fields $products = Product::filter([ 'category_id' => 5, 'is_active' => true, ])->get(); // Combined search and filter $products = Product::search('laptop') ->filter(['category_id' => 5]) ->get(); // In service (automatic via DashboardCRUDService) // GET /api/admin/products?search=laptop&filters[category_id]=5

Custom Scopes

You can create custom scope methods that will be automatically used by the filter:

class Product extends Model { // This scope will be called when filtering by 'active' public function scopeActive($query) { return $query->where('is_active', true); } // This scope will be called when filtering by 'featured' public function scopeFeatured($query, $value) { return $query->where('is_featured', $value); } } // Usage: Product::filter(['active' => true, 'featured' => 1])->get(); // Automatically uses scopeActive() and scopeFeatured()

HasMedia Trait

The HasMedia trait from the Media module provides seamless media file management for models.

Key Features

  • Polymorphic Relationship - Models can have multiple media files
  • Collections - Organize media into collections (e.g., 'image', 'gallery', 'documents')
  • Single/Multiple Files - Support for both single files and multiple files per collection
  • Hash-Based - Uses media hashes for referencing (from streaming service)
  • Automatic Cleanup - Deletes files when model is deleted
  • URL Generation - Automatic URL generation for media files

How to Use

use Modules\Media\Traits\HasMedia; class Product extends Model { use HasMedia { HasMedia::setAttribute as setMediaAttribute; HasMedia::getAttribute as getMediaAttribute; } // Define media collections public array $media_keys = [ 'image' => false, // Single file (false) 'gallery' => true, // Multiple files (true) 'video' => false, // Single video file ]; }

Setting Media

Media is set using hashes (from streaming service upload):

// Single file (image collection) $product->image = 'abc123def456'; // Media hash // Multiple files (gallery collection) $product->gallery = ['hash1', 'hash2', 'hash3']; // Or comma-separated string $product->gallery = 'hash1,hash2,hash3'; // Or array of objects $product->gallery = [ ['hash' => 'hash1'], ['hash' => 'hash2'], ]; $product->save(); // Media is attached after save

Getting Media

// Get single file URL $imageUrl = $product->image; // Returns: "http://domain.com/storage/path/to/file.jpg" // Or null if no media // Get multiple file URLs $galleryUrls = $product->gallery; // Returns: ["url1", "url2", "url3"] // Or null if no media // Get media with IDs $imageWithId = $product->getAttributeWithId('image'); // Returns: ['id' => 123, 'url' => 'http://...'] $galleryWithIds = $product->getAttributeWithId('gallery'); // Returns: [ // ['id' => 123, 'url' => 'http://...'], // ['id' => 124, 'url' => 'http://...'], // ] // Get all media URLs $allMedia = $product->media_urls; // Returns: [ // 'image' => 'http://...', // 'gallery' => ['http://...', 'http://...'], // 'video' => null, // ] // Access relationship directly $media = $product->media; // Collection of Media models $imageMedia = $product->media()->where('collection', 'image')->get();

How It Works

  1. Setting Media:
    • When you set $product->image = 'hash', it's stored in $pendingMediaHashes
    • On save(), the saved event fires
    • The trait's bootHasMedia() method handles the event
    • Media records are attached via attachMediaByHash()
  2. Getting Media:
    • When you access $product->image, it checks if it's a media key
    • Queries the media relationship
    • Returns URL(s) for the collection
  3. Deleting Media:
    • When model is deleted, deleting event fires
    • All associated media files are deleted from storage
    • Media records are force deleted from database

Integration with Translatable

When using both HasMedia and Translatable traits, you need to handle attribute resolution:

class Product extends Model { use HasMedia { HasMedia::setAttribute as setMediaAttribute; HasMedia::getAttribute as getMediaAttribute; } use Translatable { Translatable::setAttribute as setTranslatableAttribute; Translatable::getAttribute as getTranslatableAttribute; } public function setAttribute($key, $value) { // Check media first if ($this->isMediaKey($key)) { return $this->setMediaAttribute($key, $value); } // Then check translatable return $this->setTranslatableAttribute($key, $value); } public function getAttribute($key) { // Check media first if ($this->isMediaKey($key)) { return $this->getMediaAttribute($key); } // Then check translatable return $this->getTranslatableAttribute($key); } }

Database Transactions

All save operations in DashboardCRUDService are wrapped in database transactions to ensure data integrity.

How It Works

public function save($request, $id = null, ?Closure $customSaveLogic = null, $resource = null, $load = []): JsonResponse { DB::beginTransaction(); try { // Resolve or create model if ($id instanceof Model) { $this->model = $id; } else { $this->model = $id ? $this->resolveModelOrFail($id) : new $this->model; } // Run save pipeline or custom logic if ($customSaveLogic !== null) { $customSaveLogic($request, $this->model); } else { $this->runSavePipeline($request); } // Commit transaction DB::commit(); // Load relations if needed if (!empty($load)) { $this->model->load($load); } return json($resource ? $resource::make($this->model) : null, __('Data saved successfully')); } catch (\Exception $e) { // Rollback on error DB::rollBack(); return $this->handleException($e); } }

Benefits

  • Atomicity - All operations succeed or fail together
  • Data Integrity - No partial saves
  • Error Recovery - Automatic rollback on exceptions
  • Consistency - Database remains in consistent state

Manual Transactions

You can also use transactions manually in your services:

use Illuminate\Support\Facades\DB; class ProductService extends DashboardCRUDService { public function complexOperation($data) { DB::beginTransaction(); try { // Create product $product = Product::create($data); // Create variations foreach ($data['variations'] as $variation) { $product->variations()->create($variation); } // Update inventory $this->updateInventory($product); // Send notifications $this->notifyAdmins($product); DB::commit(); return $product; } catch (\Exception $e) { DB::rollBack(); throw $e; } } }

Fillable vs Guarded

Laravel uses $fillable or $guarded to control mass assignment.

Guarded Approach (Used in This Project)

This project uses protected $guarded = []; which allows mass assignment of all fields except those explicitly guarded.

⚠️ Security Note: Using $guarded = [] means ALL fields are mass assignable. Always validate input using FormRequest classes!
class Product extends Model { // Allow mass assignment of all fields protected $guarded = []; // This is safe because: // 1. FormRequest validation ensures only valid fields are submitted // 2. Only validated data reaches the model // 3. Sensitive fields (like 'is_admin') are not in the request }

Fillable Approach (Alternative)

class Product extends Model { // Only these fields can be mass assigned protected $fillable = [ 'name', 'description', 'price', 'stock', 'category_id', // ... other safe fields ]; // Fields NOT in fillable cannot be mass assigned // Even if they're in the request, they'll be ignored }

Best Practice

This project uses $guarded = [] because:

  • FormRequest validation provides the security layer
  • More flexible - don't need to update fillable array for every new field
  • Validation rules in FormRequest define what's allowed
  • Easier to maintain with many models

Translatable Attributes

The project uses astrotomic/laravel-translatable for multi-language support.

How to Use

use Astrotomic\Translatable\Translatable; use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract; class Product extends Model implements TranslatableContract { use Translatable { Translatable::setAttribute as setTranslatableAttribute; Translatable::getAttribute as getTranslatableAttribute; } // Define which fields are translatable public array $translatedAttributes = [ 'name', 'description', 'slug', ]; }

Database Structure

Translatable models require a translation table:

// Main table: products Schema::create('products', function (Blueprint $table) { $table->id(); $table->decimal('price'); $table->integer('stock'); $table->timestamps(); }); // Translation table: product_translations Schema::create('product_translations', function (Blueprint $table) { $table->id(); $table->foreignId('product_id')->constrained()->onDelete('cascade'); $table->string('locale')->index(); $table->string('name'); $table->text('description'); $table->string('slug'); $table->unique(['product_id', 'locale']); });

Setting Translations

// Set current locale translation $product->name = 'Laptop'; $product->description = 'A powerful laptop'; // Set specific locale $product->translate('en')->name = 'Laptop'; $product->translate('ar')->name = 'لابتوب'; // Set multiple locales at once $product->translate('en')->name = 'Laptop'; $product->translate('en')->description = 'A powerful laptop'; $product->translate('ar')->name = 'لابتوب'; $product->translate('ar')->description = 'لابتوب قوي'; $product->save();

Getting Translations

// Get current locale (based on app()->getLocale()) $name = $product->name; // Returns name in current locale // Get specific locale $nameEn = $product->translate('en')->name; $nameAr = $product->translate('ar')->name; // Check if translation exists if ($product->hasTranslation('en')) { $name = $product->translate('en')->name; } // Get all translations $translations = $product->translations; // Collection of all translations

Querying Translations

// Search in translations Product::whereTranslation('name', 'Laptop')->get(); Product::whereTranslationLike('name', '%laptop%')->get(); // Order by translated field Product::orderByTranslation('name', 'asc')->get(); // Get products with specific locale translation Product::withTranslation('en')->get(); // Get products with all translations Product::with('translations')->get();

Configuration

Configure locales in config/translatable.php:

return [ 'locales' => [ 'en', 'ar', 'fr', // ... other locales ], 'locale_separator' => '-', 'fallback_locale' => 'en', 'fallback_any_locale' => false, ];
💡 Summary: These architectural patterns work together to provide a robust, maintainable, and scalable codebase. The Dashboard Controller/Service pattern handles CRUD operations, Pipelines provide extensibility, Traits add reusable functionality, and Transactions ensure data integrity.

🛠️ Helpers & Utilities

Helper functions are defined in app/Helpers/helpers.php.

Available Helpers

json()

Standardized JSON response helper.

json($data, $message, $status, $headerStatus) // Examples: json($user, 'User retrieved', 'success', 200); json(null, 'Error occurred', 'fail', 400);

log_activity()

Structured logging helper.

log_activity('class_name', $data, 'info|error|warning'); // Example: log_activity('UserService', ['action' => 'create', 'user_id' => 1], 'info');

throttle()

Rate limiting helper.

throttle($key, $maxAttempts, $decaySeconds, $field); // Example: throttle('login:' . $request->ip(), 5, 60);

generate_unique_code()

Generate unique codes for models.

generate_unique_code($length, $model, $column, $type, $letter_type); // Example: $code = generate_unique_code(8, Product::class, 'code', 'numbers');

detectLoginField()

Detect if login input is email or phone.

$field = detectLoginField($login); // Returns 'email' or 'phone'

getUserType()

Get user types array from request.

$types = getUserType($request); // Returns array of user types

resolveCoords()

Resolve coordinates from various sources (request, user, IP).

[$lat, $lng] = resolveCoords();

isApi()

Check if request is an API request.

if (isApi($request)) { // API response }

uniqueTranslationRule()

Generate unique validation rule for translatable fields.

Rule::unique('name')->where(function ($query) { return $query->where('locale', $locale); });

🛡️ Middleware

Middleware is registered in bootstrap/app.php.

Global Middleware

Applied to all requests:

  • DetectUserType - Detects user type from request
  • SetLocale - Sets application locale
  • HandleCors - CORS handling
  • GzipMiddleware - Response compression

Route Middleware

Available as aliases:

Alias Class Purpose
user_type CheckUserType Check if user has required type
active ActiveMiddleware Ensure user is active
permission PermissionMiddleware Check user permissions
set_locale SetLocale Set application locale
cors HandleCors CORS handling

Usage Examples

// Check user type Route::middleware(['auth:api', 'user_type:admin'])->group(function () { // Admin routes }); // Check if user is active Route::middleware(['auth:api', 'active'])->group(function () { // Active user routes }); // Check permissions Route::middleware(['auth:api', 'permission:manage-products'])->group(function () { // Permission-protected routes });

💻 Development Workflow

Initial Setup

# Install PHP dependencies composer install # Install Node dependencies npm install # Copy environment file cp .env.example .env # Generate application key php artisan key:generate # Run migrations php artisan migrate # Seed database (if seeders exist) php artisan db:seed # Install streaming service dependencies cd streaming-service npm install cp .env.example .env cd ..

Development Commands

Start Development Server

# Start all services (Laravel, Queue, Logs, Vite) composer dev # Or individually: php artisan serve # Laravel server php artisan queue:listen # Queue worker php artisan pail # Log viewer npm run dev # Vite dev server

Code Quality

# Format code with Pint ./vendor/bin/pint # Run tests php artisan test

Creating New Features

Recommended Workflow:
  1. Create/update migration
  2. Create model (with translations if needed)
  3. Generate CRUD using php artisan make:crud
  4. Customize service logic
  5. Add routes
  6. Test endpoints

Database Migrations

# Create migration php artisan make:migration create_products_table # Run migrations php artisan migrate # Rollback last migration php artisan migrate:rollback # Refresh migrations php artisan migrate:refresh

Model Translations

For translatable models, use the astrotomic/laravel-translatable package:

use Astrotomic\Translatable\Translatable; use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract; class Product extends Model implements TranslatableContract { use Translatable; public $translatedAttributes = ['name', 'description']; // Access translations $product->translate('en')->name; $product->name; // Current locale }

✨ Best Practices

Code Organization

  • ✅ Keep business logic in Services, not Controllers
  • ✅ Use Form Requests for validation
  • ✅ Use Resources for API response formatting
  • ✅ Follow PSR-12 coding standards
  • ✅ Use Type Hints and Return Types

Laravel 12 Conventions

Important:
  • ❌ Don't create app/Http/Kernel.php - use bootstrap/app.php
  • ❌ Don't create app/Console/Kernel.php - use routes/console.php
  • ✅ Register service providers in bootstrap/providers.php
  • ✅ Define scheduled tasks in bootstrap/app.php
  • ✅ Use Tailwind CSS for new Blade pages, not Bootstrap

Service Layer Pattern

// ✅ Good: Business logic in service class ProductService { public function create(array $data): Product { // Validation, business rules, etc. return Product::create($data); } } // ❌ Bad: Business logic in controller class ProductController { public function store(Request $request) { // Don't put business logic here Product::create($request->all()); } }

Error Handling

Error handling is centralized in bootstrap/app.php:

  • Validation errors return 422 with formatted messages
  • Authentication errors return 401
  • Authorization errors return 403
  • Model not found returns 404
  • All errors are logged using log_activity()

API Response Format

Always use the json() helper for consistent responses:

// Success return json($data, 'Operation successful'); // Error return json(null, 'Error message', 'fail', 400);

Database Transactions

DB::beginTransaction(); try { // Multiple operations DB::commit(); } catch (\Exception $e) { DB::rollBack(); throw $e; }

Testing

  • Write feature tests for API endpoints
  • Test service layer logic
  • Use factories for test data
  • Mock external services

Security

  • ✅ Always validate input using Form Requests
  • ✅ Use middleware for authentication/authorization
  • ✅ Sanitize user input
  • ✅ Use parameterized queries (Eloquent does this automatically)
  • ✅ Implement rate limiting for sensitive endpoints
  • ✅ Use HTTPS in production

📚 Quick Reference

A handy cheat sheet for common commands, patterns, and snippets.

⚡ Artisan Commands

CRUD Generation
php artisan make:crud Product

Generates: Controller, Service, Request, Resource, Routes, Migration, Factory

Service Generation
php artisan make:service ProductService
Enum Generation
php artisan make:enum ProductStatus
Export & Documentation
php artisan export:postman php artisan export:api-docs
Fill Commands
php artisan fill:request ProductRequest php artisan fill:resource ProductResource

🔧 Troubleshooting

Common issues and their solutions.

❌ Issue: "Class not found" errors

Solution:

composer dump-autoload php artisan optimize:clear
❌ Issue: Media uploads not working

Solution:

  • Check storage/app/public permissions
  • Run php artisan storage:link
  • Verify streaming service is running
  • Check CORS configuration
❌ Issue: JWT authentication failing

Solution:

  • Verify JWT_SECRET in .env
  • Check token expiration time
  • Ensure auth:api middleware is applied
  • Verify user has required role/permissions
❌ Issue: Routes not found

Solution:

php artisan route:clear php artisan route:cache php artisan route:list // Check if route exists
❌ Issue: Database migrations failing

Solution:

php artisan migrate:fresh php artisan migrate:refresh php artisan db:seed
❌ Issue: Service provider not loading

Solution:

  • Register in bootstrap/providers.php (Laravel 11+)
  • Run php artisan config:clear
  • Check service provider namespace
❌ Issue: CORS errors in API

Solution:

  • Check config/cors.php configuration
  • Verify allowed origins in .env
  • Ensure CORS middleware is enabled in bootstrap/app.php
❌ Issue: Translatable not working

Solution:

  • Ensure model uses Translatable trait
  • Check $translatedAttributes array
  • Verify locale is set: app()->setLocale('en')
  • Check translation tables exist

💾 Code Snippets Library

Reusable code examples for common tasks. Click to copy!

Dashboard Controller
class ProductController extends DashboardBaseController { public function __construct(ProductService $service) { $requirements = [ 'model' => Product::class, 'with' => ['category', 'media'], 'load' => ['translations', 'media'], 'service' => $service, 'storeRequest' => ProductRequest::class, 'updateRequest' => ProductRequest::class, 'indexResource' => ProductResource::class, 'showResource' => ProductDetailsResource::class, 'permissions' => [ 'index' => 'index-products', 'show' => 'show-products', 'store' => 'store-products', 'update' => 'update-products', 'destroy' => 'destroy-products', ], ]; parent::__construct($requirements); } }
Service Class
class ProductService extends DashboardCRUDService { protected $model = Product::class; protected function beforeSave($data) { // Custom logic before saving if (isset($data['slug'])) { $data['slug'] = Str::slug($data['slug']); } return $data; } protected function afterSave($model, $data) { // Custom logic after saving if (isset($data['media'])) { $model->syncMedia($data['media']); } } }
Model with Traits
use Modules\Media\Traits\HasMedia; use App\Traits\SearchAndFilterAbilities; use Astrotomic\Translatable\Translatable; class Product extends Model { use HasMedia, SearchAndFilterAbilities, Translatable; protected $searchableFields = ['name', 'description']; public $translatedAttributes = ['name', 'description']; protected $fillable = ['slug', 'price', 'stock']; }
Form Request Validation
class ProductRequest extends FormRequest { public function rules(): array { return [ 'name' => 'required|string|max:255', 'price' => 'required|numeric|min:0', 'stock' => 'required|integer|min:0', 'category_id' => 'required|exists:categories,id', 'media' => 'nullable|array', 'media.*' => 'file|mimes:jpeg,png,jpg|max:2048', ]; } }
API Resource
class ProductResource extends JsonResource { public function toArray($request): array { return [ 'id' => $this->id, 'name' => $this->name, 'price' => $this->price, 'stock' => $this->stock, 'category' => new CategoryResource($this->whenLoaded('category')), 'media' => MediaResource::collection($this->whenLoaded('media')), 'created_at' => $this->created_at, ]; } }
API Routes
// Admin routes Route::prefix('admin') ->middleware(['auth:api', 'role:admin']) ->group(function () { Route::apiResource('products', ProductController::class); }); // Client routes (read-only) Route::prefix('client') ->middleware(['auth:api', 'role:client']) ->group(function () { Route::get('products', [ProductController::class, 'index']); Route::get('products/{id}', [ProductController::class, 'show']); });
Query with Search & Filter
// Search and filter $products = Product::query() ->search($request->input('search')) ->filter($request->input('filters')) ->sort($request->input('sort')) ->with(['category', 'media']) ->paginate($request->input('per_page', 15)); // With translations $products = Product::with('translations') ->whereTranslation('name', 'like', '%laptop%') ->get();
Media Upload
// Upload single file $media = uploadMedia($request->file('image'), 'products'); // Upload to collection $media = uploadMedia($request->file('image'), 'products', 'gallery'); // Attach to model $product->attachMedia($media); // Get media $product->getMedia('gallery'); $product->getFirstMedia('gallery');

🌐 API Explorer

📅 Last Updated: 2025-12-25 | Last Documented: ✅ Complete

Interactive explorer for all API endpoints. Filter by method, prefix, or search by name. Click "Test" to send requests!

🔗 Related: See Routes & API for endpoint documentation, Services for implementation details.

🚀 Deployment Guide

Step-by-step guide for deploying your Laravel 12 application.

Prerequisites

  • PHP 8.2 or higher
  • Composer
  • Node.js and NPM (for frontend assets)
  • MySQL/PostgreSQL database
  • Web server (Nginx/Apache)
  • SSL certificate (for HTTPS)

Step 1: Server Setup

# Update system sudo apt update && sudo apt upgrade -y # Install PHP and extensions sudo apt install php8.2 php8.2-fpm php8.2-mysql php8.2-xml php8.2-mbstring php8.2-curl php8.2-zip # Install Composer curl -sS https://getcomposer.org/installer | php sudo mv composer.phar /usr/local/bin/composer # Install Node.js curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - sudo apt install -y nodejs

Step 2: Application Deployment

# Clone repository git clone your-repo-url /var/www/your-app cd /var/www/your-app # Install dependencies composer install --optimize-autoloader --no-dev npm install npm run build # Set permissions sudo chown -R www-data:www-data /var/www/your-app sudo chmod -R 755 /var/www/your-app sudo chmod -R 775 storage bootstrap/cache

Step 3: Environment Configuration

# Copy environment file cp .env.example .env # Generate application key php artisan key:generate # Set environment variables APP_ENV=production APP_DEBUG=false APP_URL=https://yourdomain.com # Database configuration DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_DATABASE=your_database DB_USERNAME=your_username DB_PASSWORD=your_password # JWT configuration JWT_SECRET=your-jwt-secret-key # Run migrations php artisan migrate --force # Cache configuration php artisan config:cache php artisan route:cache php artisan view:cache

Step 4: Nginx Configuration

server { listen 80; server_name yourdomain.com; root /var/www/your-app/public; add_header X-Frame-Options "SAMEORIGIN"; add_header X-Content-Type-Options "nosniff"; index index.php; charset utf-8; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass unix:/var/run/php/php8.2-fpm.sock; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; include fastcgi_params; } location ~ /\.(?!well-known).* { deny all; } }

Step 5: Queue & Scheduler Setup

# Install Supervisor for queue workers sudo apt install supervisor # Create supervisor config sudo nano /etc/supervisor/conf.d/laravel-worker.conf # Add to config: [program:laravel-worker] process_name=%(program_name)s_%(process_num)02d command=php /var/www/your-app/artisan queue:work --sleep=3 --tries=3 autostart=true autorestart=true user=www-data numprocs=2 redirect_stderr=true stdout_logfile=/var/www/your-app/storage/logs/worker.log # Start supervisor sudo supervisorctl reread sudo supervisorctl update sudo supervisorctl start laravel-worker:* # Setup cron for scheduler * * * * * cd /var/www/your-app && php artisan schedule:run >> /dev/null 2>&1

Step 6: Streaming Service Deployment

# Navigate to streaming service cd streaming-service # Install dependencies npm install --production # Setup PM2 for process management npm install -g pm2 # Start service pm2 start src/index.js --name media-streaming # Save PM2 configuration pm2 save pm2 startup

Post-Deployment Checklist

  • ✅ Verify all environment variables are set
  • ✅ Test API endpoints
  • ✅ Verify media uploads work
  • ✅ Check queue workers are running
  • ✅ Verify scheduled tasks execute
  • ✅ Test authentication flow
  • ✅ Enable HTTPS/SSL
  • ✅ Setup monitoring and logging
  • ✅ Configure backups

⚡ Performance Optimization

Best practices and tips for optimizing your Laravel application performance.

Database Optimization

✅ Use Eager Loading
// ❌ Bad: N+1 query problem $products = Product::all(); foreach ($products as $product) { echo $product->category->name; // Query for each product } // ✅ Good: Eager loading $products = Product::with('category')->get(); foreach ($products as $product) { echo $product->category->name; // No additional queries }
✅ Use Database Indexes
// Add indexes for frequently queried columns Schema::table('products', function (Blueprint $table) { $table->index('category_id'); $table->index('slug'); $table->index(['status', 'created_at']); });
✅ Use Query Caching
// Cache expensive queries $products = Cache::remember('products_list', 3600, function () { return Product::with('category')->get(); }); // Cache with tags (Redis) Cache::tags(['products'])->remember('products_list', 3600, function () { return Product::all(); });

Application Optimization

✅ Enable OPcache

Enable OPcache in php.ini:

opcache.enable=1 opcache.memory_consumption=256 opcache.max_accelerated_files=20000 opcache.validate_timestamps=0 # Set to 1 in development
✅ Use Response Caching
// Cache API responses Route::middleware('cache.headers:public;max_age=3600')->group(function () { Route::get('products', [ProductController::class, 'index']); }); // Or in controller return Cache::remember('products_index_' . $page, 3600, function () { return ProductResource::collection(Product::paginate(15)); });
✅ Optimize Autoloader
# Generate optimized autoloader composer install --optimize-autoloader --no-dev # Cache configuration php artisan config:cache php artisan route:cache php artisan view:cache

Frontend Optimization

✅ Build Assets for Production
# Build optimized assets npm run build # Or for production npm run build -- --mode production
✅ Enable Gzip Compression

Already enabled via GzipMiddleware in this base project.

Queue & Background Jobs

✅ Use Queues for Heavy Tasks
// Dispatch heavy operations to queue ProcessMediaJob::dispatch($media); // Use database queue driver for small apps QUEUE_CONNECTION=database // Use Redis for better performance QUEUE_CONNECTION=redis

Monitoring & Profiling

  • Use Laravel Telescope for debugging (development only)
  • Monitor query performance with DB::enableQueryLog()
  • Use Laravel Debugbar for development
  • Setup application monitoring (New Relic, Sentry, etc.)
  • Monitor server resources (CPU, memory, disk)

🔒 Security Best Practices

Comprehensive security checklist for your Laravel application.

Authentication & Authorization

  • ✅ Use strong JWT secrets (minimum 32 characters)
  • ✅ Implement token expiration and refresh tokens
  • ✅ Use role-based access control (RBAC)
  • ✅ Implement permission checks on all protected routes
  • ✅ Validate user status (active/inactive) before authentication
  • ✅ Implement rate limiting on authentication endpoints
  • ✅ Use secure password hashing (bcrypt/argon2)
  • ✅ Implement account lockout after failed attempts

Input Validation

  • ✅ Always validate input using Form Requests
  • ✅ Sanitize user input before processing
  • ✅ Use parameterized queries (Eloquent does this automatically)
  • ✅ Validate file uploads (type, size, MIME type)
  • ✅ Implement CSRF protection for web routes
  • ✅ Validate and sanitize file names
  • ✅ Use whitelist validation instead of blacklist

API Security

  • ✅ Enable CORS with specific allowed origins
  • ✅ Implement API rate limiting
  • ✅ Use HTTPS for all API communications
  • ✅ Validate and sanitize all API inputs
  • ✅ Implement proper error handling (don't expose sensitive info)
  • ✅ Use API versioning
  • ✅ Log security events (failed logins, unauthorized access)

Environment & Configuration

  • ✅ Never commit .env file
  • ✅ Use strong, unique secrets for each environment
  • ✅ Set APP_DEBUG=false in production
  • ✅ Use environment-specific configuration
  • ✅ Rotate secrets regularly
  • ✅ Use secure session configuration
  • ✅ Enable secure cookies

Database Security

  • ✅ Use parameterized queries (Eloquent default)
  • ✅ Limit database user permissions
  • ✅ Use database encryption for sensitive data
  • ✅ Implement database backups
  • ✅ Use connection encryption (SSL/TLS)
  • ✅ Regularly update database software

File & Media Security

  • ✅ Validate file types and sizes
  • ✅ Store files outside web root when possible
  • ✅ Scan uploaded files for malware
  • ✅ Use secure file names (avoid user-provided names)
  • ✅ Implement file access controls
  • ✅ Set proper file permissions (644 for files, 755 for directories)

Server Security

  • ✅ Keep server software updated
  • ✅ Use firewall to restrict access
  • ✅ Implement fail2ban for brute force protection
  • ✅ Use SSH keys instead of passwords
  • ✅ Disable unnecessary services
  • ✅ Regular security audits
  • ✅ Monitor server logs

Code Security

  • ✅ Keep dependencies updated (composer update)
  • ✅ Review and audit third-party packages
  • ✅ Use dependency scanning tools
  • ✅ Implement code reviews
  • ✅ Follow secure coding practices
  • ✅ Regular security testing