6 Tips To Organize Your Routes

Last updated on by

6 Tips To Organize Your Routes image

Laravel Routing is the feature that developers learn from the very beginning. But as their projects grow, it's getting harder to manage evergrowing routes files, scrolling to find the right Route::get() statements. Luckily, there are techniques to make the route files shorter and more readable, grouping routes and their settings in different ways. Let's take a look.

And no, I won't just talk about the general simple Route::group(), that one is the beginner level. Let's dive a bit deeper than that.


Grouping 1. Route::resource and Route::apiResource

Let's start with the elephant in the room: this is probably the most well-known grouping. If you have a typical set of CRUD actions around one Model, it's worth grouping them into a resource controller

Such controller can consist up to 7 methods (but may have fewer):

  • index()
  • create()
  • store()
  • show()
  • edit()
  • update()
  • destroy()

So if your set of routes corresponds to those methods, instead of:

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 may have just one line:

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

If you work with an API project, you don't need the visual forms for create/edit, so you may have a different syntax with apiResource() that would cover 5 methods out of 7:

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

Also, I advise you to consider the resource controllers even if you have 2-4 methods, and not the full 7. Just because it keeps the standard naming convention - for URLs, methods, and route names. For example, in this case, you don't need to provide 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. 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.

Realistic example: you want the authorized routes to be grouped with auth middleware, but inside you need to separate more sub-groups, like administrator 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 Group

What if you have quite a lot of middlewares, some of them repeating in a few 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 them repeating. So, we can move those 4 into 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'
],
];

So we named our group check_user, and 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. Same Name Controllers, Different Namespaces

Quite a common situation is to have, for example, HomeController for different user roles, like 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 with those full paths, right? That's why many developers prefer to have only HomeController::class in the route list 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 wouldn't work:

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

Which one would be the "official" one? Well, one way is to change the name and assign the alias for one of them:

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, personally, changing the name of the class on top is quite confusing to me, I like another approach: to add a namespace() for the sub-folders of the 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
});

Grouping 5. Separate Route Files

If you feel that your main routes/web.php or routes/api.php is getting too big, you may take some of the routes and put them into a separate file, name them however you want, like routes/admin.php.

Then, to enable that file to be included, you have two ways: I call the "Laravel way" and "PHP way".

If you want to follow the structure of how Laravel structures its default route files, it's happening in the 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 a bit different settings. So, all you need 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's a shorter way - just include/require your routes file into another file like you would do in any PHP file, outside of the Laravel framework.

In fact, it's done by Taylor Otwell himself, requiring the routes/auth.php file directly into 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. Route::controller()

If you have a few methods in the Controller but they don't follow the standard Resource structure, you may still group them, without repeating the Controller name for every method.

Instead:

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');
});

That's it, these are the grouping techniques that, hopefully, will help you to organize and maintain your routes, no matter how big your project grows.

PovilasKorop photo

Creator of Courses and Tutorials at Laravel Daily

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

image
Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Visit Laravel Forge
Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
Lucky Media logo

Lucky Media

Bespoke software solutions built for your business. We ♥ Laravel

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
LaraJobs logo

LaraJobs

The official Laravel job board

LaraJobs
Larafast: Laravel SaaS Starter Kit logo

Larafast: Laravel SaaS Starter Kit

Larafast is a Laravel SaaS Starter Kit with ready-to-go features for Payments, Auth, Admin, Blog, SEO, and beautiful themes. Available with VILT and TALL stacks.

Larafast: Laravel SaaS Starter Kit
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
Rector logo

Rector

Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies

Rector

The latest

View all →
DirectoryTree Authorization is a Native Role and Permission Management Package for Laravel image

DirectoryTree Authorization is a Native Role and Permission Management Package for Laravel

Read article
Sort Elements with the Alpine.js Sort Plugin image

Sort Elements with the Alpine.js Sort Plugin

Read article
Anonymous Event Broadcasting in Laravel 11.5 image

Anonymous Event Broadcasting in Laravel 11.5

Read article
Microsoft Clarity Integration for Laravel image

Microsoft Clarity Integration for Laravel

Read article
Apply Dynamic Filters to Eloquent Models with the Filterable Package image

Apply Dynamic Filters to Eloquent Models with the Filterable Package

Read article
Property Hooks Get Closer to Becoming a Reality in PHP 8.4 image

Property Hooks Get Closer to Becoming a Reality in PHP 8.4

Read article