0

Named routes and route() helper

Beginner5 min read·lv-06-003

Concept

Named routes give you a stable identifier for a URL that is decoupled from the actual URI string. Instead of hardcoding /users/42/settings in a redirect or a Blade template, you call route('users.settings', ['id' => 42]) and Laravel resolves the current URL for that route. This means renaming the URI in routes/web.php is a one-line change; nothing else breaks.

Under the hood, Route::name('users.settings') calls setName() on the Illuminate\Routing\Route object and registers the name in RouteCollection's internal $nameList — a flat array mapping name strings to Route objects. The route() helper calls Illuminate\Routing\UrlGenerator::route(), which uses Illuminate\Routing\RouteUrlGenerator to fill in required parameters, append remaining values as query string, and prepend the app URL.

UrlGenerator is smart about parameter substitution. If you pass a model object instead of a scalar, it calls getRouteKey() on the model (which returns $model->id by default, or whatever $primaryKey is set to). If you pass extra array entries beyond what the route pattern requires, they become query string parameters automatically.

Named routes are essential in two scenarios: redirects inside controllers and hyperlinks in views. Both redirect()->route('name', $params) and route('name', $params) in Blade use the same underlying UrlGenerator. The URL facade and url()->route() also resolve through the same mechanism, so the behavior is consistent everywhere.

The naming convention in Laravel is dot-notation: users.index, users.show, users.settings. Route::resource() automatically names routes following this convention: users.index, users.create, users.store, users.show, users.edit, users.update, users.destroy. Route groups with ->name('admin.') prepend to all names inside the group, producing admin.users.index without any per-route boilerplate.

Code Example

php
<?php

use App\Http\Controllers\UserController;
use Illuminate\Support\Facades\Route;

// Naming a route with ->name()
Route::get('/users/{id}/settings', [UserController::class, 'settings'])
    ->name('users.settings');

// Generating a URL from a named route
$url = route('users.settings', ['id' => 42]);
// => https://example.com/users/42/settings

// Passing a model — getRouteKey() is called automatically
$user = User::find(42);
$url = route('users.settings', $user);
// => https://example.com/users/42/settings

// Extra parameters become query string
$url = route('users.settings', ['id' => 42, 'tab' => 'security']);
// => https://example.com/users/42/settings?tab=security

// Relative URL (second argument = false for absolute)
$url = route('users.settings', ['id' => 42], false);
// => /users/42/settings

// Redirect to a named route in a controller
return redirect()->route('users.settings', ['id' => $user->id]);

// Blade — href using named route
// <a href="{{ route('users.settings', $user) }}">Settings</a>

// Checking the current route name in middleware or controller
if (request()->routeIs('users.*')) {
    // matches users.index, users.show, users.settings, etc.
}

// Named route inside a group — names are prefixed automatically
Route::name('admin.')->prefix('/admin')->group(function () {
    Route::get('/users', [AdminUserController::class, 'index'])->name('users.index');
    // Full name: admin.users.index
});

Interview Q&A

Q: How does the route() helper resolve named routes to URLs, and what data structure does it search?

route() calls app('url')->route($name, $parameters), which is Illuminate\Routing\UrlGenerator::route(). The UrlGenerator asks the RouteCollection for the route by name via $this->routes->getByName($name). RouteCollection maintains a $nameList array — a hash map from name string to Route object — so the lookup is O(1). The matched Route object is then passed to Illuminate\Routing\RouteUrlGenerator::to(), which substitutes parameters into the compiled URI pattern and appends extras as query string.


Q: What does request()->routeIs('admin.*') actually do under the hood?

Request::routeIs() calls $this->route()->getName() to get the current route's name, then passes it through Str::is() which supports * wildcards using fnmatch() semantics. If the name matches the pattern, it returns true. You can also pass multiple patterns: routeIs('admin.*', 'api.*'). This is frequently used in middleware and navigation components to highlight the active menu item or enforce section-level access rules without hardcoding URI strings.


Q: If two routes are given the same name, which one wins and is there a way to detect the collision?

The last registered route with that name wins, because RouteCollection::addToCollections() overwrites the $nameList entry. There is no runtime warning. The first route becomes unreachable by name. In development you can detect this by running php artisan route:list — duplicate names will appear in the table, but no exception is thrown automatically. For large teams, a CI step asserting no duplicate names in route:list --json output is a practical safeguard.