Laravel Routing – 8 Advanced Tips: Languages, APIs, Groups, Validation
Povilas Korop
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