6 Ways to Organize Routes

In the course of work, it is always useful for a programmer to have structured records for later return to them. This allows you not to lose the necessary knowledge and unload memory. We offer you a useful entry in the work, which you can always refer to for organizing (refactoring) routes. Translation of an article famous in Laravel circles PovilasKorop

Laravel Routing is a feature that developers are learning from the very beginning. But as projects grow, it becomes more and more difficult to manage the ever-growing route files, scrolling through them to find the right one. Route::get() ad. Fortunately, there are methods to make route files shorter and more readable by grouping routes and their settings in different ways. Let’s see.

Here we will not talk only about the general simple Route::group()is the entry level. Let’s dive a little deeper.

Grouping 1. Route::resource and Route::ApiResource

Let’s start with the “elephant in the room”: this is perhaps the most famous grouping. If you have a typical set of CRUD actions around a single model, it’s worth grouping them into a controller resources

Such a controller may be up to 7 methods (but may have less):

  • index()

  • create()

  • store()

  • show()

  • edit()

  • update()

  • destroy()

So if your route set matches these methods, instead of code:

Route::get('books', [BookController::class, 'index'])->name('books.index');
Route::get('books/create', [BookController::class, 'create'])->name('books.create');
Route::post('books', [BookController::class, 'store'])->name('books.store');
Route::get('books/{book}', [BookController::class, 'show'])->name('books.show');
Route::get('books/{book}/edit', [BookController::class, 'edit'])->name('books.edit');
Route::put('books/{book}', [BookController::class, 'update'])->name('books.update');
Route::delete('books/{book}', [BookController::class, 'destroy'])->name('books.destroy');

.. you can only have one line:

Route::resource('books', BookController::class);

If you’re working with an API project, you don’t need the visual routes for creating/editing (the edit and create routes), so you might have a different syntax. apiResource() it will cover 5 methods out of 7:

Route::apiResource('books', BookController::class);

Also, I advise you to consider resource controllers even if you have 2-4 methods and not the full 7. Simply because it follows the standard naming convention – for urls, methods and route names.

For example, in this case, you don’t need to specify the names manually:

Route::get('books/create', [BookController::class, 'create'])->name('books.create');
Route::post('books', [BookController::class, 'store'])->name('books.store');
// Instead, here names "books.create" and "books.store" are assigned automatically
Route::resource('books', BookController::class)->only(['create', 'store']);

Grouping 2. A group within a group

Of course, everyone knows about the general route grouping. But for more complex projects, one level of grouping may not be enough.

Real world example: you want authorized routes to be grouped with auth middleware, but internally you need to separate more subgroups like admin and simple user.

Route::middleware('auth')->group(function() {
    Route::middleware('is_admin')->prefix('admin')->group(function() {
    	Route::get(...) // administrator routes
    });


    Route::middleware('is_user')->prefix('user')->group(function() {
    	Route::get(...) // user routes
    });
});

Grouping 3. Repeating middleware into a group

What if you have quite a lot of middleware, some of which are repeated in several route groups?

Route::prefix('students')->middleware(['auth', 'check.role', 'check.user.status', 'check.invoice.status', 'locale'])->group(function () {
    // ... student routes
});


Route::prefix('managers')->middleware(['auth', 'check.role', 'check.user.status', 'locale'])->group(function () {
    // ... manager routes
});

As you can see, there are 5 middlewares, 4 of which are repeated. So we can move these 4 to a separate middleware group in the file app/Http/Kernel.php:

protected $middlewareGroups = [
    // This group comes from default Laravel
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],

    // This group comes from default Laravel
    'api' => [
        // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
        'throttle:api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],

    // THIS IS OUR NEW MIDDLEWARE GROUP
    'check_user' => [
        'auth',
        'check.role',
        'check.user.status',
        'locale'
    ],
];

This is how we named our group. check_userand now we can shorten the routes:

Route::prefix('students')->middleware(['check_user', 'check.invoice.status'])->group(function () {
    // ... student routes
});

Route::prefix('managers')->middleware(['check_user'])->group(function () {
    // ... manager routes
});

Grouping 4. Controllers with the same name, different namespaces

A fairly common situation is to have, for example, HomeController for different user roles, for example Admin/HomeController and User/HomeController And if you use the full path in your routes, it looks something like this:

