laravel 5 Q&As

Laravel FAQ & Answers

5 expert Laravel answers researched from official documentation. Every answer cites authoritative sources you can verify.

unknown

5 questions
A

Laravel does not enforce strict naming conventions for session keys in custom middleware, but the community follows these patterns: use dot notation for namespacing (e.g., middleware.auth.user_id), prefix with middleware name or context, and use snake_case for key names.

Recommended Patterns:

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class CustomAuthMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        // Pattern 1: Namespace with dot notation (recommended)
        $request->session()->put('middleware.auth.user_id', 123);
        $request->session()->put('middleware.auth.authenticated_at', now());
        
        // Pattern 2: Prefix with middleware name
        $request->session()->put('custom_auth_user_id', 123);
        
        // Pattern 3: Descriptive snake_case
        $request->session()->put('authenticated_user_id', 123);
        $request->session()->put('last_activity_time', now());
        
        // Avoid: Generic keys that might conflict
        // $request->session()->put('user', $user);  // Too generic
        // $request->session()->put('data', $data);  // Too vague
        
        return $next($request);
    }
}

Laravel's Own Conventions:

Laravel's built-in middleware follows these patterns:

// Auth middleware
session()->put('login.id', $userId);  // User ID
session()->put('login.remember', $remember);  // Remember me flag

// CSRF token
session()->token();  // '_token' key

// Previous URL
session()->previousUrl();  // 'url.intended' key

// Flash data
session()->flash('status', 'Success!');  // Temporary data

Complete Example:

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class TrackUserActivity
{
    public function handle(Request $request, Closure $next)
    {
        if (Auth::check()) {
            // Use namespaced keys for clarity
            $request->session()->put('tracking.user_id', Auth::id());
            $request->session()->put('tracking.last_activity', now());
            $request->session()->put('tracking.current_route', $request->path());
            
            // Store array of recent pages
            $recentPages = $request->session()->get('tracking.recent_pages', []);
            array_unshift($recentPages, $request->path());
            $recentPages = array_slice($recentPages, 0, 5);  // Keep last 5
            
            $request->session()->put('tracking.recent_pages', $recentPages);
        }
        
        return $next($request);
    }
}

// Accessing in controllers
class DashboardController extends Controller
{
    public function index(Request $request)
    {
        $lastActivity = $request->session()->get('tracking.last_activity');
        $recentPages = $request->session()->get('tracking.recent_pages', []);
        
        return view('dashboard', compact('lastActivity', 'recentPages'));
    }
}

Best Practices:

  1. ✅ Use dot notation for namespacing: middleware.feature.key
  2. ✅ Use snake_case for key names
  3. ✅ Prefix with middleware/feature name to avoid conflicts
  4. ✅ Be descriptive: auth_user_id better than uid
  5. ✅ Document session keys in middleware comments
  6. ❌ Avoid generic keys like user, data, info
  7. ❌ Don't store large objects directly (use IDs and lazy load)

Session Helper Methods:

// Store data
$request->session()->put('key', 'value');
session(['key' => 'value']);  // Alternative

// Retrieve data
$value = $request->session()->get('key');
$value = session('key');  // Alternative
$value = $request->session()->get('key', 'default');

// Check existence
if ($request->session()->has('key')) {
    // Key exists
}

if ($request->session()->exists('key')) {
    // Key exists (even if null)
}

// Remove data
$request->session()->forget('key');
$request->session()->forget(['key1', 'key2']);

// Flash data (available for next request only)
$request->session()->flash('status', 'Success!');

Version Note: Naming conventions consistent across Laravel 8-11 (2020-2024)

99% confidence
A

Laravel supports multi-database connections through the $connection property on models or the DB::connection() method. Connections are defined in config/database.php and can be switched per model or per query.

Configuration (config/database.php):

