Tinkerwell - The PHP Scratchpad

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
Tinkerwell

Enjoy coding and debugging in an editor designed for fast feedback and quick iterations. It's like a shell for your application – but with multi-line editing, code completion, and more.

Visit Tinkerwell
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
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
PhpStorm logo

PhpStorm

The go-to PHP IDE with extensive out-of-the-box support for Laravel and its ecosystem.

PhpStorm
Laravel Cloud logo

Laravel Cloud

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

Laravel Cloud
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
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 →
Clawdbot Rebrands to Moltbot After Trademark Request From Anthropic image

Clawdbot Rebrands to Moltbot After Trademark Request From Anthropic

Read article
Automate Laravel Herd Worktrees with This Claude Code Skill image

Automate Laravel Herd Worktrees with This Claude Code Skill

Read article
Laravel Boost v2.0 Released with Skills Support image

Laravel Boost v2.0 Released with Skills Support

Read article
Radiance: Generate Deterministic Mesh Gradient Avatars in PHP image

Radiance: Generate Deterministic Mesh Gradient Avatars in PHP

Read article
Speeding Up Laravel News With Cloudflare image

Speeding Up Laravel News With Cloudflare

Read article
Livewire 4 Support in Laravel VS Code Extension v1.4.3 image

Livewire 4 Support in Laravel VS Code Extension v1.4.3

Read article