Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

PabloT's avatar

Laravel routes - best practices.

Hi,

Please, I need some advice. I'm building up an app that the client requires the URLs to be as the following example:

//www.radar.com
//-URL when you are on: Commercial site.

//www.radar.com/app/
//-URL when you are on: The app (service).

//www.radar.com/app/businesses/
//-URL when you are on: List of registered business on user's account.

//www.radar.com/app/businesses/visit-london-ltd/
//-URL when you are on: A selected business

//www.radar.com/app/businesses/visit-london-ltd/offices/
//-URL when you are on: List of offices a business has.

//www.radar.com/app/businesses/visit-london-ltd/offices/south-path-office/
//-URL when you are on: A selected office.

//www.radar.com/app/businesses/visit-london-ltd/offices/south-path-office/staff-members
//-URL when you are on: List of staff members an office has.

//www.radar.com/app/businesses/visit-london-ltd/offices/south-path-office/staff-members/[email protected]
//-URL when you are on: A selected staff member.

I have the application working but the URL's are not as the client wants... So far I have this set.

//App
Route::group(['prefix' => 'app' ], function () {
    //Business
    Route::resource('businesses', 'App\BusinessController');
    Route::group(['prefix' => 'businesses' ], function () {
        Route::resource('offices', 'App\OfficeController');
        Route::resource('members', 'App\MemberController');
    });
});
    

I would like to have the application following best practices/conventions, any advice? Thank you.

0 likes
11 replies
bobbybouwmann's avatar

Well, there is no real default for this. It's highly recommended to name your routes. This way you can reference the name on different places in your code and change the URL without having to change your application.

Documentation: https://laravel.com/docs/6.x/urls#urls-for-named-routes

For the rest, if your clients want those URLs you can do that. It doesn't really matter in the end ;)

1 like
Snapey's avatar

you will struggle doing it this way with nested resource routes. I would save resource routes for the maintenance of the data, and set all the public urls explicitly

eg

'app/businesses/{business}/offices/{office}/staff-members/{staff}'

and then all the lower level routes are just reduced versions of this path

martinbean's avatar

@pablot There’s no real concrete rules, but there are a handful of prevalent practices that make managing your routes easier in the long run.

With your example where you have a front-end marketing website and the application itself, a lot of businesses like that will have the marketing website on the www. subdomain (so www.radar.com) and then the application itself on an app. subdomain (i.e. app.radar.com). This allows you to separate your marketing website from the application itself, and scale both independently.

Other than that, the convention you’re following for businesses and offices is good. Use resource controllers (and nested resource controllers) as much as possible. If you build your application around resources as much as possible, it makes life so much easier!

Amaury's avatar

Try this:

Route::group(['prefix' => 'app' ], function () {
    Route::resource('businesses', 'App\BusinessController');
    Route::resource('businesses.offices', 'App\BusinessOfficeController');
    Route::resource('businesses.offices.members', 'App\BusinessOfficeMemberController');
});

More information: https://laravel.com/docs/master/controllers#restful-nested-resources.

If you run the php artisan route:list command, you will see urls as expected by your client.

Snapey's avatar

@amaury except that you wont see the words 'offices' or 'staff-members' in the url

Amaury's avatar

@snapey

I changed 'members' to 'staff-members':

Route::group(['prefix' => 'app' ], function () {
    Route::resource('businesses', 'App\BusinessController');
    Route::resource('businesses.offices', 'App\BusinessOfficeController');
    Route::resource('businesses.offices.staff-members', 'App\BusinessOfficeMemberController');
});

Routes generated:

METHOD: uri (name)
GET:    app/businesses (businesses.index)
POST:   app/businesses (businesses.store)
GET:    app/businesses/create (businesses.create)
GET:    app/businesses/{business} (businesses.show)
PATCH:  app/businesses/{business} (businesses.update)
DELETE: app/businesses/{business} (businesses.destroy)
GET:    app/businesses/{business}/edit (businesses.edit)

GET:    app/businesses/{business}/offices (businesses.offices.index)
POST:   app/businesses/{business}/offices (businesses.offices.store)
GET:    app/businesses/{business}/offices/create (businesses.offices.create)
GET:    app/businesses/{business}/offices/{office} (businesses.offices.show)
PATCH:  app/businesses/{business}/offices/{office} (businesses.offices.update)
DELETE: app/businesses/{business}/offices/{office} (businesses.offices.destroy)
GET:    app/businesses/{business}/offices/{office}/edit (businesses.offices.edit)

