Building Web Apps with Laravel Module 3 – Requests, routing and middleware
Routing, route parameters and URL generation
Match Laravel 13 routes with parameters, constraints and groups, build URLs from route names, sign a link so edits are rejected, and cache routes.
What you will learn
- Define routes for the common HTTP verbs with required, optional and constrained parameters
- Predict which route answers a path, and whether it answers 200, 404 or 405
- Group routes by prefix, name, controller and domain and read the result with route:list
- Generate URLs from route names, including query values and URL defaults
- Create and test a signed URL, and cache routes at a deploy while avoiding what makes caching fail
Before you start
What you’ll learn · Before you start
What you will learn
- Define routes for the common HTTP verbs with required, optional and constrained parameters
- Predict which route answers a path, and whether it answers 200, 404 or 405
- Group routes by prefix, name, controller and domain and read the result with route:list
- Generate URLs from route names, including query values and URL defaults
- Create and test a signed URL, and cache routes at a deploy while avoiding what makes caching fail
Before you start
On this page
Pathshala’s batch page lives at /batches/7. Ask for /batches/seven and the router answers 405 Method Not Allowed, not the 404 that most developers expect for a URL no page owns. That one surprise comes from three rules: routes are tried in the order they were declared, a constraint belongs to the single route that carries it, and a path that matches under another verb gets a different answer from a path that matches nothing.
This lesson learns those rules by running them. You will resolve nine paths against real routes, group routes by prefix and name, build URLs from route names instead of typing them, sign a link so that an edited copy is refused, and cache routes the way a deploy does, including the mistake that makes the cache fail.
A route pairs a verb and a path pattern with an action
A route: A line in Laravel that maps an HTTP method and a URL path to the code that answers it, such as Route::get('/notices', [NoticeController::class, 'index']). More in “Your first page: route, controller and view” is a rule of the form: for this HTTP verb and this path pattern, run this action. The Route facade has one method for each common verb, get, post, put, patch, delete and options, plus match for a list of verbs and any for all of them. A route registered with get also answers HEAD, which is why php artisan route:list prints GET|HEAD beside it. Here are the routes of this lesson’s probe project:
<?php
use App\Enums\FeeStatus;
use Illuminate\Support\Facades\Route;
// First match wins, so the fixed path /batches/new sits above the pattern /batches/{batch}.
Route::get('/batches', fn () => 'batch list')->name('batches.index');
Route::get('/batches/new', fn () => 'new batch form')->name('batches.create');
Route::get('/batches/{batch}', fn (string $batch) => "batch {$batch}")
->whereNumber('batch')
->name('batches.show');
// {roll?} is optional; where() limits it to two capital letters followed by three digits.
Route::get('/batches/{batch}/students/{roll?}', fn (string $batch, ?string $roll = null) => "batch {$batch}, roll ".($roll ?? 'none'))
->whereNumber('batch')
->where('roll', '[A-Z]{2}[0-9]{3}')
->name('batches.students');
// The FeeStatus type hint is an implicit enum binding: only due, paid and late reach the closure.
Route::get('/fees/{status}', fn (FeeStatus $status) => "fees that are {$status->value}")
->name('fees.by-status');
Route::post('/batches', fn () => 'create a batch')->name('batches.store');
Route::delete('/batches/{batch}', fn (string $batch) => "remove batch {$batch}")->name('batches.destroy'); The rest of the project is ordinary. Run route:list to see how Laravel reads those lines:
from session import run
# Every route of the probe project: verbs, paths, names and actions.
run(
"""
php artisan route:list --except-vendor
""",
app=True,
overlay=("routes-probe",),
) Output
$ php artisan route:list --except-vendor
GET|HEAD batches ........................ batches.index › routes/web.php:7
POST batches ....................... batches.store › routes/web.php:23
GET|HEAD batches/new ................... batches.create › routes/web.php:8
GET|HEAD batches/{batch} ................. batches.show › routes/web.php:9
DELETE batches/{batch} ............. batches.destroy › routes/web.php:24
GET|HEAD batches/{batch}/students/{roll?} batches.students › routes/web.p…
GET|HEAD fees/{status} ................ fees.by-status › routes/web.php:20
Showing [7] routes
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 verbs_session.py
Each row is one verb, one path pattern, a name and where the action was defined. --except-vendor hides the routes that packages register, so only yours are shown. The pattern batches/{batch} appears twice, once for GET and once for DELETE, and that is not a mistake: the router distinguishes routes by verb and pattern together.
HTML forms can send only GET and POST. To reach a PUT, PATCH or DELETE route from a form, the Blade directive @method('PUT') adds a hidden _method field, and the HTTP kernel treats the request as the verb named there before it chooses a route. A script or a mobile app does not need the field, because it can send the real verb.
Page-based routing exists too
The Folio package routes by files: a Blade file under resources/views/pages becomes a URL. It suits content-heavy sites. This track uses route files, which keep every URL in one searchable place.
Parameters and constraints decide what matches, and the first match wins
A route parameter: A part of a route's path pattern in curly braces, such as {batch}. It captures that part of the URL and hands it to the action by name. is a part of the path pattern in curly braces. It captures that segment of the URL and passes it to the action by name. A parameter followed by a question mark, as in {roll?}, is optional, and the action must give it a default value. A route constraint: A rule that limits what a route parameter may capture, such as whereNumber or a regular expression. A route whose parameter fails the rule is skipped. narrows what a parameter may capture: whereNumber('batch') accepts digits only, where('roll', '[A-Z]{2}[0-9]{3}') takes any regular expression, and whereAlpha, whereUuid and whereIn cover common shapes. A type-hinted backed enum works as a built-in constraint, called implicit enum binding: A route parameter type-hinted with a backed enum matches only the enum's values; any other value answers 404 and the action does not run.. Run nine paths through the real kernel:
from session import run
# Resolve a list of paths against constrained, optional and enum routes.
run(
"""
php resolve_paths.php /batches /batches/new /batches/7 /batches/seven /batches/7/students /batches/7/students/AB123 /batches/7/students/ab123 /fees/paid /fees/refunded
""",
app=True,
overlay=("routes-probe",),
) Output
$ php resolve_paths.php /batches /batches/new /batches/7 /batches/seven /batches/7/students /batches/7/students/AB123 /batches/7/students/ab123 /fees/paid /fees/refunded GET /batches 200 batch list GET /batches/new 200 new batch form GET /batches/7 200 batch 7 GET /batches/seven 405 Allow: DELETE GET /batches/7/students 200 batch 7, roll none GET /batches/7/students/AB123 200 batch 7, roll AB123 GET /batches/7/students/ab123 404 GET /fees/paid 200 fees that are paid GET /fees/refunded 404
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 resolve_session.py
Read the results one at a time. /batches/new matched the fixed route and /batches/7 the constrained one. /batches/7/students has no roll, so the optional parameter took its default. AB123 matched the roll pattern and ab123 did not, so it answered 404. /fees/refunded answered 404 too, because the FeeStatus enum has no such case, and no code of yours ran.
The odd one is /batches/seven, which answered 405 with Allow: DELETE. The GET route rejected seven because of its whereNumber, but the DELETE /batches/{batch} route carries no constraint, so its pattern accepts any word. The router then found a path that exists under another verb. Step through the order:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- The request arrives. The router holds the routes in the order they were declared and asks each one, from the top, whether it matches the method and the path of the request.
- A shorter path. GET /batches has one segment less, so its pattern cannot match a path with two segments.
- A fixed word. GET /batches/new matches only the text new in its second segment. The request has seven there, so it does not match.
- The constraint says no. GET /batches/{batch} has the right shape, but whereNumber requires digits and seven is not digits. The route is skipped.
- A sibling that matches. DELETE /batches/{batch} has the same path pattern and no constraint, so seven fits the pattern. Its verb is DELETE, which is not GET.
- Wrong verb, not wrong path. A path that matches only under another verb is answered with 405 and an Allow header that lists the verbs that would work. A 404 would mean no pattern matched at all.
A 405 therefore means the path pattern exists, only under other verbs, and a 404 means that no pattern matched at all. A constraint protects only the route that carries it. When one pattern appears in several routes, put the constraint where all of them share it: in a group, or with Route::pattern('batch', '[0-9]+') in a service provider’s boot method, which applies one rule to every parameter of that name.
The same rule explains why order matters. The router asks the routes from the top and the first match wins:
A pattern above a fixed path
If Route::get('/batches/{batch}', ...) is declared above Route::get('/batches/new', ...) and has no constraint, the word new is captured as a batch and the form never shows. In a test of that order, /batches/new answered 200 with the body batch new. Declare the fixed path first, or add whereNumber, which is what the probe project does.
Checkpoint
Check: Parameters, constraints and first match
Groups share a prefix, a name, a controller and middleware
Routes for one feature repeat the same settings. A route group: A way to give several routes the same prefix, name prefix, controller, middleware or domain once, instead of repeating it on each route. states them once. prefix() adds a path prefix, name() adds a name prefix, controller() sets the class whose methods the routes name, middleware() attaches middleware, and domain() limits the group to a host. The settings combine, and the group’s closure holds the routes:
<?php
use App\Http\Controllers\FeeController;
use Illuminate\Support\Facades\Route;
// One group sets the controller, the path prefix, the name prefix and a middleware alias for all its routes.
Route::controller(FeeController::class)
->prefix('fees')
->name('fees.')
->middleware('throttle:60,1')
->group(function () {
Route::get('/', 'index')->name('index');
Route::get('/{receipt}', 'show')->whereNumber('receipt')->name('show');
});
// A domain group: the first label of the host becomes the {centre} parameter.
Route::domain('{centre}.pathshala.test')
->name('portal.')
->group(function () {
Route::get('/', fn (string $centre) => "portal of {$centre}")->name('home');
}); Ask Laravel what those few lines produced:
from session import run
# php artisan route:list -v shows the full path, the full name, the action and the middleware of every route.
run(
"""
php artisan route:list --except-vendor -v
""",
app=True,
overlay=("groups-probe",),
) Output
$ php artisan route:list --except-vendor -v
GET|HEAD {centre}.pathshala.test/ .... portal.home › routes/web.php:20
⇂ web
GET|HEAD fees ....................... fees.index › FeeController@index
⇂ web
⇂ throttle:60,1
GET|HEAD fees/{receipt} ............... fees.show › FeeController@show
⇂ web
⇂ throttle:60,1
Showing [3] routes
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 groups_session.py
The first group made two routes whose paths are fees and fees/{receipt}, whose names are fees.index and fees.show, and whose action is FeeController@index and @show. The -v flag adds the middleware under each route: web, which every route in routes/web.php receives, and throttle:60,1, the group’s own. The domain group made a route on {centre}.pathshala.test, and the first label of the host reaches the action as the $centre parameter, so one definition can serve a separate subdomain for every centre.
Groups nest. A prefix inside a prefix concatenates, and so do name prefixes, which is why the trailing dot in name('fees.') matters: without it the names would read feesindex. When a route behaves oddly, route:list -v is the first place to look, because it prints the final path, the final name and the final middleware, after every group has had its say.
Names turn into URLs, and defaults fill the gaps
Typing /batches/7 into a view works until the path changes. A named route: A route given a label with ->name('notices.index'), so its URL can be built anywhere with route('notices.index') instead of a hardcoded path. More in “Your first page: route, controller and view” lets a view write route('batches.show', $batch) and leaves the path to the router. Passing a model works because the router asks the model for its route key, its id unless you change it. Values that match no parameter become the query string. Run the calls:
from session import run
# Names and values in, paths out; then the same call with URL::defaults() set.
run(
"""
php url_generation.php
""",
app=True,
overlay=("urls-probe",),
) Output
$ php url_generation.php
route('batches.show', 7) http://localhost:8000/batches/7
route('batches.show', $batch) http://localhost:8000/batches/7
route('batches.show', $batch, false) /batches/7
route('batches.index', [...]) http://localhost:8000/batches?page=2&q=evening%20batch
url('/notices') http://localhost:8000/notices
route('timetable') UrlGenerationException: Missing required parameter for [Route: timetable] [URI: {centre}/timetable] [Missing parameter: centre].
route('timetable') with a default http://localhost:8000/north/timetable
route('timetable', 'south') http://localhost:8000/south/timetable
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 url_session.py
The first two lines build the same URL, one from the number 7 and one from a model with that id. The third passes false and gets the path only, which is what a link inside a page often wants. The batches.index call shows the leftovers: page and q follow a question mark, and the space in evening batch is written %20. The URL begins with the application’s root, which comes from the APP_URL setting when there is no request to take it from.
Then route('timetable') fails. Its pattern is {centre}/timetable, and nothing says which centre. The error names the route and the missing parameter, and that message is the reason to prefer names over strings: a typo is found when the call runs, not when a visitor clicks. A URL defaults: Values set with URL::defaults that fill route parameters the caller of route() leaves out, such as the centre in {centre}/timetable. repairs it. After URL::defaults(['centre' => 'north']) the same call returns /north/timetable, and an explicit value, as in route('timetable', 'south'), still wins. The documentation suggests calling it from a route middleware, which can read the signed-in user or the host, and adds a rule: give that middleware a higher priority than SubstituteBindings, or implicit model binding may run before the default exists.
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- A name and a value. The call route('timetable', 'south') gives a route name and one value. Nothing is requested; the call only builds text.
- The route is found by name. The router looks the name up in its list. A name that no route has is an error, which is why a typo shows up at once and not as a dead link.
- Parameters are filled in. The path is {centre}/timetable, so south takes the place of {centre}. A parameter that you leave out is taken from URL::defaults, and an error is raised if neither supplies it.
- Leftovers become the query. A value that matches no parameter is added after a question mark, so route('batches.index', ['page' => 2]) ends with ?page=2, and spaces are written as %20.
- The root is added. By default the URL is absolute and starts with the application's root. Passing false as the third argument leaves the root off and gives the path only.
- The finished URL. The result for route('timetable', 'south') is the root followed by /south/timetable. If the path changes later, the call stays the same.
Key idea
A route name is the stable part of a link and the path is the changeable part. Build every URL from the name and the values, and a change to a path touches one line.
Checkpoint
Check: Groups share prefix, name, controller and middleware · Building URLs from names, with defaults
A signed URL proves a link was not edited
Some links must work without a login: a receipt a parent opens from an email, or a download from a message. If the link is /receipts/1042, anyone can try /receipts/1043. A signed URL: A URL with a signature in its query, a keyed hash of the whole URL. The server refuses a copy that was edited or has passed its expiry. adds a signature to the query, a keyed hash of the whole URL made with the application key. The signed middleware, an alias you can put on any route, makes the hash again from what arrived and refuses the request with 403 when the two differ. URL::temporarySignedRoute() also adds an expiry time, and the signature covers that too. Make a link, then abuse it:
from session import run
# A temporary signed URL, requested untouched, edited and expired.
run(
"""
php signed_links.php
""",
app=True,
overlay=("signed-probe",),
) Output
$ php signed_links.php link /receipts/1042 query values: expires, signature as made 200 fee changed from 1042 to 1043 403 signature removed 403 31 minutes later 403 permanent link /receipts/1042 query values: signature permanent link as made 200
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 signed_session.py
The link as made answered 200. Changing the fee number answered 403, and so did removing the signature. A request made 31 minutes later, with the clock moved by Carbon::setTestNow, answered 403 for a link that was valid for 30. The permanent link, made with signedRoute, has only a signature.
The drawing shows the whole scene; its 4 steps are listed below.
Every step as text
- The app makes the link. temporarySignedRoute adds an expiry time to the URL and then adds a signature, a keyed hash of the whole URL made with the application key. Only a holder of the key can make a signature that fits.
- The link is used as made. The browser asks for exactly the URL it was given. The middleware makes the hash again from the URL it received, compares it with the one in the URL, and checks the expiry time. Both pass.
- One digit is changed. A curious visitor changes 1042 to 1043. The hash made from the new URL no longer equals the signature in the query, so the middleware stops the request with 403.
- The link is used too late. The link is untouched, but the time is 31 minutes after it was made and the expiry was 30 minutes. The signature is valid and the middleware still answers 403, because the expiry time has passed.
The signature proves that nobody changed the link. It does not prove who holds it. A forwarded email, a browser history or a log line gives the same working link to a stranger, until the expiry passes. For a private document use a short expiry, and put the auth middleware on the route as well when the link must work only for the signed-in owner. Laravel’s own email verification links are signed URLs in the same way.
Checkpoint
Check: Signed URLs
Route caching makes startup faster and rejects clashing names
On every request without a cache, Laravel loads the route files, runs the code in them and builds the route list. php artisan route:cache does that once and writes the result to bootstrap/cache/routes-v7.php. Requests then load one file. You run the command at each deploy, after the code is in place. Run it on the probe project, look at the list and clear it again:
from session import run
# Cache the routes of the probe project, list them from the cache, clear the cache again.
run(
"""
php artisan route:cache
ls bootstrap/cache
php artisan route:list --except-vendor --path=batches
php artisan route:clear
ls bootstrap/cache
""",
app=True,
overlay=("routes-probe",),
) Output
$ php artisan route:cache
INFO Routes cached successfully.
$ ls bootstrap/cache
packages.php
routes-v7.php
services.php
$ php artisan route:list --except-vendor --path=batches
GET|HEAD batches ........................................... batches.index
POST batches ........................................... batches.store
GET|HEAD batches/new ...................................... batches.create
GET|HEAD batches/{batch} .................................... batches.show
DELETE batches/{batch} ................................. batches.destroy
GET|HEAD batches/{batch}/students/{roll?} ............... batches.students
Showing [6] routes
$ php artisan route:clear
INFO Route cache cleared successfully.
$ ls bootstrap/cache
packages.php
services.php
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 cache_session.py
The list after caching has the same six batch routes, now read from the cache file. The cache is a snapshot. A route you add later is invisible until you run route:cache again, and while a cache exists, an edit to routes/web.php does nothing. Clear the cache with route:clear on a development machine, and cache only in production.
The other thing caching does is check. Two routes may share a name while the app runs, and a name lookup then picks one of them. The cache cannot store two routes under one name:
from session import run
# Two routes with one name: it works while the app runs, but route:cache refuses.
run(
"""
php artisan route:list --except-vendor
!php artisan route:cache
""",
app=True,
overlay=("dup-probe",),
) Output
$ php artisan route:list --except-vendor
GET|HEAD batches .................... batches.index › routes/web.php:5
GET|HEAD batches/archive ............ batches.index › routes/web.php:8
Showing [2] routes
$ php artisan route:cache
LogicException
Unable to prepare route [batches/archive] for serialization. Another route has already been assigned name [batches.index].
at vendor/laravel/framework/src/Illuminate/Routing/AbstractRouteCollection.php:257
253▕ $route->name($this->generateRouteName());
254▕
255▕ $this->add($route);
256▕ } elseif (! is_null($symfonyRoutes->get($name))) {
➜ 257▕ throw new LogicException("Unable to prepare route [{$route->uri}] for serialization. Another route has already been assigned name [{$name}].");
258▕ }
259▕
260▕ $symfonyRoutes->add($route->getName(), $route->toSymfonyRoute());
261▕
+19 vendor frames
20 artisan:16
Illuminate\Foundation\Application::handleCommand(Object(Symfony\Component\Console\Input\ArgvInput))
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 cache_fail_session.py
The list shows both routes, and route:cache stops with a LogicException that names the second route. The probe’s routes are closures, and they cached without trouble in Laravel 13.34. Even so, a route that points at a controller method is the safer form to deploy, because the cache then stores a class and a method name and not a copy of code.
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- Routes live in files. Without a cache, every request loads routes/web.php and the other route files, runs their code and builds the route list again.
- The command reads them once. php artisan route:cache loads every route file, checks the names, and prepares the routes for storing.
- A clash stops it. Two routes named batches.index make the command stop with a LogicException that names the second route, so the clash is found at deploy time and not on a visitor's request.
- One file is written. With unique names the command writes a single file, bootstrap/cache/routes-v7.php, that holds all the routes in a ready form.
- Requests load the cache. Each request now loads that one file and does not run the route files. A route you add afterwards is not seen until you run route:cache again, or route:clear to go back to the files.
Checkpoint
Check: Route caching
Practise
Rebuild the rules on your own Pathshala project, and check each with a request:
- Declare
GET /batches/{batch}withwhereNumber('batch')andDELETE /batches/{batch}without one. Request/batches/seven, note the status, then move the rule into aRoute::patterncall and request it again. - Put three routes into a group with a prefix, a name prefix and the
authalias. Read the result withphp artisan route:list -v, and write down the final path, name and middleware of each. - Make a
receipts.showroute with thesignedmiddleware, build a temporary signed URL in a feature test, and assert 200 for the link, 403 for an edited copy and 403 after$this->travel(31)->minutes().
Then work through the self-check.
Practice that adapts to you
Practise this lesson
17 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up: what is the difference between a 404 and a 405 from the router?
A 404 means that no route matched the path at all, so the router has nothing that could answer it. A 405 means that at least one route has a matching path pattern, but none of the matching routes accept the verb of this request, and the response carries an Allow header listing the verbs that would work. A common cause of a surprising 405 is a constraint that sits on one route and not on its sibling: the GET route refuses a value and a DELETE route with the same pattern and no constraint accepts it. The fix is to move the constraint to where every sibling shares it, in a group or in a global Route::pattern, and to test the failing path with a real request.
Three longer questions grow out of these rules:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
After a refactor the "new batch" form at /batches/new shows a page titled "Batch new", and no error is logged. Explain how a routing change can cause this, how you would confirm it, and how you would fix it so it cannot come back.
-
Fresher to mid
Pathshala emails each parent a link to a fee receipt that opens without a login. Design the link so a parent cannot read another family's receipt by changing the number, and say what your design still does not stop.
-
Mid to senior
A deploy script runs php artisan route:cache and the release stops with a LogicException about a route name, although the site worked in staging. Find the cause, fix it, and describe what you would add to the pipeline.
Flashcards · 7 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
The router has /batches/{batch} and /batches/new. Why does the order of the two matter?
Matching follows declaration order, and the earliest route that fits answers. Declared first, /batches/{batch} would read new as a batch id, so the fixed path goes above the pattern.
-
What is the difference between a 404 and a 405 from the router?
A 404 means no route's path matched. A 405 means a path matched but no matching route accepts this verb; the response's Allow header lists the verbs that would work.
-
How can a where() constraint on one route turn a bad id into a 405 instead of a 404?
A constraint belongs only to the route that carries it. If a sibling route with the same pattern and another verb has no constraint, it still matches the value, so the router answers 405 instead of 404. Put the constraint where every sibling shares it.
-
What can a route group set once for all the routes inside it?
A path prefix, a name prefix, a controller, middleware or a domain. php artisan route:list -v shows the final path, name and middleware each route ends up with.
-
Why build links with route() and a route name instead of writing the path?
The name is the stable part and the path can change: a link from route() follows the route, an unknown name or a missing parameter fails loudly, and URL::defaults can fill a parameter every route shares.
-
What does a signed URL prove, and what does it not?
It proves the link was not edited and has not expired: Laravel answers 403 to a changed or expired one. It does not prove who is using it, so it is no replacement for a login check.
-
What does php artisan route:cache do, and when does it fail?
Run at each deploy, it builds the route list once and writes it to one file in bootstrap/cache, so requests load that file instead of the route files. It is a snapshot (a new route needs route:cache again), and it stops with an exception when two routes share a name.
Key takeaways
- A route pairs a verb and a path pattern with an action, the router tries them in the order they were declared, and the first match wins, so a fixed path such as
/batches/newmust come before/batches/{batch}. - A constraint belongs to the route that carries it, so a sibling route with the same pattern can accept a value the first refused, and the result is a 405 with an
Allowheader, not a 404. - A route group sets a prefix, a name prefix, a controller, middleware or a domain once, and
php artisan route:list -vshows the final path, name and middleware of every route. - Building URLs from route names with
route()keeps links correct when a path changes, turns unknown names and missing parameters into errors, and letsURL::defaultsfill a parameter that every route shares. - A signed URL is refused with 403 when it is edited or past its expiry, but it proves integrity and not identity, and
route:cachewrites one cache file at a deploy and fails when two routes share a name.
Terms in this lesson
- implicit enum binding
- A route parameter type-hinted with a backed enum matches only the enum's values; any other value answers 404 and the action does not run.
- route constraint
- A rule that limits what a route parameter may capture, such as whereNumber or a regular expression. A route whose parameter fails the rule is skipped.
- route group
- A way to give several routes the same prefix, name prefix, controller, middleware or domain once, instead of repeating it on each route.
- route parameter
- A part of a route's path pattern in curly braces, such as {batch}. It captures that part of the URL and hands it to the action by name.
- signed URL
- A URL with a signature in its query, a keyed hash of the whole URL. The server refuses a copy that was edited or has passed its expiry.
- URL defaults
- Values set with URL::defaults that fill route parameters the caller of route() leaves out, such as the centre in {centre}/timetable.
References
- Routing (Laravel (laravel.com))
- URL generation (Laravel (laravel.com))
- Folio (Laravel (laravel.com))
- Deployment, optimization (Laravel (laravel.com))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress