As we're defining our route definitions there's actually something we can do now to help us vastly cut down on the amount of time needed to refactor in the future anytime we need to change one of our route's paths. We can do this by naming our routes.
Naming Routes
By naming our routes we can then dynamically generate a URL for that route throughout our application using its name instead of manually supplying the URL. When we do this, should we need to change a route's path in any way in the future, all we'd need to do is update the route's definition and all the dynamically generated URLs will auto-update. We won't need to worry about finding and updating each reference to the route.
We can name a route by chaining an extra method off the route's definition called as
. The as
method then accepts as the first argument the name we'd like to give the route.
Route.get('/posts', () => `get all posts`).as('posts.index')
Copied!
Here I've named my /posts
route posts.index
. We'll get more into generating URLs in the next lesson, but to show you the benefit here, let's generate a URL for this route.
Route.get('/', () => { const postsUrl = Route.makeUrl('posts.index') return postsUrl }) // RETURNS: /posts
Copied!
Here we're using the makeUrl
method off the Route module to generate a route for our posts.index
route. Pretty cool, huh?!
We can name our routes regardless of the HTTP Method the route is using. It's also default behavior to delimit route names using a period, hence why I'm following the resource.action
naming with posts.index
. Here's how it looks when we start adding additional routes.
Route.get('/posts', () => { return 'get all posts' }).as('posts.index') Route.get('/posts/:id', ({ params }) => { return `get post with id of ${params.id}` }).as('posts.show') Route.post('/posts', () => { return 'create a post' }).as('posts.store') Route.put('/posts/:id', ({ params }) => { return `update post with id of ${params.id}` }).as('posts.update') Route.delete('/posts/:id', ({ params }) => { return `delete post with id of ${params.id}` }).as('posts.destroy')
Copied!
So now we have routes with the names:
posts.index
forGET: /posts
posts.show
forGET: /posts/:id
posts.update
forPUT: /posts/:id
posts.destroy
forDELETE: /posts/:id
Grouping Routes
So, one thought you might be having is that the naming and route paths here can be a little redundant, having posts.
and /posts
for each. To remedy this, we can utilize route grouping. This allows us to apply things to a group of routes instead of routes individually. Of course, without taking away our ability to apply things to individual routes as well.
To define a group, we'll call a method off our Route module called group
. This then takes a callback function. Whatever routes we define within the group's callback function will then have all the items applied to the group applied to them as well.
So let’s turn our post routes into a group and remove those redundancies.
Route.group(() => { Route.get('/', () => { return 'get all posts' }).as('index') Route.get('/:id', ({ params }) => { return `get post with id of ${params.id}` }).as('show') Route.post('/', () => { return 'create a post' }).as('store') Route.put('/:id', ({ params }) => { return `update post with id of ${params.id}` }).as('update') Route.delete('/:id', ({ params }) => { return `delete post with id of ${params.id}` }).as('destroy') }).prefix('/posts').as('posts')
Copied!
Here we've placed all our post routes inside a group. Then, on the group itself, we're applying a method called prefix
. Prefix is specific to groups because it allows the group to prefix the provided string to all the group's route paths. In essence, this means the group is prefixing /posts
to each route path defined within the group. So, all our defined routes are still /posts
or /posts/:id
.
Second, we've also used as
to apply a name to the group as well. This will do the same thing for our routes names as prefix is doing. It's going to prefix each route name within the group with posts
, then it's also going to delimit the two with a period. Hence, our routes still have the same names as before.
Of course, you can verify this and see it for yourself by running node ace list:routes
within your terminal as well. That will show you all your defined routes, their paths, Http Method, name, etc.
Naming A Group Requirements
There's one requirement when you name a group using the as
method and that's that all routes defined within the named group must have a name as well. This is to prevent routes from receiving duplicate names.
Middleware, Subdomains, Namespacing, and Matchers
While we haven't gotten to most of these yet, I wanted to also make a note while we're talking about grouping that you can also apply middleware, subdomains, namespacing, and route validation and casting matchers to a route group as well. These all behave the same as prefix and as by applying the definition to each of the group's routes.
So, if we add a middleware to the group, that middleware would run anytime any of the group's routes are requested.
Nested Groups
Another cool thing that we can do with groups is nest them. So, say we had a marketing side and an application side to our application. We could create a group for the marketing side and another group for the application side. This would allow us to globally split the two apart from one another so we don't need to worry about collisions.
For example, we could have the same post routes we've been using defined within both and know that they'll work just fine because the outermost group separates them.
// Marketing Group Route.group(() => { Route.group(() => { Route.get('/', () => { return 'get all posts' }).as('index') Route.get('/:id', ({ params }) => { return `get post with id of ${params.id}` }).as('show') }).prefix('/posts').as('posts') }).as('marketing') // Application Group Route.group(() => { Route.group(() => { Route.get('/', () => { return 'get all posts' }).as('index') Route.get('/:id', ({ params }) => { return `get post with id of ${params.id}` }).as('show') Route.post('/', () => { return 'create a post' }).as('store') Route.put('/:id', ({ params }) => { return `update post with id of ${params.id}` }).as('update') Route.delete('/:id', ({ params }) => { return `delete post with id of ${params.id}` }).as('destroy') }).prefix('/posts').as('posts') }).prefix('/app').as('app')
Copied!
Now we'll have two sets of our post routes, one defined from our marketing group and one from our app group. Also, note that we didn't give our marketing group a prefix, so the routes are just /posts
and /posts/:id
. Lastly, since the marketing side of our site doesn't need updating or deleting capabilities, we forego those routes.
So our route names look like the below.
Marketing Routes
marketing.posts.index
forGET: /posts
marketing.posts.show
forGET: /posts/:id
Application Routes
app.posts.index
forGET: /app/posts
app.posts.show
forGET: /app/posts/:id
app.posts.store
forPOST: /app/posts
app.posts.update
forPUT: /app/posts/:id
app.posts.destroy
forDELETE: /app/posts/:id
Grouping for API
We could do the same sort of thing for an API, except we could use the nested grouping to version our API.
// API Routes Route.group(() => { Route.group(() => { Route.get('/', () => { return 'get all posts' }).as('index') Route.get('/:id', ({ params }) => { return `get post with id of ${params.id}` }).as('show') Route.post('/', () => { return 'create a post' }).as('store') Route.put('/:id', ({ params }) => { return `update post with id of ${params.id}` }).as('update') Route.delete('/:id', ({ params }) => { return `delete post with id of ${params.id}` }).as('destroy') }).prefix('/v1').as('v1') }).prefix('/api').as('api')
Copied!
Now we have the following:
API Routes
api.v1.posts.index
forGET: /api/v1/posts
api.v1.posts.show
forGET: /api/v1/posts/:id
api.v1.posts.store
forPOST: /api/v1/posts
api.v1.posts.update
forPUT: /api/v1/posts/:id
api.v1.posts.destroy
forDELETE: /api/v1/posts/:id
Worries About Size?
Hey, so if you're worried about how big our routes might get continuing on like this I just want to make a quick note for you. In a few lessons, we'll cover Controllers. Once we get to controllers all our route handling will be moved from our route definitions to those controllers. This will vastly simplify our route definitions!!! So, don't fret just yet, we'll get there.
Next Up
We’ve learned about the benefits of naming our routes and using groups. In the next lesson, we’ll learn how we can dynamically generate routes using the names we’ve defined in this lesson.
Join The Discussion! (0 Comments)
Please sign in or sign up for free to join in on the dicussion.
Be the first to Comment!