return [
    'default' => env('DB_CONNECTION', 'mysql'),
    
    'connections' => [
        'mysql' => [
            'driver' => 'mysql',
            'host' => env('DB_HOST', '127.0.0.1'),
            'database' => env('DB_DATABASE', 'main_db'),
            'username' => env('DB_USERNAME'),
            'password' => env('DB_PASSWORD'),
        ],
        
        'mysql_secondary' => [
            'driver' => 'mysql',
            'host' => env('DB_SECONDARY_HOST', '127.0.0.1'),
            'database' => env('DB_SECONDARY_DATABASE', 'analytics_db'),
            'username' => env('DB_SECONDARY_USERNAME'),
            'password' => env('DB_SECONDARY_PASSWORD'),
        ],
        
        'pgsql' => [
            'driver' => 'pgsql',
            'host' => env('PGSQL_HOST', '127.0.0.1'),
            'database' => env('PGSQL_DATABASE', 'warehouse'),
            'username' => env('PGSQL_USERNAME'),
            'password' => env('PGSQL_PASSWORD'),
            'schema' => 'public',
        ],
    ],
];

Pattern 1: Model-Level Connection (Recommended for Permanent Assignment):

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

// Default connection (mysql)
class User extends Model
{
    // Uses default connection from config
}

// Specific connection
class AnalyticsEvent extends Model
{
    protected $connection = 'mysql_secondary';
    protected $table = 'events';
    
    // All queries use mysql_secondary
}

class WarehouseData extends Model
{
    protected $connection = 'pgsql';
    protected $table = 'data';
}

// Usage
$user = User::find(1);  // Uses default (mysql)
$event = AnalyticsEvent::create([...]);  // Uses mysql_secondary
$data = WarehouseData::all();  // Uses pgsql

Pattern 2: Query Builder with Dynamic Connection:

use Illuminate\Support\Facades\DB;

// Query specific connection
$users = DB::connection('mysql_secondary')
    ->table('users')
    ->where('active', true)
    ->get();

// Raw queries
DB::connection('pgsql')->statement('CREATE INDEX ...');

// Transactions on specific connection
DB::connection('mysql_secondary')->transaction(function () {
    DB::connection('mysql_secondary')->table('users')->insert([...]);
    DB::connection('mysql_secondary')->table('logs')->insert([...]);
});

Pattern 3: Dynamic Connection Switching on Models:

class User extends Model
{
    // Default uses mysql
}

// Switch connection at runtime
$user = User::on('mysql_secondary')->find(1);

// Create query on specific connection
$users = User::on('pgsql')
    ->where('created_at', '>', now()->subDays(7))
    ->get();

// Chain methods
$user = User::on('mysql_secondary')
    ->with('posts')
    ->findOrFail(1);

Pattern 4: Multi-Database Relationships:

class User extends Model
{
    protected $connection = 'mysql';
    
    // Relationship to model on different connection
    public function analyticsEvents()
    {
        return $this->hasMany(AnalyticsEvent::class, 'user_id');
    }
}

class AnalyticsEvent extends Model
{
    protected $connection = 'mysql_secondary';
    
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

// Usage (Laravel handles cross-database relationships)
$user = User::with('analyticsEvents')->find(1);

Complete Multi-Database Example:

namespace App\Services;

use App\Models\User;
use App\Models\AnalyticsEvent;
use Illuminate\Support\Facades\DB;

class UserAnalyticsService
{
    public function syncUserActivity(int $userId, array $activityData): void
    {
        // Use transactions on both databases
        DB::connection('mysql')->transaction(function () use ($userId, $activityData) {
            // Update user in main database
            User::where('id', $userId)
                ->update(['last_activity' => now()]);
        });
        
        DB::connection('mysql_secondary')->transaction(function () use ($userId, $activityData) {
            // Store analytics in secondary database
            AnalyticsEvent::create([
                'user_id' => $userId,
                'event_type' => $activityData['type'],
                'event_data' => $activityData['data'],
            ]);
        });
    }
    
