Skip to content

Laravel Routing

Routes map HTTP requests to controller actions. Defined in routes/web.php (session/CSRF) and routes/api.php (stateless). Routes support parameters, named references, grouping, and resource CRUD conventions. Controllers organize route handlers by domain. Closely tied to laravel middleware for access control and laravel blade templates for view rendering.

Key Facts

  • routes/web.php - web routes with session, CSRF protection, cookie middleware
  • routes/api.php - API routes, stateless, /api prefix by default
  • Route methods: Route::get(), ::post(), ::put(), ::patch(), ::delete(), ::any(), ::match()
  • Named routes: ->name('posts.index') - used in route('posts.index') helper for URL generation
  • Route parameters: {id} required, {id?} optional
  • Route model binding: type-hint Eloquent model in controller to auto-resolve by ID
  • Resource routes: Route::resource('posts', PostController::class) generates 7 CRUD routes
  • Route groups: share middleware, prefix, namespace across multiple routes
  • php artisan route:list shows all registered routes

Patterns

Basic routes

<?php
use App\Http\Controllers\PostController;
use Illuminate\Support\Facades\Route;

// Closure route (simple pages)
Route::get('/', function () {
    return view('welcome');
});

// Controller route
Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
Route::get('/posts/{post}', [PostController::class, 'show'])->name('posts.show');
Route::post('/posts', [PostController::class, 'store'])->name('posts.store');
Route::put('/posts/{post}', [PostController::class, 'update'])->name('posts.update');
Route::delete('/posts/{post}', [PostController::class, 'destroy'])->name('posts.destroy');

Resource routes (CRUD)

<?php
// Generates all 7 RESTful routes
Route::resource('posts', PostController::class);

// Equivalent to:
// GET    /posts              -> index    posts.index
// GET    /posts/create       -> create   posts.create
// POST   /posts              -> store    posts.store
// GET    /posts/{post}       -> show     posts.show
// GET    /posts/{post}/edit  -> edit     posts.edit
// PUT    /posts/{post}       -> update   posts.update
// DELETE /posts/{post}       -> destroy  posts.destroy

// Partial resource
Route::resource('posts', PostController::class)->only(['index', 'show']);
Route::resource('posts', PostController::class)->except(['destroy']);

// API resource (no create/edit - those are form views)
Route::apiResource('posts', PostController::class);

Route groups

<?php
// Admin routes with prefix, middleware, and controller namespace
Route::prefix('admin')
    ->middleware(['auth', 'admin'])
    ->name('admin.')
    ->group(function () {
        Route::get('/', [Admin\MainController::class, 'index'])->name('main.index');
        Route::resource('posts', Admin\PostController::class);
        Route::resource('categories', Admin\CategoryController::class);
    });
// Generates: admin/posts, admin/categories, etc.
// Names: admin.posts.index, admin.categories.index, etc.

Route model binding

<?php
// Implicit binding - Laravel resolves {post} to Post model by ID
Route::get('/posts/{post}', function (Post $post) {
    return view('posts.show', compact('post'));
});
// Automatically returns 404 if post not found

// Custom column binding
Route::get('/posts/{post:slug}', function (Post $post) {
    return $post; // resolved by slug column
});

Controllers

<?php
namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;

class PostController extends Controller
{
    public function index()
    {
        $posts = Post::latest()->paginate(15);
        return view('posts.index', compact('posts'));
    }

    public function show(Post $post)
    {
        return view('posts.show', compact('post'));
    }

    public function store(Request $request)
    {
        $validated = $request->validate([
            'title' => 'required|max:255',
            'body'  => 'required',
        ]);

        Post::create($validated);
        return redirect()->route('posts.index')
            ->with('success', 'Post created.');
    }

    public function destroy(Post $post)
    {
        $post->delete();
        return redirect()->route('posts.index')
            ->with('success', 'Post deleted.');
    }
}

URL generation

<?php
// In Blade templates
<a href="{{ route('posts.show', $post) }}">View</a>
<a href="{{ route('posts.edit', ['post' => $post->id]) }}">Edit</a>

// In controllers
return redirect()->route('admin.main.index');
return redirect()->back()->with('error', 'Not found');

// Asset URLs
<link href="{{ asset('assets/admin/css/adminlte.css') }}" rel="stylesheet">

Gotchas

Symptom Cause Fix
Route [name] not defined Typo in route name or route not registered Run php artisan route:list to verify
Wrong route matches (e.g., posts/create hits posts/{post}) Route order matters - specific routes before parameterized Place posts/create before posts/{post}
405 Method Not Allowed Form uses GET but route expects POST, or missing @method('PUT') Add @method('PUT') or @method('DELETE') to form
Route model binding returns 404 Model not found by ID (or soft deleted) Check database or use withTrashed() in route binding
CSRF token mismatch on POST Missing @csrf directive in form Add @csrf inside <form> tag
Route cache stale after changes Route cache was generated before new routes Run php artisan route:clear

See Also