Routing is one of the first Laravel concepts you need to understand well. Before models, queues, policies, jobs, or service classes matter, every Laravel application needs a clear answer to a simple question: when someone visits this URL, what code should run?
This guide explains Laravel routing from the ground up using real examples: blog pages, user profiles, products, orders, admin screens, and API endpoints. The goal is not to memorize every routing method. The goal is to understand how routes shape an application and how to keep them readable as your project grows.
If you are new to the framework, start with the Laravel Complete Guide. If you have not created a local Laravel app yet, follow How to Install Laravel first.
What Is Routing in Laravel?
A route maps an incoming HTTP request to application behavior. In plain terms, it tells Laravel what should happen when the browser or API client requests a specific URL using a specific HTTP method.
For example, a blog application might need these routes:
GET /poststo show all postsGET /posts/my-first-postto show one postPOST /poststo store a new postGET /profile/maazto show a user profileGET /api/productsto return product data as JSON
Laravel gives you an expressive routing API for defining all of that without manually parsing URLs or writing switch statements.
How Laravel Routing Works
In a standard Laravel 12 application, web routes live in routes/web.php. These routes are meant for browser-facing pages and use the web middleware group, which includes features such as sessions and CSRF protection.
API routes are usually placed in routes/api.php. In Laravel 12, a fresh installation may not include that file until you enable API routing with Artisan:
php artisan install:api
Routes in routes/api.php are stateless and receive the /api URI prefix automatically unless you customize routing in bootstrap/app.php.
When a request reaches Laravel, the router compares the request method and path against registered routes. The first matching route wins, so route order can matter when broad routes might overlap more specific routes.
Defining Basic Routes
The simplest route returns a response directly from a closure. This is fine for small examples, quick static pages, and prototypes.
<?php
use IlluminateSupportFacadesRoute;
Route::get('/', function () {
return view('welcome');
});
Route::get('/about', function () {
return view('pages.about');
});
The first argument is the URI. The second argument is the action Laravel should run. In these examples, the action returns a Blade view.
GET Routes
Use GET routes for reading or displaying information. A product page, blog index, order detail page, dashboard, and search results page are usually GET routes.
Route::get('/products', function () {
return view('products.index');
});
Route::get('/orders', function () {
return view('orders.index');
});
POST Routes
Use POST routes when the request creates something or submits data. In browser forms, include a CSRF token when posting to routes in web.php.
Route::post('/posts', function () {
// Validate input, create the post, then redirect.
});
Route::post('/orders', function () {
// Validate the cart, create the order, then redirect.
});
<form method="POST" action="/posts">
@csrf
<input type="text" name="title">
<textarea name="body"></textarea>
<button type="submit">Publish</button>
</form>
Route Parameters
Route parameters let part of the URL become a variable. This is how Laravel handles URLs such as /posts/42, /users/maaz, or /products/keyboard.
Route::get('/posts/{post}', function (string $post) {
return "Showing post: {$post}";
});
Route::get('/users/{username}', function (string $username) {
return "Profile for {$username}";
});
In /users/maaz, Laravel passes maaz into the $username argument.
You can also constrain parameters with regular expressions when a route should only match certain values.
Route::get('/orders/{order}', function (string $order) {
return "Order number: {$order}";
})->whereNumber('order');
Route::get('/posts/{slug}', function (string $slug) {
return "Post slug: {$slug}";
})->where('slug', '[a-z0-9-]+');
Optional Parameters
An optional parameter uses a question mark after the parameter name and requires a default value in the route action.
Route::get('/reports/{period?}', function (string $period = 'monthly') {
return "Showing {$period} reports";
});
This route can match both /reports and /reports/weekly. Use optional parameters carefully. They are useful for simple defaults, but they can make routes harder to reason about if you use them everywhere.
Named Routes
Named routes let you generate URLs by route name instead of hardcoding paths throughout your application. This is one of the easiest habits that saves pain later.
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');
Now Blade views and controllers can generate URLs safely:
<a href="{{ route('posts.index') }}">All posts</a>
<a href="{{ route('posts.show', $post) }}">
{{ $post->title }}
</a>
return redirect()->route('posts.show', $post);
If you later change /posts/{post} to /blog/{post}, every call to route('posts.show', $post) keeps working. That is the practical value of names.
Route Groups
Route groups let you share configuration across multiple routes. You can group middleware, URL prefixes, name prefixes, controller namespaces, and other route attributes.
Here is a simple admin group:
Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index'])
->name('dashboard');
Route::get('/settings', [SettingsController::class, 'edit'])
->name('settings.edit');
});
Both routes require authenticated and verified users. You do not have to repeat the middleware call on every route.
Route Prefixes
A route prefix adds a shared URI segment to every route in a group. This is useful for admin areas, account areas, API versions, and feature sections.
Route::prefix('admin')->name('admin.')->middleware('auth')->group(function () {
Route::get('/products', [AdminProductController::class, 'index'])
->name('products.index');
Route::get('/orders', [AdminOrderController::class, 'index'])
->name('orders.index');
});
This creates routes such as:
/admin/productsnamedadmin.products.index/admin/ordersnamedadmin.orders.index
The combination of prefix() and name() keeps related routes organized without making each route definition noisy.
Route Middleware
Middleware runs before or after a request reaches your route action. You use it for cross-cutting concerns such as authentication, email verification, rate limiting, permissions, localization, and request filtering.
Route::get('/account', [AccountController::class, 'show'])
->middleware('auth')
->name('account.show');
Route::post('/orders', [OrderController::class, 'store'])
->middleware(['auth', 'verified'])
->name('orders.store');
For API routes, middleware is also commonly used for authentication and throttling:
Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
Route::get('/orders', [ApiOrderController::class, 'index']);
Route::post('/orders', [ApiOrderController::class, 'store']);
});
Future internal-link opportunity: Laravel Middleware Guide.
Controller Routes
Closure routes are useful while learning, but real applications usually move behavior into controllers. A controller keeps route files focused on URL structure instead of business logic.
<?php
namespace AppHttpControllers;
use AppModelsPost;
use IlluminateViewView;
class PostController extends Controller
{
public function index(): View
{
return view('posts.index', [
'posts' => Post::latest()->paginate(10),
]);
}
public function show(Post $post): View
{
return view('posts.show', [
'post' => $post,
]);
}
}
use AppHttpControllersPostController;
Route::get('/posts', [PostController::class, 'index'])
->name('posts.index');
Route::get('/posts/{post}', [PostController::class, 'show'])
->name('posts.show');
A good rule for beginners: if a route does more than return a simple view or redirect, consider moving it to a controller.
Future internal-link opportunity: Laravel Controllers vs Closures.
Resource Routes
Resource routes are a compact way to define the standard CRUD routes for a controller. They are especially useful for common application objects such as posts, products, orders, projects, and teams.
use AppHttpControllersProductController;
Route::resource('products', ProductController::class);
That single line creates conventional routes for actions such as listing products, showing a create form, storing a product, showing one product, editing, updating, and deleting.
A matching controller usually looks like this:
<?php
namespace AppHttpControllers;
use AppModelsProduct;
use IlluminateHttpRedirectResponse;
use IlluminateHttpRequest;
use IlluminateViewView;
class ProductController extends Controller
{
public function index(): View
{
return view('products.index', [
'products' => Product::query()->latest()->paginate(),
]);
}
public function show(Product $product): View
{
return view('products.show', compact('product'));
}
public function store(Request $request): RedirectResponse
{
$validated = $request->validate([
'name' => ['required', 'string', 'max:255'],
'price' => ['required', 'numeric', 'min:0'],
]);
$product = Product::create($validated);
return redirect()->route('products.show', $product);
}
}
You do not have to use every resource action. Laravel lets you limit resource routes when a controller only needs part of the CRUD set.
Route::resource('products', ProductController::class)
->only(['index', 'show', 'store']);
Route::resource('orders', OrderController::class)
->except(['destroy']);
API Routes
API routes return data, usually JSON, instead of full HTML pages. Use API routes for mobile apps, JavaScript frontends, integrations, webhooks, and public or private developer APIs.
After enabling API routing with php artisan install:api, define API endpoints in routes/api.php.
use AppHttpControllersApiProductController;
use IlluminateSupportFacadesRoute;
Route::get('/products', [ProductController::class, 'index']);
Route::get('/products/{product}', [ProductController::class, 'show']);
Route::middleware('auth:sanctum')->group(function () {
Route::post('/orders', [OrderController::class, 'store']);
Route::get('/orders/{order}', [OrderController::class, 'show']);
});
Those routes are reached as /api/products, /api/products/{product}, and so on because Laravel applies the API prefix.
An API controller might return arrays, JSON responses, or API resources:
<?php
namespace AppHttpControllersApi;
use AppHttpControllersController;
use AppModelsProduct;
use IlluminateHttpJsonResponse;
class ProductController extends Controller
{
public function index(): JsonResponse
{
return response()->json([
'data' => Product::query()->latest()->take(20)->get(),
]);
}
public function show(Product $product): JsonResponse
{
return response()->json([
'data' => $product,
]);
}
}
Future internal-link opportunity: Laravel APIs Guide.
Route Model Binding
Route model binding lets Laravel automatically resolve a route parameter into an Eloquent model. Instead of manually finding a product by ID inside the controller, type-hint the model in the controller method.
use AppHttpControllersProductController;
Route::get('/products/{product}', [ProductController::class, 'show'])
->name('products.show');
use AppModelsProduct;
use IlluminateViewView;
public function show(Product $product): View
{
return view('products.show', [
'product' => $product,
]);
}
If someone visits /products/15, Laravel looks for a Product with primary key 15. If it cannot find one, Laravel returns a 404 response automatically.
Binding by Slug
For public content, slugs often produce cleaner URLs than IDs. Laravel supports this directly in the route definition.
Route::get('/blog/{post:slug}', [PostController::class, 'show'])
->name('posts.show');
public function show(Post $post): View
{
return view('posts.show', compact('post'));
}
Now /blog/laravel-routing-explained resolves the post by its slug column instead of its primary key.
Route Caching
Route caching compiles your route definitions into a cached file so Laravel can load them faster in production. It is a deployment optimization, not something you usually need while actively developing routes.
php artisan route:cache
php artisan route:clear
Use route:cache during production deployment after your route files are stable. Use route:clear if you change routes and the application appears to be using old definitions.
When debugging routes, start with Laravel’s route list command:
php artisan route:list
php artisan route:list --path=api
php artisan route:list -v
Common Routing Mistakes
Putting Business Logic in Route Files
Route files should describe the shape of the application. If a closure is validating input, querying several models, sending notifications, and building a complicated response, move that behavior into a controller or another application class.
Forgetting CSRF Protection on Web Forms
Forms that submit to POST, PUT, PATCH, or DELETE routes in web.php should include @csrf. Missing CSRF tokens often cause confusing 419 responses for beginners.
Using the Wrong Route File
Put browser pages in routes/web.php. Put stateless API endpoints in routes/api.php. If you put an API endpoint in web.php, it may receive session and CSRF behavior you did not expect. If you put a browser form route in api.php, it will not behave like a normal web form.
Hardcoding URLs Instead of Using Named Routes
Hardcoded paths are easy at first and annoying later. Use named routes for links, redirects, and tests whenever the route belongs to your application.
Defining Broad Routes Too Early
A broad route such as /{page} can accidentally catch requests that should go to more specific routes. Define specific routes first and be careful with catch-all patterns.
Forgetting to Clear Cached Routes
If production behaves as if your new route does not exist, route caching should be one of the first things you check. Clear and rebuild the route cache during deployment.
Best Practices
- Keep route files readable. They should show the application’s URL structure at a glance.
- Use controller routes for behavior that goes beyond simple pages or redirects.
- Name important routes, especially routes used in links, redirects, tests, emails, and notifications.
- Use route groups for shared middleware, prefixes, and naming patterns.
- Use route model binding to remove repetitive lookup code from controllers.
- Use API routes for stateless JSON endpoints and web routes for browser pages.
- Check routes with
php artisan route:listwhen debugging. - Cache routes in production, not as part of normal local development.
- Avoid clever URL patterns that future you will have to decode six months later.
Future internal-link opportunity: Laravel Authentication Guide.
Frequently Asked Questions
Where are routes defined in Laravel?
Most browser-facing routes are defined in routes/web.php. API routes are defined in routes/api.php after API routing is installed or configured.
Should beginners use closure routes or controller routes?
Use closure routes while learning simple examples. Once a route contains validation, database queries, or application decisions, move it to a controller.
What is the difference between web routes and API routes?
Web routes are built for browser pages and include web middleware such as sessions and CSRF protection. API routes are stateless and usually return JSON.
What are named routes used for?
Named routes let you generate URLs and redirects by name instead of hardcoding paths. This makes your application easier to change later.
What is route model binding?
Route model binding is Laravel’s ability to automatically resolve a route parameter into an Eloquent model instance, such as resolving /products/15 into a Product model.
When should I use route caching?
Use route caching in production deployment after routes are stable. Avoid relying on route cache during active local route development.
Conclusion
Laravel routing starts simple, but it becomes one of the main ways your application stays understandable. Clean routes show how users and systems move through your product. Messy routes hide important behavior and make the application harder to maintain.
Start with simple routes, move real behavior into controllers, name the routes your application depends on, group related routes, and use route model binding when URLs represent database records. Those habits will carry you through most beginner and intermediate Laravel projects.
Next cluster topics to build from this article: Laravel Controllers vs Closures, Laravel Middleware Guide, Laravel APIs Guide, and Laravel Authentication Guide.
FAQ
Where are routes defined in Laravel?
Most browser-facing routes are defined in routes/web.php. API routes are defined in routes/api.php after API routing is installed or configured.
Should beginners use closure routes or controller routes?
Closure routes are fine for learning and simple pages. Use controller routes when a route includes validation, database queries, or application decisions.
What is the difference between web routes and API routes?
Web routes are for browser pages and include web middleware such as sessions and CSRF protection. API routes are stateless and usually return JSON.
What are named routes used for?
Named routes let you generate URLs and redirects by name instead of hardcoding paths, which makes the application easier to change later.
What is route model binding?
Route model binding lets Laravel automatically resolve a route parameter into an Eloquent model instance.
When should I use route caching?
Use route caching during production deployment after routes are stable. Avoid relying on route cache during active local route development.
