Unread Notifications

Latest Notifications

No Notifications

You're all set! Start a discussion by leaving a comment on a lesson or replying to an existing comment.

Let's Learn AdonisJS 5

Naming, Grouping, & Prefixing Routes

7 MIN READ
2 MONTHS AGO

In this lesson, we'll learn about naming routes and using groups with prefixes to structure our routes and reduce redundancies.

Watch on YouTube

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')

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

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')

So now we have routes with the names:

  • posts.index for GET: /posts

  • posts.show for GET: /posts/:id

  • posts.update for PUT: /posts/:id

  • posts.destroy for DELETE: /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')

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')

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 for GET: /posts

  • marketing.posts.show for GET: /posts/:id

Application Routes

  • app.posts.index for GET: /app/posts

  • app.posts.show for GET: /app/posts/:id

  • app.posts.store for POST: /app/posts

  • app.posts.update for PUT: /app/posts/:id

  • app.posts.destroy for DELETE: /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')

Now we have the following:

API Routes

  • api.v1.posts.index for GET: /api/v1/posts

  • api.v1.posts.show for GET: /api/v1/posts/:id

  • api.v1.posts.store for POST: /api/v1/posts

  • api.v1.posts.update for PUT: /api/v1/posts/:id

  • api.v1.posts.destroy for DELETE: /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.

Comment

Prepared By

Tom Gobich

Burlington, KY

Owner of Adocasts, JavaScript developer, educator, PlayStation gamer, burrito eater.

Visit Website