Laravel Cloud is here! Zero-config managed infrastructure for Laravel apps. Deploy now.

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
Bacancy

Outsource a dedicated Laravel developer for $3,200/month. With over a decade of experience in Laravel development, we deliver fast, high-quality, and cost-effective solutions at affordable rates.

Visit Bacancy
Curotec logo

Curotec

World class Laravel experts with GenAI dev skills. LATAM-based, embedded engineers that ship fast, communicate clearly, and elevate your product. No bloat, no BS.

Curotec
Bacancy logo

Bacancy

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

Bacancy
Tinkerwell logo

Tinkerwell

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

Tinkerwell
Cut PHP Code Review Time & Bugs into Half with CodeRabbit logo

Cut PHP Code Review Time & Bugs into Half with CodeRabbit

CodeRabbit is an AI-powered code review tool that specializes in PHP and Laravel, running PHPStan and offering automated PR analysis, security checks, and custom review features while remaining free for open-source projects.

Cut PHP Code Review Time & Bugs into Half with CodeRabbit
Get expert guidance in a few days with a Laravel code review logo

Get expert guidance in a few days with a Laravel code review

Expert code review! Get clear, practical feedback from two Laravel devs with 10+ years of experience helping teams build better apps.

Get expert guidance in a few days with a Laravel code review
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
Harpoon: Next generation time tracking and invoicing logo

Harpoon: Next generation time tracking and invoicing

The next generation time-tracking and billing software that helps your agency plan and forecast a profitable future.

Harpoon: Next generation time tracking and invoicing
Lucky Media logo

Lucky Media

Get Lucky Now - the ideal choice for Laravel Development, with over a decade of experience!

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
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Multi-tenant 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

The latest

View all →
Laravel 12.44 Adds HTTP Client afterResponse() Callbacks image

Laravel 12.44 Adds HTTP Client afterResponse() Callbacks

Read article
Handle Nested Data Structures in PHP with the Data Block Package image

Handle Nested Data Structures in PHP with the Data Block Package

Read article
Detect and Clean Up Unchanged Vendor Files with Laravel Vendor Cleanup image

Detect and Clean Up Unchanged Vendor Files with Laravel Vendor Cleanup

Read article
Seamless PropelAuth Integration in Laravel with Earhart image

Seamless PropelAuth Integration in Laravel with Earhart

Read article
Laravel API Route image

Laravel API Route

Read article
Laravel News 2025 Recap image

Laravel News 2025 Recap

Read article