GET:    app/businesses/{business}/offices/{office}/staff-members (businesses.offices.staff-members.index)
POST:   app/businesses/{business}/offices/{office}/staff-members (businesses.offices.staff-members.store)
GET:    app/businesses/{business}/offices/{office}/staff-members/create (businesses.offices.staff-members.create)
DELETE: app/businesses/{business}/offices/{office}/staff-members/{staff_member} (businesses.offices.staff-members.destroy)
PATCH:  app/businesses/{business}/offices/{office}/staff-members/{staff_member} (businesses.offices.staff-members.update)
GET:    app/businesses/{business}/offices/{office}/staff-members/{staff_member} (businesses.offices.staff-members.show)
GET:    app/businesses/{business}/offices/{office}/staff-members/{staff_member}/edit (businesses.offices.staff-members.edit)
2 likes
PabloT's avatar

Hi All, Thank you so much for all the advice, as always super professional.

Just a quick update...

I'm having this:

//App
Route::group(['prefix' => 'app'], function () {

    //Businesses
    Route::resource('businesses', 'App\BusinessController');

    //Businesses/Domains
    Route::get('businesses/{slug}/domains/', ['as' => 'businesses.domains.index', 'uses' => 'App\BusinessDomainController@index']);
    Route::resource('businesses.domains', 'App\BusinessDomainController', ['except' => ['index']]);

    //Business/Offices
    Route::get('businesses/{slug}/offices/', ['as' => 'businesses.offices.index', 'uses' => 'App\BusinessOfficeController@index']);
    Route::resource('businesses.offices', 'App\BusinessOfficeController', ['except' => ['index']]);

    //Business/Members
    Route::get('businesses/{slug}/offices/{slug}/members', ['as' => 'businesses.offices.members.index', 'uses' => 'App\BusinessOfficeMemberController@index']);
    Route::resource('businesses.offices.members', 'App\BusinessOfficeMemberController', ['except' => ['index']]);

});

The reason why I'm implementing the "get" routes is that when for example:

In a selected business page: (www.domain.com/businesses/hca-hospital/)

I have a link to hca-hospital's offices (www.domain.com/businesses/hca-hospital/offices/). This link point to my index method on my App\BusinessOfficeController, without implementing "except" => ['index] and having the 'get' route it doesn't work.

If anyone knows the reason why personally I would appreciate the explanation.

Amaury's avatar

@pablot

You should not name the parameters with same name like in Route::get('businesses/{slug}/offices/{slug}/members'...

When you use:

Route::resource('businesses.offices.staff-members', 'App\BusinessOfficeMemberController');

The uri for the index page will be:

app/businesses/{business}/offices/{office}/staff-members

Parameters are:

  • {business}
  • {office}

In the Business and Office models you can customizing The key name for route model binding by overriding the getRouteKeyName method on the Eloquent models:

/**
 * Get the route key for the model.
 *
 * @return string
 */
public function getRouteKeyName()
{
    return 'slug';
}

See https://laravel.com/docs/master/routing#route-model-binding

PabloT's avatar

Hi,

Following @amaury

I have updated my code to:

    //App
    Route::group(['prefix' => 'app'], function () {

        //Businesses
        Route::resource('businesses', 'App\BusinessController');

        //Businesses/Domains
        Route::get('businesses/{business}/domains/', ['as' => 'businesses.domains.index', 'uses' => 'App\BusinessDomainController@index']);
        Route::resource('businesses.domains', 'App\BusinessDomainController', ['except' => ['index']]);

        //Businesses/Offices
        Route::get('businesses/{business}/offices/', ['as' => 'businesses.offices.index', 'uses' => 'App\BusinessOfficeController@index']);
        Route::resource('businesses.offices', 'App\BusinessOfficeController', ['except' => ['index']]);

        //Businesses/Offices/Members
        Route::get('businesses/{business}/offices/{office}/members', ['as' => 'businesses.offices.members.index', 'uses' => 'App\BusinessOfficeMemberController@index']);
        Route::resource('businesses.offices.members', 'App\BusinessOfficeMemberController', ['except' => ['index']]);

    });

Still, this is something I don't get...

In a selected business page: (www.domain.com/businesses/hca-hospital/)

I have a link to hca-hospital's offices (www.domain.com/businesses/hca-hospital/offices/). This link point to my index method on my App\BusinessOfficeController, without implementing "except" => ['index] and having the 'get' route it doesn't work.

If anyone knows the reason why personally I would appreciate the explanation.

Amaury's avatar

@pablot

Can you explain more this:

I have a link to hca-hospital's offices (www.domain.com/businesses/hca-hospital/offices/). This link point to my index method on my App\BusinessOfficeController, without implementing "except" => ['index] and having the 'get' route it doesn't work.

Amaury's avatar

@pablot

In your controller, did you add the parameters present in the uri?

Example in App\ BusinessOfficeController@index:

use App\Business;
use App\Office;

public function index(Business $business, Office $office)
{
    // ...
}

Please or to participate in this conversation.