    public function getUserReport(int $userId): array
    {
        // Query from main database
        $user = User::find($userId);
        
        // Query from analytics database
        $events = AnalyticsEvent::on('mysql_secondary')
            ->where('user_id', $userId)
            ->whereBetween('created_at', [now()->subDays(30), now()])
            ->get();
        
        return [
            'user' => $user,
            'recent_events' => $events,
        ];
    }
}

Testing Multiple Connections:

// Test connectivity
DB::connection('mysql')->getPdo();
DB::connection('mysql_secondary')->getPdo();
DB::connection('pgsql')->getPdo();

// Get connection name
$connection = User::getConnectionName();  // 'mysql'
$connection = AnalyticsEvent::getConnectionName();  // 'mysql_secondary'

// Check if connection exists
if (config('database.connections.mysql_secondary')) {
    // Connection configured
}

Environment Variables (.env):

# Primary database
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=main_db
DB_USERNAME=root
DB_PASSWORD=secret

# Secondary database
DB_SECONDARY_HOST=192.168.1.100
DB_SECONDARY_DATABASE=analytics_db
DB_SECONDARY_USERNAME=analytics_user
DB_SECONDARY_PASSWORD=analytics_pass

# PostgreSQL database
PGSQL_HOST=192.168.1.200
PGSQL_DATABASE=warehouse
PGSQL_USERNAME=warehouse_user
PGSQL_PASSWORD=warehouse_pass

Best Practices:

  1. ✅ Define all connections in config/database.php
  2. ✅ Use model $connection property for permanent assignments
  3. ✅ Use ->on() for dynamic connection switching
  4. ✅ Wrap cross-database operations in separate transactions
  5. ✅ Test all connections during deployment
  6. ⚠️ Be cautious with relationships across databases (performance)
  7. ❌ Don't hardcode connection names in queries (use config)

Version Note: Multi-database support available since Laravel 5.0+, improved in Laravel 8-11 (2020-2024)

99% confidence
A

Yes, Laravel's middleware execution order directly affects context availability. Middleware runs in a stack ("onion" structure): first-registered middleware wraps all others, executing before them on the request and after them on the response.

Execution Flow:

Request → Middleware 1 (before) → Middleware 2 (before) → Middleware 3 (before) → Controller
Response ← Middleware 1 (after) ← Middleware 2 (after) ← Middleware 3 (after) ← Controller

Registration Order Matters:

// bootstrap/app.php (Laravel 11)
use App\Http\Middleware\{SetLocale, TrackActivity, LogRequests};

return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware) {
        // Global middleware (order matters!)
        $middleware->append([
            LogRequests::class,      // 1st: Runs before all, logs after all
            SetLocale::class,        // 2nd: Sets locale for TrackActivity
            TrackActivity::class,    // 3rd: Can use locale set by SetLocale
        ]);
        
        // Web middleware group
        $middleware->web(append: [
            CustomMiddleware::class,
        ]);
    })
    ->create();

Example: Context Passing Between Middleware:

// Middleware 1: Sets user context
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class SetUserContext
{
    public function handle(Request $request, Closure $next)
    {
        if (Auth::check()) {
            // Store user in request attributes (available to subsequent middleware)
            $request->attributes->set('current_user', Auth::user());
            $request->attributes->set('user_role', Auth::user()->role);
        }
        
        return $next($request);
    }
}

// Middleware 2: Uses context from Middleware 1
class LogUserActivity
{
    public function handle(Request $request, Closure $next)
    {
        // ✅ Available if SetUserContext ran first
        $user = $request->attributes->get('current_user');
        $role = $request->attributes->get('user_role');
        
        if ($user) {
            logger()->info("User {$user->id} ({$role}) accessed {$request->path()}");
        }
        
        return $next($request);
    }
}

// Registration order is critical
$middleware->append([
    SetUserContext::class,     // Must run first
    LogUserActivity::class,    // Depends on SetUserContext
]);

Session Middleware Dependency:

Laravel 11 changed how session middleware is registered - it's part of the web middleware group, not global:

// Laravel 11: Session is in web middleware group
$middleware->web(append: [
    CustomSessionMiddleware::class,  // ✅ Session available here
]);

// ❌ WRONG: Global middleware runs before web group
$middleware->append([
    CustomSessionMiddleware::class,  // ❌ Session NOT available here!
]);

Correct Pattern for Session-Dependent Middleware:

// Laravel 11
$middleware->web(append: [
    SetLocale::class,  // Uses session data
]);

// Or use appendToGroup for clarity
$middleware->appendToGroup('web', [
    SetLocale::class,
]);

namespace App\Http\Middleware;

class SetLocale
{
    public function handle(Request $request, Closure $next)
    {
        // ✅ Session available (web middleware group includes StartSession)
        $locale = $request->session()->get('locale', 'en');
        app()->setLocale($locale);
        
        return $next($request);
    }
}

