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.
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
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.
This always has been a discussion between developers even the creators of Laravel have this argument, so what is the good practice?
Lately I've seen a tweet from Taylor Otwell saying that he has deprecated the
nested routes section from Laravel docs because he didn't prefer it, while when you open Laracasts you see Jeffrey is implementing this concept like /series/php-testing/episodes/hello-world.
As I told it's a quiet argument and when it comes to choices like that I always do what it feels good for me. So if I were you I wouldn't nest neither teams or projects but maybe I would nest projects/documents, I don't go for nesting always.
This might be a bit of a deviation from the original question, but I feel that Adam Wathan's Laracon US 2017 talk might be a useful resource for this discussion. (https://www.youtube.com/watch?v=MF0jFKvS4SI)
I am relatively new to development and therefore explore a lot of code bases on github. I always struggle to understand nested routes. So as a best practice I would prefer no-nesting to nesting.
Keeping stuff "CRUDY by design" as Adam calls it, one thing you achieve, imo, is simplification of route names. If I were to work in a group, that is a pattern I would prefer.
However, referring to the penultimate paragraph on your question, I struggle to understand why an integrity check is not needed.
I think you should makes use of Role concepts, Route grouping , Middleware concepts to build this app
For Role related things check https://github.com/spatie/laravel-permission , a good package
Eg:
Route::group(['middleware' => ['role:super-admin']], function () {
//
});
You can almost do any role, permission related things using above package.
Assume roles like project manager, developer.
Group your Routes based on roles & assign proper Middleware as you need.
For URL : /teams/team-a/projects/project-b/documents/doc-c
Check the Authenticated user has a role in team-a & project-b (You can check this in Middleware, Controller or custom Service provider anywhere as your need).
Also just check https://laravelvoyager.com/
Thanks
Q1: So this article says attribute routing is more favourable than conventional routing for api versioning. It's not clear to me the reasons behind such claim because to me in order to support these:
/api/v1/products
/api/v2/products
all you need to do is to define two routes:
routes.MapHttpRoute("V1", "api/v1/products", new {controller = "V1Controller", action = "ListProducts"});
routes.MapHttpRoute("V2", "api/v2/products", new {controller = "V2Controller", action = "ListProducts"});
Can something share some insight?
Q2: this article says one issue with conventional routing is the order of the entries in the table and that you can accidentally have requests being mapped to the wrong controller. Why is this not an issue with attribute routing? I mean the template is just a string so how can it prevent me from defining two routes where one is more generic than the other?
Q3: Can any give a concret example where you can accomplish with attribute routing but can't with the conventional routing? - I am not talking about code readability and maintainability.
Q1
The document specifically states "makes it easy" for API versioning, not "makes it easier" or "is preferred".
I don't agree that convention-based routes are necessarily "hard to support" (provided you understand how routing works). But if you are not using a convention that is shared across multiple controllers in your project, it is far less code to maintain if you use attribute routing. Namely, you don't have to specify the controller and action to use on every route in the code since the RouteAttribute knows the action/controller it is paired with natively.
But as the document points out, putting all of your routes in one place does also have its merits. If your API is such that you can use conventions that apply to multiple/all controllers, then setting a single convention is easier to maintain than multiple route attributes.
For those conventions that might be "hard to support" with the built-in MapRoute method, you can extend convention-based routing as necessary using your own extension methods and even inherit the RouteBase class to define them however you like.
Q2
Ordering is an issue with attribute routing. In fact, attribute routing makes it more difficult to see what order your routes are registered in. Not all routes are order-sensitive, so it is not an issue much of the time.
However, when you do have an ordering problem with attribute routing, it is much more subtle than when using convention-based routing. Attributes do not guarantee any order when Reflection retrieves them. Therefore, the default ordering is unknown regardless of what order you have specified them on your controller actions.
Fixing ordering on attribute routing can be easy. Simply specify the Order property of the attribute (lower values are evaluated before higher values).
That said, there is no way to specify order between different controllers, so it could end up biting you. If that happens, the only built-in alternative is convention-based routing.
Q3
I can't give a concrete example of where you can use attribute routing where you can't use convention-based routing (and AFAIK, there isn't one). An example of using convention-based routing in a way that is not supported by attribute routing is to make data-driven CMS routes.
Attribute routing supports a subset of the functionality that convention-based routing supports. It is not more advanced technologically than convention-based routing. Convention-based routing gives you the ability to directly specify your own RouteBase (or Route) subclass, which allows you to do many things that you cannot do with the built-in attribute routing, such as make routes that are based on subdomains, query string values, form post values, or cookies. You can even make extension methods that generate routes based on conventions in advanced scenarios.
Attribute routing cannot be extended this way without resorting to Reflection because many of the types it uses are marked internal.
But there are 3 compelling reasons you might consider using attribute routing instead of convention-based routing, depending on your project:
It puts the routes in the context with the rest of the controller code, which might make it easier to maintain.
It means you don't need to type (or copy and paste) controller and action names into each of your route definitions. Maintaining this relationship (and working out when it is wrong) between routes and your controllers might cost more than what it is worth to have all of your routes defined in one place in terms of maintainability.
Attribute routing is easier to learn than convention-based routing. If your deadline is tight and/or your team is inexperienced with routing it could be a better choice to use attribute routing because the learning curve is shorter.
The thing to take away from this is: Use the type of routing that will be easiest to maintain in your project. If you have lots of similar patterns, use convention-based routing. If your URLs are inconsistent or irregular across the project or you simply like to see your URLs in the same context as your action methods when you work, consider attribute routing, but keep in mind that convention-based routing is another option.
NOTE: Most of the examples I have linked to are for MVC, not Web API. Routing is very similar (in fact, most of the code classes are shared) between the two frameworks. The same concepts can be used in both MVC and Web API as far as attribute/convention-based routing goes, but do be aware that you need to target the System.Web.Http namespace rather than the System.Web.Mvc namespace if you are using Web API and you want to take advantage of those examples.
I'm wondering if there is a way to declare routes in MVC3 so that the route "zone1/{controller}/{action}" would direct to {controller}.zone1{action} method and "zone2/{controller}/{action}" would direct to {controller}.zone2{action} method, for example. So that's basically transforming the target action name based on the route.
Thanks
Check out The Attribute Routing project. You can specify on your methods the routes which I feel is a bit easier to read. Here's a decent blurb on it:
http://gregorsuttie.com/2012/01/12/attributerouting-for-mvc/
You could also write your own custom route handler but I don't believe you can do what you want without some custom code. I could be wrong here though. The attribute routing project should work just fine for what you want however.
I'd like to be able to use T4MVC with custom routes.
Is this possible and how do I use it? I've not seen anything generated in the strong helpers to be able to do this. Since the custom routes are just strings, I don't know if this is even possible.
If by custom route you mean 'named routes', then T4MVC indeed doesn't generate anything for them, as that would require parsing your code.
Instead, the simple thing to do is to just define a public constant with the route name, and use that both in the route definition and wherever you need to refer to the named route.
Simple examples of controllers in a RESTful architecture suggest four actions per controller -- index, create, update, delete -- corresponding with the GET, POST, PUT, and DELETE. But beyond that I still find a dozen little decisions:
Do you have a different controller for resource collections (example.com/things) than you do for an individual resource (example.com/things/123)?
With individual resources, is it preferable to pass the id in as a parameter to the action, or set it as a member variable in the controller class?
How do you go about URI routing? The old tried-and-true example.com/{controller}/{action} approach kind of falls apart.
What about subordinate resources like example.com/user/123/things? Do you have to explicitly define every route for these or is there a way to write a good general rule?
Do you differentiate between API requests and browser requests, or do you channel them through the same controller and/or controller methods?
Obviously, you could go about these things a dozen different ways, but I'd really like to not have to re-invent the wheel if others have hashed through the problem. I'm looking for any advice or maybe better some good tutorials that deal with these (and other related) practical issues in designing a RESTful mvc framework.
My controllers have methods Get(), Put(), Post(), Delete(), etc. I think using the "action terms" confuses the issue.
I always create a different controller for collections and single things. To me they are very different resources and I want the HTTP methods to do different things.
Routing I do differently than most frameworks. I don't match on the entire url, but on a segment by segment basis. It is similar to the way SubResources work in JAX-RS
For services that only have a small number of distinct resources then using regex style url pattern matching is fine. I just found it started to break down once you start dealing with hundreds of resources.