Route::prefix('admin')->middleware('is_admin')->group(function () {
    Route::get('home', [\App\Http\Controllers\Admin\HomeController::class, 'index']);
});

 

Route::prefix('user')->middleware('is_user')->group(function () {
    Route::get('home', [\App\Http\Controllers\User\HomeController::class, 'index']);
});

Quite a lot of code to type in with those full paths, isn’t it? This is why many developers prefer to have only HomeController::class in the list of routes and add something like this on top:

use App\Http\Controllers\Admin\HomeController;

But the problem here is that we have the same controller class name! So this won’t work:

use App\Http\Controllers\Admin\HomeController;
use App\Http\Controllers\User\HomeController;

Which one will be “official”? Well, one way is to change the name and assign an alias to one of them:

use App\Http\Controllers\Admin\HomeController as AdminHomeController;
use App\Http\Controllers\User\HomeController;
use App\Http\Controllers\Admin\HomeController as AdminHomeController;
use App\Http\Controllers\User\HomeController;

 
Route::prefix('admin')->middleware('is_admin')->group(function () {
    Route::get('home', [AdminHomeController::class, 'index']);
});


Route::prefix('user')->middleware('is_user')->group(function () {
    Route::get('home', [HomeController::class, 'index']);
});

But for me personally, changing the class name from above is quite confusing, I like a different approach: add namespace() for subfolders of Controllers:

Route::prefix('admin')->namespace('App\Http\Controllers\Admin')->middleware('is_admin')->group(function () {
    Route::get('home', [HomeController::class, 'index']);
    // ... other controllers from Admin namespace
});

 
Route::prefix('user')->namespace('App\Http\Controllers\User')->middleware('is_user')->group(function () {
    Route::get('home', [HomeController::class, 'index']);
    // ... other controllers from User namespace
});
hidden text

However, not all IDEs work correctly with this approach.

VSCODE will incorrectly show controller on hover

In addition, starting with Laravel 8, the namespace assignment removed from documentation and is not supported. This section is only available for Laravel 7


Grouping 5. Separate route files

If you feel like your main routes/web.php or routes/api.php gets too big, you can take some routes and put them in a separate file, name them whatever you want, like routes/admin.php.

Then, to include this file, you have two ways: I call it “Laravel way” and “PHP way”.

If you want to follow the structure of how Laravel structures its default route files, this happens in app/Providers/RouteServiceProvider.php :

public function boot()
{
    $this->configureRateLimiting();
    $this->routes(function () {
        Route::middleware('api')
            ->prefix('api')
            ->group(base_path('routes/api.php'));

        Route::middleware('web')
            ->group(base_path('routes/web.php'));
    });
}

As you can see, both routes/api.php and routes/web.php are here, with slightly different settings. So, all you have to do is add your admin file here:

$this->routes(function () {
    Route::middleware('api')
        ->prefix('api')
        ->group(base_path('routes/api.php'));
 
    Route::middleware('web')
        ->group(base_path('routes/web.php'));

    Route::middleware('is_admin')
        ->group(base_path('routes/admin.php'));
});

But if you don’t want to dive into service providers there is a shorter way – just include/require the routes file in another file, as you would in any PHP file, outside of Laravel.

In fact, Taylor Otwell did it himself, demanding routes/auth.php file directly to Laravel Breeze routes :

routes/web.php :

Route::get('/', function () {
    return view('welcome');
});

Route::get('/dashboard', function () {
    return view('dashboard');
})->middleware(['auth'])->name('dashboard');

require __DIR__.'/auth.php';

Grouping 6. New in Laravel 9: Route::controller()

If you have multiple methods in a controller but they don’t follow the standard resource structure, you can still group them without repeating the controller name for each method.

Instead of:

Route::get('profile', [ProfileController::class, 'getProfile']);
Route::put('profile', [ProfileController::class, 'updateProfile']);
Route::delete('profile', [ProfileController::class, 'deleteProfile']);

You can do:

Route::controller(ProfileController::class)->group(function() {
    Route::get('profile', 'getProfile');
    Route::put('profile', 'updateProfile');
    Route::delete('profile', 'deleteProfile');
});

This feature is available in Laravel 9 and latest minor versions of Laravel 8.


That’s it, these are grouping methods that will hopefully help you organize and maintain your routes, no matter how big your project is.

Similar Posts

Leave a Reply