Middleware Groups (Laravel 11):

// Predefined groups in Laravel 11
'web' => [
    \Illuminate\Cookie\Middleware\EncryptCookies::class,
    \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
    \Illuminate\Session\Middleware\StartSession::class,  // Provides session
    \Illuminate\View\Middleware\ShareErrorsFromSession::class,
    \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],

'api' => [
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],

Complete Example:

// bootstrap/app.php
return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware) {
        // 1. Global middleware (runs on ALL requests)
        $middleware->append([
            \App\Http\Middleware\TrustProxies::class,
            \App\Http\Middleware\LogRequests::class,
        ]);
        
        // 2. Web group (for web routes, includes session)
        $middleware->web(append: [
            \App\Http\Middleware\SetLocale::class,
            \App\Http\Middleware\TrackActivity::class,
        ]);
        
        // 3. API group (for API routes)
        $middleware->api(prepend: [
            \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
        ]);
        
        // 4. Route-specific middleware (defined in routes)
        $middleware->alias([
            'auth' => \App\Http\Middleware\Authenticate::class,
            'admin' => \App\Http\Middleware\AdminMiddleware::class,
        ]);
    })
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
    )
    ->create();

Best Practices:

  1. ✅ Order dependencies first (e.g., session before session-dependent middleware)
  2. ✅ Use web group for session-dependent middleware in Laravel 11
  3. ✅ Use $request->attributes to pass context between middleware
  4. ✅ Document middleware dependencies in comments
  5. ✅ Test middleware order changes thoroughly
  6. ❌ Don't assume session is available in global middleware (Laravel 11)
  7. ❌ Don't rely on implicit ordering - be explicit

Version Note: Middleware execution order consistent since Laravel 5.0+, major changes to registration in Laravel 11 (2024)

99% confidence
A

Laravel's DB::connection() handles PostgreSQL schema-prefixed tables through the schema configuration option in config/database.php. For other databases like MySQL, schemas are equivalent to databases and require separate connections.

PostgreSQL Schema Support:

// config/database.php
'connections' => [
    'pgsql' => [
        'driver' => 'pgsql',
        'host' => env('DB_HOST', '127.0.0.1'),
        'database' => env('DB_DATABASE', 'mydb'),
        'username' => env('DB_USERNAME'),
        'password' => env('DB_PASSWORD'),
        'schema' => 'public',  // Default schema
        'search_path' => 'public,other_schema',  // Schema search path
    ],
    
    'pgsql_analytics' => [
        'driver' => 'pgsql',
        'host' => env('DB_HOST', '127.0.0.1'),
        'database' => env('DB_DATABASE', 'mydb'),  // Same database
        'username' => env('DB_USERNAME'),
        'password' => env('DB_PASSWORD'),
        'schema' => 'analytics',  // Different schema
    ],
];

Querying Schema-Prefixed Tables:

use Illuminate\Support\Facades\DB;

// Method 1: Use configured schema (recommended)
$users = DB::connection('pgsql')
    ->table('users')  // Queries public.users
    ->get();

$events = DB::connection('pgsql_analytics')
    ->table('events')  // Queries analytics.events
    ->get();

// Method 2: Explicit schema prefix in table name
$data = DB::connection('pgsql')
    ->table('analytics.events')  // Explicit schema.table
    ->where('created_at', '>', now()->subDays(7))
    ->get();

// Method 3: Raw queries with schema
$results = DB::connection('pgsql')->select(
    'SELECT * FROM analytics.events WHERE user_id = ?',
    [123]
);

Eloquent Models with PostgreSQL Schemas:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

// Model using default schema (public)
class User extends Model
{
    protected $connection = 'pgsql';
    protected $table = 'users';  // public.users
}

// Model using specific schema
class AnalyticsEvent extends Model
{
    protected $connection = 'pgsql_analytics';
    protected $table = 'events';  // analytics.events (via connection config)
}

// Alternative: Schema prefix in table name
class WarehouseData extends Model
{
    protected $connection = 'pgsql';
    protected $table = 'warehouse.data';  // Explicit schema.table
}

// Usage
$user = User::find(1);  // SELECT * FROM public.users WHERE id = 1
$event = AnalyticsEvent::create([...]);  // INSERT INTO analytics.events
$data = WarehouseData::all();  // SELECT * FROM warehouse.data

MySQL "Schemas" (Actually Databases):

MySQL treats schemas as separate databases, requiring separate connections:

// config/database.php
'connections' => [
    'mysql' => [
        'driver' => 'mysql',
        'host' => '127.0.0.1',
        'database' => 'main_db',  // Database 1
        'username' => 'root',
        'password' => 'secret',
    ],
    
    'mysql_analytics' => [
        'driver' => 'mysql',
        'host' => '127.0.0.1',
        'database' => 'analytics_db',  // Database 2 (different "schema")
        'username' => 'root',
        'password' => 'secret',
    ],
];

// Query different MySQL databases
$users = DB::connection('mysql')->table('users')->get();
$events = DB::connection('mysql_analytics')->table('events')->get();

PostgreSQL Search Path:

// Set search path dynamically
DB::connection('pgsql')->statement('SET search_path TO analytics, public');

// Now queries check analytics schema first, then public
$events = DB::connection('pgsql')
    ->table('events')  // Finds analytics.events (if exists) or public.events
    ->get();

Complete Multi-Schema Example:

namespace App\Services;

use Illuminate\Support\Facades\DB;
use App\Models\User;
use App\Models\AnalyticsEvent;

class SchemaService
{
    public function syncUserActivity(int $userId): array
    {
        // Query from public schema
        $user = DB::connection('pgsql')
            ->table('users')
            ->find($userId);
        
        // Query from analytics schema (configured connection)
        $events = DB::connection('pgsql_analytics')
            ->table('events')
            ->where('user_id', $userId)
            ->count();
        
        // Query from warehouse schema (explicit prefix)
        $aggregates = DB::connection('pgsql')
            ->table('warehouse.user_stats')
            ->where('user_id', $userId)
            ->first();
        
        return [
            'user' => $user,
            'event_count' => $events,
            'aggregates' => $aggregates,
        ];
    }
    
    public function createCrossSchemaReport(): void
    {
        DB::connection('pgsql')->transaction(function () {
            // Insert into public schema
            DB::table('reports')->insert([
                'name' => 'User Activity Report',
                'created_at' => now(),
            ]);
            
            // Insert into analytics schema
            DB::connection('pgsql')
                ->statement('INSERT INTO analytics.report_runs (report_id, run_at) VALUES (?, ?)', [
                    1,
                    now(),
                ]);
        });
    }
}

Testing Schema Access:

// Check current schema
$schema = DB::connection('pgsql')
    ->select('SELECT current_schema()')[0]->current_schema;

// List available schemas
$schemas = DB::connection('pgsql')
    ->select("SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT IN ('pg_catalog', 'information_schema')");

// Check if table exists in schema
$exists = DB::connection('pgsql')
    ->select(
        "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = ? AND table_name = ?)",
        ['analytics', 'events']
    )[0]->exists;

Best Practices:

  1. ✅ Use schema config option for PostgreSQL connections
  2. ✅ Create separate connections for frequently-used schemas
  3. ✅ Use explicit schema.table notation when mixing schemas
  4. ✅ Set search_path for PostgreSQL when querying multiple schemas
  5. ✅ For MySQL, use separate connections for different databases
  6. ❌ Don't hardcode schema names in queries (use config)
  7. ❌ Don't mix schemas without transactions (data consistency)

Version Note: PostgreSQL schema support available since Laravel 5.0+, improved configuration in Laravel 8-11 (2020-2024)

99% confidence
A

Laravel's Route::apiResource() automatically generates 5 RESTful routes (index, store, show, update, destroy) excluding web-specific routes (create, edit) that return HTML forms. It's equivalent to manually defining all routes but more concise.

apiResource() Auto-Generated Routes:

// routes/api.php
use App\Http\Controllers\UserController;

Route::apiResource('users', UserController::class);

// Equivalent to:
Route::get('/users', [UserController::class, 'index']);
Route::post('/users', [UserController::class, 'store']);
Route::get('/users/{user}', [UserController::class, 'show']);
Route::put('/users/{user}', [UserController::class, 'update']);
Route::patch('/users/{user}', [UserController::class, 'update']);
Route::delete('/users/{user}', [UserController::class, 'destroy']);

