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
Try our QuickAdminPanel generator!
1 Comment
Leave a Reply Cancel reply
Recent Posts
Try our QuickAdminPanel Generator!
How it works:
1. Generate panel online
No coding required, you just choose menu items.
2. Download code & install locally
Install with simple "composer install" and "php artisan migrate".
3. Customize anything!
We give all the code, so you can change anything after download.
Extremely good article, thank you Povilas. It has a good mix of very useful tips&tricks. I have encountered some of the issues you mentioned and it took me quite some time to figure it out. You’re helping others to fast-forward, great. And I learned some news things as well, even greater (for me).