Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I Nest Routes to Resources in Laravel?

This may be a little subjective, but I feel that best-practice must exist (or even good design when it comes to Laravel apps). Googling results in lots of things that are not to do with the actual points in this question.


Say I am building a web application that has teams, which may have projects, which may have documents.

Should I design the routing so that documents are within the path of the projects they belong to, which are then within the path of the teams they belong to, or keep things at a top level?

As far as I can tell, there are two ends to this spectrum that are worth discussing (other options are just the grey in-between):

Nesting

Example, Doc C is found at: /teams/team-a/projects/project-b/documents/doc-c

It is easy to do this, and in the routing file, I can use route groups to help keep the structure clean. I think it's more logical and perhaps more convenient for the user (they can work out URLs for themselves!). My concerns are that I am importing complexity into each page request:

  • in checking that the route has integrity (i.e., that doc-c does belong to project-b), and
  • that the user has authority to access each of the nested assets all the way through the route.

Should I be putting gates/policy checks for every resource at the beginning of each controller method, for every route parameter? Otherwise, where can this be abstracted?

And regarding route integrity, I've never seen examples testing for this - so is this not a common approach? If we don't verify route integrity, then a page could show mixed information by hacking the route (e.g.,/teams/team-a/projects/project-Z/documents/doc-c, would show info about project Z on doc-c's page).

Without Nesting

Example, Doc C is found at : /documents/doc-c

In this example, every asset would have its own base route, more like an API I guess.

No integrity checks required, and the controller would pre-determine the other assets shown to generate the view.

But is this UX good enough? The majority of websites I've seen do not do this.

like image 689
Kurucu Avatar asked Nov 18 '18 07:11

Kurucu


People also ask

What is the use of Route resource in Laravel?

Route::resource: The Route::resource method is a RESTful Controller that generates all the basic routes required for an application and can be easily handled using the controller class.

What is the benefit of named routes in Laravel?

Named routing is another amazing feature of Laravel framework. Named routes allow referring to routes when generating redirects or Urls more comfortably. You can specify named routes by chaining the name method onto the route definition: Route::get('user/profile', function () { // })->name('profile');

What are the two main routing files found in Laravel?

All Laravel routes are defined in route files found within the routes directory. The application's App\Providers\RouteServiceProvider automatically stacks these files. And the routes/web. php file defines the routes for your web interface.

Do you understand routing in Laravel how it works?

Routing in Laravel allows you to route all your application requests to their appropriate controller. The main and primary routes in Laravel acknowledge and accept a URI (Uniform Resource Identifier) along with a closure, given that it should have to be a simple and expressive way of routing.


2 Answers

This is an interesting question - as you mentioned, it may be a little subjective, but worth the discussion.

You touch on a few points, so I will attempt to address them separately.

Nesting vs Not nesting

First thing to clear up in my opinion is browser routes versus API routes. If you are providing an API - either internally to your app or externally to the public, I would avoid nested routes for a few reasons:

  • resource/id format is quite standard and expressive for API's
  • this makes it easier to document
  • this makes it easier for the consumer app to dynamically construct API requests

However, your question does seem to focus on the browser routes. In my opinion browser routes can and should be whatever reads nicely - the url, especially these days, can be considered as part of the UI. For example, you may go to settings (and I would expect to see /settings), from the settings page, if I were to go into the notifications settings section, I would expect to see /settings/notifications.

The routes act and assist with UX - they are almost a breadcrumb and should look as such.

So, I would definitely nest for browser routes, and would definitely not for APIs.

Route integrity

The real heart of your question I think is about the route integrity. I think regardless if you choose to nest or not you need to be checking your permissions with the assumption that someone is tampering with the urls - the same way you assume that the user has tampered with the form input.

Essentially your routes (nested or not) act as input, and you will need to validate that. Route level middleware is one approach, but is often too generic to solve anything complex so you may find it easier to tackle it with controller middleware (https://laravel.com/docs/5.7/controllers#controller-middleware).

So you may do something like:

public function __construct()
{
    $this->middleware('auth');

    $this->middleware('canViewProject')->only('show');

    $this->middleware('canEditProject')->except('store');
}

Whether you use Gates, Policies or just plain old middleware will probably depend on the complexity of the project - but the above applies regardless - treat the urls as input and validate accordingly

like image 150
Chris Avatar answered Oct 18 '22 13:10

Chris


I've spent the last year looking at this and refining it, and stumbled upon an elegant solution recently. Nesting can be done nicely, I'm sticking to resource controllers and dot-syntax for nested route resources.

In order to enforce the route validation, I am using something like the following. There is a good answer here, which suggests explicitly binding the models in question.

So with

Route::resource('posts.comments', 'CommentsController');

You'd use

Route::bind('comment', function ($comment, $route) {
    return Comment::where('post_id', $route->parameter('post'))->findOrFail($comment);
});

This will automatically work everywhere. If required, you could test for the upstream parameter to be found, and only perform the test in those cases (e.g. to cater for routes where only a comment is specified).


So much work has been subsequently done on Laravel. My default response to this is "yes, use route nesting". I keep routes restful, using (nested) resource controllers wherever possible, and single-action controllers for the odd use case. Route scoping is even automatic now, if specified correctly.

like image 29
Kurucu Avatar answered Oct 18 '22 13:10

Kurucu