I think we all have encountered a situation where we had a massive file contains our routes. I can't lie, this was driving me crazy for a long time and I had to figure a solution to tackle this issue. So, Here is what I have ended up using to structure my routes files.
Initially, I thought of making use of the fact that the group route method accepts a file, and that's how laravel addresses our routes at RouteServiceProvider
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
/**
* This namespace is applied to your controller routes.
*
* In addition, it is set as the URL generator's root namespace.
*
* @var string
*/
protected $namespace = 'App\Http\Controllers';
/**
* Define your route model bindings, pattern filters, etc.
*
* @return void
*/
public function boot()
{
//
parent::boot();
}
/**
* Define the routes for the application.
*
* @return void
*/
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
//
}
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*
* @return void
*/
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*
* @return void
*/
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
}
I abstracted the routes which concern the user for example to a file called users.php and duplicated the mapApiRoutes to be mapUsersRoutes and navigated to my users.php file.
/**
* Define the routes for the application.
*
* @return void
*/
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
$this->mapUsersRoutes();
//
}
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*
* @return void
*/
protected function mapUsersRoutes()
{
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/users.php'));
}
I know what you are thinking of, apparently, this isn't the best solution as whenever we start to create a new file, we will have to register it as we did before. So, I had to improve this initial idea.
I thought of breaking the routes file into regions that are common across the whole application, and I've come into a thought that all of our routes can't be out of this scope: authenticated, guest, and public routes.
I structured my routes folder to the following:
├── routes
│ ├── api
│ │ ├── public
│ | │ ├── users.php
│ │ ├── auth
│ | │ ├── users.php
│ │ ├── guest
│ | │ ├── users.php
at first glance, you might think that "well, It didn't change much, we are going to map these files again". But, we can actually make use of a function called "glob" that php provides by nature which is sort of out of the box solution, as we aren't coupled to laravel solutions.
glob accepts a pattern and can find filenames under a path that's matching our pattern. Hence our routes are structured under specific folders, we can now find all of the files under these folders and register them to their middlewares.
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
/**
* This namespace is applied to your controller routes.
*
* In addition, it is set as the URL generator's root namespace.
*
* @var string
*/
protected $namespace = 'App\Http\Controllers';
/**
* Define the routes for the application.
*
* @return void
*/
public function map()
{
$this->mapAuthRoutes();
$this->mapGuestRoutes();
$this->mapPublicRoutes();
// $this->mapWebRoutes();
//
}
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*
* @return void
*/
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*
* @return void
*/
protected function mapAuthRoutes()
{
foreach (glob(base_path('routes/api/auth/*.php')) as $file) {
Route::prefix('api')
->middleware(['api', 'auth:api'])
->group($file);
}
}
protected function mapGuestRoutes()
{
foreach (glob(base_path('routes/api/guest/*.php')) as $file) {
Route::prefix('api')
->middleware(['api', 'guest:api'])
->group($file);
}
}
protected function mapPublicRoutes()
{
foreach (glob(base_path('routes/api/public/*.php')) as $file) {
Route::prefix('api')
->middleware('api')
->group($file);
}
}
}
Now, whenever we create a new file, the foreach is going to have it as it's a matching pattern (the file is under the pathname and it has the extension of PHP, so it matches our pattern). Brilliant!, but hold on for a minute.
How these files are going to be registered?
If you reviewed laravel's lifecycle, you would understand that service providers are part of the lifecycle of laravel's request, which we can make use of this feature to register our routes dynamically.
That's it for this one!, I hope you enjoyed it. Don't forget to follow me! ✌️
Top comments (4)
Amazing approach my friend thank you for sharing and teaching!
Assalamu Alikum Mohammed Osama,
I have one question and one suggestion.
question:
what difference you consider between guest and public routes?
suggestion:
you can include your 'extra route files/sub_route files' in web.php and api.php files (using the php's include()/require()). it has two benefits:
you will not have to define same route in two different files (i.e. in web.php and api.php).
you don't have to write multiple multiple functions in RouteServiceProvider
Correct me if am wrong
Salam Naveed,
concerning the question:
the public route doesn't matter whether you are currently authenticated or not, you can view these routes in either case.
guest routes can only be shown when you aren't logged in, such as login/register routes.
concerning the suggestion:
I don't need to require the files inside the web.php/api.php as it will be repetitive for each file we are going to require, we will have to define its middleware which will end up doing the same logic I'm trying to achieve by the service provider, but without writing the same requiring and assigning the middleware.
concerning the first point, It's better to not have the same route at api/web, because they both have different middleware groups.
Thank you. This foreach example saves a lot of time and lines of code. :D