Routing
By the end of this lesson you will be able to create web pages at any URL, read values from the URL, give routes names, group admin routes together and list every route in your app. Routing is the first thing Laravel does with each request, so it is a good place to start writing code.
What a route is
A route is a rule that says: "when a request comes for this URL with this HTTP method, run this code". All web routes live in routes/web.php.
Think of the Mumbai dabbawalas. Each tiffin has a code painted on the lid. The code tells every dabbawala on the way which train, which station and which office it goes to. A route is that code for a request: it tells Laravel exactly where to deliver it.
The simplest route returns a string:
<?php
use Illuminate\Support\Facades\Route;
Route::get('/about', function () {
return 'Vidya Coaching, Rohtak. Classes for JEE, NEET and Boards.';
});Route::get() takes two things: the URL path and the code to run. The code here is a closure (an unnamed function). Test it with curl while php artisan serve is running:
curl http://127.0.0.1:8000/aboutVidya Coaching, Rohtak. Classes for JEE, NEET and Boards.
HTTP methods
Each route also matches an HTTP method, the kind of request:
| Method | Used for | Laravel |
|---|---|---|
| GET | Showing a page or data | Route::get() |
| POST | Submitting a form, creating something | Route::post() |
| PUT / PATCH | Updating something | Route::put(), Route::patch() |
| DELETE | Deleting something | Route::delete() |
There is also Route::match(['get', 'post'], ...) for several methods and Route::any(...) for all of them. Browsers can only send GET and POST from HTML forms; you will see in Blade Templates how Laravel fakes PUT and DELETE.
Route parameters
Most URLs carry data, like a course code. Wrap a part of the path in curly braces to make it a parameter. Laravel passes it into your function.
Route::get('/courses/{code}', function (string $code) {
return "You asked for course {$code}";
});
Route::get('/batches/{year}/{month?}', function (int $year, ?int $month = null) {
return $month ? "Batches starting in {$month}/{$year}" : "All batches of {$year}";
})->whereNumber(['year', 'month']);The ? in {month?} makes the parameter optional, so the function needs a default value. whereNumber() is a constraint: the route only matches when those parts are digits. Here is what four requests return:
GET /courses/NEET-C -> You asked for course NEET-C GET /batches/2027 -> All batches of 2027 GET /batches/2027/4 -> Batches starting in 4/2027 GET /batches/abc -> 404 Not Found
Other constraints include whereAlpha(), whereAlphaNumeric(), whereUuid(), whereIn('mode', ['cash', 'upi']) and where('code', '[A-Z0-9-]+') with your own regular expression.
Returning different responses
Whatever your route returns becomes the response. Laravel converts it for you:
- A string becomes an HTML response.
- An array or Eloquent model becomes JSON with the right header.
view('name')renders a Blade template.redirect('/somewhere')sends the browser to another URL.
Route::get('/fees.json', function () {
return ['JEE-F' => 85000, 'NEET-C' => 42000];
});
Route::redirect('/admission', '/courses/JEE-F');
Route::view('/contact', 'contact');GET /fees.json -> {"JEE-F":85000,"NEET-C":42000}
GET /admission -> HTTP/1.1 302 Found, Location: /courses/JEE-FRoute::view() is a shortcut for routes that only show a template with no logic.
Named routes
Hard-coding URLs like /courses/JEE-F all over your templates is risky. If you later rename the URL, every link breaks. Instead, give the route a name and generate URLs from it.
Route::get('/courses/{code}', function (string $code) {
return "You asked for course {$code}";
})->name('courses.show');Now the route() helper builds the URL for you:
echo route('courses.show', ['code' => 'JEE-F']);http://localhost:8000/courses/JEE-F
The host comes from APP_URL in .env when there is no current request (for example in Tinker). In a browser request, it uses the real host. Change the path in one place, and every link updates.
Route groups
Admin pages often share a URL prefix, a name prefix and the same login check. A group applies these once:
Route::prefix('admin')->name('admin.')->group(function () {
Route::get('/dashboard', fn () => 'Admin dashboard')->name('dashboard');
Route::get('/reports', fn () => 'Fee reports')->name('reports');
});The URLs become /admin/dashboard and /admin/reports, and their names become admin.dashboard and admin.reports. In Middleware & Sessions you will add ->middleware('auth') to the same group so only logged-in staff can open it.
Route model binding
Very often a parameter is a database ID or code, and the first thing you do is load the row. Route model binding does it for you. Type-hint a model, and Laravel finds the record. If it does not exist, Laravel returns a 404 page automatically.
use App\Models\Course;
Route::get('/course-info/{course:code}', function (Course $course) {
return "{$course->name} costs Rs {$course->fee}";
});The :code part says "look up by the code column". Without it, Laravel uses the id column.
GET /course-info/C10-M -> Class 10 Boards Maths costs Rs 18000 GET /course-info/XYZ -> 404 Not Found
You will create the Course model and its table in Migrations, Seeders & Factories.
Resource routes and route:list
For a typical "manage students" section you need seven routes: list, create form, store, show, edit form, update and delete. One line creates all of them and points them at a controller:
use App\Http\Controllers\StudentController;
Route::resource('students', StudentController::class);To see every route in the app, run route:list. The --except-vendor option hides routes added by packages:
php artisan route:list --except-vendorGET|HEAD / ....................................... routes/web.php:7
GET|HEAD about ................................... routes/web.php:11
GET|HEAD admin/dashboard ........ admin.dashboard › routes/web.php:36
GET|HEAD admin/reports .............. admin.reports › routes/web.php:37
ANY admission ....... Illuminate\Routing › RedirectController
GET|HEAD batches/{year}/{month?} ................. routes/web.php:19
GET|HEAD contact .................................................
GET|HEAD course-info/{course:code} ............... routes/web.php:27
GET|HEAD courses/{code} ............. courses.show › routes/web.php:15
GET|HEAD fees.json ............................... routes/web.php:23
GET|HEAD students .......... students.index › StudentController@index
POST students .......... students.store › StudentController@store
GET|HEAD students/create . students.create › StudentController@create
GET|HEAD students/{student} .. students.show › StudentController@show
PUT|PATCH students/{student} students.update › StudentController@update
DELETE students/{student} students.destroy › StudentController@destroy
GET|HEAD students/{student}/edit students.edit › StudentController@edit
GET|HEAD {fallbackPlaceholder} ................... routes/web.php:42
Showing [18] routesThe last line comes from a fallback route, which runs when nothing else matches. It is a friendly custom 404:
Route::fallback(function () {
return response('Page not found. Try /courses', 404);
});Common mistakes
- Route order with similar paths.
/students/{student}defined before/students/createwill catch "create" as a student ID. Define fixed paths first, or useRoute::resource(), which orders them for you. - Forgetting the default for optional parameters.
{month?}needs?int $month = nullin the function, or PHP throws an error. - Hard-coding URLs in views. Use
route('name')so renaming a URL does not break links. - Expecting a new route to appear on a live server. If the server ran
php artisan route:cache, run it again after changing routes.
Key points
- Routes in
routes/web.phpmap an HTTP method and URL to a closure or controller. {name}creates a parameter,{name?}an optional one;whereNumber()and friends add constraints.- Strings become HTML, arrays become JSON;
Route::redirect()andRoute::view()are shortcuts. - Name routes with
->name()and build URLs withroute(). - Group shared prefixes, names and middleware; use model binding to load records and return 404 automatically.
php artisan route:listshows every route, its name and its action.
Exercise
Add these routes to the coaching app:
GET /results/{year}that only accepts a four-digit year and returns "JEE results for 2026" (with the given year).- A group with prefix
accountsand name prefixaccounts., containingGET /duesnamedduesthat returns "Pending fees list". - Print the URL of the dues route with
route().
Show solution
Route::get('/results/{year}', function (int $year) {
return "JEE results for {$year}";
})->where('year', '[0-9]{4}');
Route::prefix('accounts')->name('accounts.')->group(function () {
Route::get('/dues', fn () => 'Pending fees list')->name('dues');
});echo route('accounts.dues');http://localhost:8000/accounts/dues
The regular expression [0-9]{4} allows exactly four digits, so /results/26 returns 404. The group adds both the URL prefix and the name prefix.
Quick Quiz
1. What does {month?} mean in a route path?
A question mark makes the parameter optional; give the function argument a default value.
2. What happens with Route::get('/course-info/{course:code}', fn (Course $course) => ...) when no course has that code?
Route model binding returns 404 when the record is not found.
3. Why should you use named routes?
route('name') builds the URL from the route definition, so you change the path once.
4. How many routes does Route::resource('students', ...) create?
index, create, store, show, edit, update and destroy.
Learn Laravel with a mentor
Free lessons get you started. Our PHP & MySQL Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
