Laravel Routing – 8 Advanced Tips: Languages, APIs, Groups, Validation
Founder of QuickAdminPanel
We all use simple Route::get() and Route::post() syntax, but in bigger projects, it gets much more complicated. This article will compile various tips for different situations.
Tip 1. Route::get() BEFORE Route::resource()
For Resource Controllers, this is one of the most common mistakes, see this example:
Route::resource('photos', 'PhotoController'); Route::get('photos/popular', 'PhotoController@method');
The second route won’t be accurate, you know why? Because it will match show() method of Route::resource() which is /photos/{id}, which will assign “popular” as $id parameter.
So if you want to add any get/post route, in addition to Route::resource(), you need to put them BEFORE the resource. Like this:
Route::get('photos/popular', 'PhotoController@method'); Route::resource('photos', 'PhotoController');
Tip 2. Group in Another Group
We probably all know that we can group routes with Route::group() and assign different middlewares/prefixes and other parameters, like public routes and logged-in routes.
But what if you need a certain set of rules for sub-groups of those groups?
A typical example: you need public routes and authenticated routes, but within the authenticated group, you need to separate administrators from simple users.
So you can do this:
// public routes Route::get('/', 'HomeController@index'); // Logged-in users - with "auth" middleware Route::group(['middleware' => ['auth']], function () { // /user/XXX: In addition to "auth", this group will have middleware "simple_users" Route::group(['middleware' => ['simple_users'], 'prefix' => 'user'], function () { Route::resource('tasks', 'TaskController'); }); // /admin/XXX: This group won't have "simple_users", but will have "auth" and "admins" Route::group(['middleware' => ['admins'], 'prefix' => 'admin'], function () { Route::resource('users', 'UserController'); }); });
Tip 3. Route Parameter Validation – Multi-Language Example
A pretty typical case is to prefix your routes by language locale, like fr/blog and en/article/333. How do we ensure that those two first letters are not used for some other than language?
We can validate it directly in the route, with “where” parameter:
Route::group(['prefix' => '{locale}', 'where' => ['locale' => '[a-zA-Z]{2}']], function () { Route::get('/', 'HomeController@index'); Route::get('article/{id}', 'ArticleController@show'); });
The main part here is ‘where’ => [‘locale’ => ‘[a-zA-Z]{2}’] where we use a regular expression to match only two-letter combinations.
Tip 4. Dynamic Subdomain Routing
This comes directly from the official Laravel documentation, but rarely used so I think to mention it.
If you have a dynamic subdomain, like a different subdomain for every user, it needs to become a variable, right? Laravel has done that automatically for you. See example:
Route::domain('{account}.myapp.com')->group(function () { Route::get('user/{id}', function ($account, $id) { // }); });
Note that {account} automatically is passed as $account parameter in all inside controllers methods, so you need to accept that in all of them, don’t forget.
Tip 5. Be Careful with Non-English Route Model Binding
Sometimes, URLs should contain non-English words. For example, you have a Spanish portal for books and you want to have URL like /libros for the book list, and single book would have /libros/1, like a normal Resource Controller.
But in the database, all names should all be in English, so Laravel “magic” could work between singular and plural, right?
So if you generate a model Book with migration and Controller, you may have this command:
php artisan make:model Book -mcr
That -mcr key would generate a model and a resource controller, read more in this article. So, in that Controller, you would have this:
/** * Display the specified resource. * * @param \App\Book $book * @return \Illuminate\Http\Response */ public function show(Book $book) { // ... }
But in your routes/web.php, you would have this:
Route::resource('libros', 'BookController');
The problem is that it wouldn’t work. An even bigger problem is that it wouldn’t throw any error, just $book would be empty, and you wouldn’t understand why.
According to the official Resource Controller description, the name of the variable should be the same as a parameter in singular:
// So instead of public function show(Book $book) { // ... } // You should have public function show(Book $libro) { // ... }
But, to be very honest, in projects with non-English projects, I would suggest to not use Route::resource and Route Model Binding at all. Too much “magic” is unpredictable, like how Laravel would “guess” that singular of “libros” is “libro”?
Tip 6. API Routes – from V1 to V2
Imagine you’re working with API-based project and you need to release a new version of this API. So older endpoints will stay at api/[something], and for new version you would use api/V2/[something].
The whole logic is in app/Providers/RouteServiceProvider.php:
public function map() { $this->mapApiRoutes(); $this->mapWebRoutes(); // ... } protected function mapWebRoutes() { Route::middleware('web') ->namespace($this->namespace) ->group(base_path('routes/web.php')); } protected function mapApiRoutes() { Route::prefix('api') ->middleware('api') ->namespace($this->namespace) ->group(base_path('routes/api.php')); }
As you can see, API routes are registered in a separate function with prefix api/.
So, if you want to create V2 route group, you can create a separate routes/api_v2.php and do this:
public function map() { // ... older functions $this->mapApiV2Routes(); } // And new function protected function mapApiV2Routes() { Route::prefix('api/V2') ->middleware('api') ->namespace($this->namespace) ->group(base_path('routes/api_v2.php')); }
This way, old routes wouldn’t break, and you would just create a new set of routes.
Tip 7. Rate Limiting – Global and for Guests/Users
This also comes from official documentation, but with less-known details.
First, you can limit some URL to be called a maximum of 60 times per minute, with throttle:60,1.
Route::middleware('auth:api', 'throttle:60,1')->group(function () { Route::get('/user', function () { // }); });
But did you know you can do it separately for public and for logged-in users?
// maximum of 10 requests per minute for guests 60 for authenticated users Route::middleware('throttle:10|60,1')->group(function () { // });
Also, you can have a DB field users.rate_limit and limit the amount for specific user:
Route::middleware('auth:api', 'throttle:rate_limit,1')->group(function () { Route::get('/user', function () { // }); });
Tip 8. Route List and Route Caching
Final tip – how to check existing routes.
Not all of you know what routes exactly are hidden under Route::resource(), or under some more complex Route::group statement.
But at any point, you can check your actual route with Artisan command:
php artisan route:list
Keep in mind, that you are using route caching, after every change of your routes, you need to launch command:
php artisan route:clear