Route Table Comparison:

Method URI Action Route Name apiResource resource
GET /users index users.index
POST /users store users.store
GET /users/create create users.create
GET /users/{id} show users.show
GET /users/{id}/edit edit users.edit
PUT/PATCH /users/{id} update users.update
DELETE /users/{id} destroy users.destroy

Controller Implementation:

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;

class UserController extends Controller
{
    // GET /api/users
    public function index(): JsonResponse
    {
        $users = User::paginate(15);
        return response()->json($users);
    }
    
    // POST /api/users
    public function store(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|email|unique:users',
        ]);
        
        $user = User::create($validated);
        return response()->json($user, 201);
    }
    
    // GET /api/users/{user}
    public function show(User $user): JsonResponse
    {
        return response()->json($user);
    }
    
    // PUT/PATCH /api/users/{user}
    public function update(Request $request, User $user): JsonResponse
    {
        $validated = $request->validate([
            'name' => 'sometimes|string|max:255',
            'email' => 'sometimes|email|unique:users,email,'.$user->id,
        ]);
        
        $user->update($validated);
        return response()->json($user);
    }
    
    // DELETE /api/users/{user}
    public function destroy(User $user): JsonResponse
    {
        $user->delete();
        return response()->json(null, 204);
    }
}

Customization Options:

// 1. Limit routes
Route::apiResource('users', UserController::class)
    ->only(['index', 'show']);

Route::apiResource('users', UserController::class)
    ->except(['destroy']);

// 2. Custom route names
Route::apiResource('users', UserController::class)
    ->names([
        'index' => 'users.all',
        'store' => 'users.create',
    ]);

// 3. Custom parameter names
Route::apiResource('users', UserController::class)
    ->parameter('users', 'user_id');
// Routes become: /users/{user_id}

// 4. Shallow nesting
Route::apiResource('posts.comments', CommentController::class)
    ->shallow();
// GET /posts/{post}/comments (index)
// GET /comments/{comment} (show) - no post ID!

// 5. Multiple resources
Route::apiResources([
    'users' => UserController::class,
    'posts' => PostController::class,
]);

Nested Resources:

// Nested API resources
Route::apiResource('posts.comments', CommentController::class);

// Generated routes:
// GET    /posts/{post}/comments
// POST   /posts/{post}/comments
// GET    /posts/{post}/comments/{comment}
// PUT    /posts/{post}/comments/{comment}
// DELETE /posts/{post}/comments/{comment}

class CommentController extends Controller
{
    public function index(Post $post)
    {
        return response()->json($post->comments);
    }
    
    public function store(Request $request, Post $post)
    {
        $comment = $post->comments()->create($request->validated());
        return response()->json($comment, 201);
    }
}

Middleware and Protection:

// Apply middleware to resource
Route::apiResource('users', UserController::class)
    ->middleware('auth:sanctum');

// Different middleware per action
Route::apiResource('users', UserController::class)
    ->middleware(['auth:sanctum' => ['except' => ['index', 'show']]]);

// In controller
class UserController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth:sanctum')->except(['index', 'show']);
        $this->middleware('admin')->only(['destroy']);
    }
}

Viewing Registered Routes:

# List all routes
php artisan route:list

# Filter by resource
php artisan route:list --name=users

# Example output:
GET|HEAD  api/users .............. users.index › UserController@index
POST      api/users .............. users.store › UserController@store
GET|HEAD  api/users/{user} ....... users.show › UserController@show
PUT|PATCH api/users/{user} ....... users.update › UserController@update
DELETE    api/users/{user} ....... users.destroy › UserController@destroy

Best Practices:

  1. ✅ Use apiResource() for APIs (excludes create/edit)
  2. ✅ Use resource() for web routes (includes create/edit forms)
  3. ✅ Limit routes with only() or except() when not all actions needed
  4. ✅ Use route model binding for automatic model injection
  5. ✅ Apply authentication middleware to protect sensitive actions
  6. ✅ Return proper HTTP status codes (201, 204, etc.)
  7. ❌ Don't use resource() for APIs (includes unnecessary routes)

Version Note: apiResource() available since Laravel 5.5+, improved in Laravel 8-11 (2020-2024)

99% confidence