Let's Learn Adonis 5 Routing Lesson 2.4

Multi-File Route Grouping Strategies

tomgobich avatar
tomgobich
4 min
Quick Summary

In this lesson, we'll discuss strategies you can use to apply multiple files worth of route definitions into a single route group without redefining the route group in each file.

I received a great question on the last lesson covering route groups and I wanted to answer it for everyone before moving forward. It's in regard to having routes split among several different files.

The question was if I have a route group I want to apply to more than one file, do I need to redefine the group within each file? In other words, for the below file structure, in order to have a route group for /api/v1 do you need to define the group in both the posts.ts file and the series.ts file?

start/
├─ routes/
│  ├─ api/
│  │  ├─ v1/
│  │  │  ├─ posts.ts
│  │  │  ├─ series.ts
├─ routes.ts

That would be a pain! Thankfully, the answer is no, you don't. So long as the code defining a route definition executes within a route group, that route definition will be created as a member of the group.

For example, we can wrap any set of routes within a function then call the function inside two different groups. The result will be the routes defined in the function will be defined twice, once for each group.

function postRoutes() {
  Route.get('/posts', () => 'get all posts')
}

Route.group(() => {
  // this will define a route for GET: /accounts/:accountId/posts
  postRoutes()
}).prefix('/accounts/:accountId')

Route.group(() => {
  // this will define a route for GET: /hub/posts
  postRoutes()
}).prefix('/hub')

We can then use this same methodology to our advantage when it comes to defining routes to a single group from multiple files.

Using A Function

First, let's take the above example and apply it to our multi-file scenario. We can wrap the routes we have defined within our posts.ts file and series.ts file in a function that we export. Then, we can import those functions and execute them within our group, like the below.

// start/routes/api/v1/posts.ts

import Route from '@ioc:Adonis/Core/Route'

export default function postRoutes() {
  Route.group(() => {
    Route.get('/', () => 'get all posts').as('index')
  }).prefix('/posts').as('posts')
}
// start/routes/api/v1/series.ts

import Route from '@ioc:Adonis/Core/Route'

export default function postRoutes() {
  Route.group(() => {
    Route.get('/', () => 'get all series').as('index')
  }).prefix('/series').as('series')
}
// start/routes.ts

import Route from '@ioc:Adonis/Core/Route'
import postRoutes from './routes/api/v1/posts'
import seriesRoutes from './routes/api/v1/series'

Route.group(() => {
  postRoutes()
  seriesRoutes()
}).prefix('/api/v1').as('api.v1')

Since the code registering the post and series routes is executed within our /api/v1 route group, the group is applied both. So, our routes end up looking like this:

/api/v1/posts    AS api.v1.posts.index
/api/v1/series   AS api.v1.series.index

Using this knowledge you can also extract the /api/v1 group into its own file within /start/routes/api as well if you'd wish, then you can import it within routes.ts the same way we are our post and series routes.

Importing Using Require

The second option we'll be looking at is importing using require. By using require we can import our code directly where we need it to execute, eliminating the need to wrap everything in an additional function the way we are with the first approach we looked at.

So, with the same structure we used with our function approach, let's see how it'd look using require.

// start/routes/api/v1/posts.ts

import Route from '@ioc:Adonis/Core/Route'

Route.group(() => {
  Route.get('/', () => 'get all posts').as('index')
}).prefix('/posts').as('posts')
// start/routes/api/v1/series.ts

import Route from '@ioc:Adonis/Core/Route'

Route.group(() => {
  Route.get('/', () => 'get all series').as('index')
}).prefix('/series').as('series')
// start/routes.ts

import Route from '@ioc:Adonis/Core/Route'

Route.group(() => {
  require('./routes/api/v1/posts')
  require('./routes/api/v1/series')
}).prefix('/api/v1').as('api.v1')

The first thing you'll notice is our post and series files no longer are wrapped with an exported function. Since we're importing using require, we no longer need a way to put off the execution of this code.

The next thing you'll notice is that we're directly using require inside of our group.

Apart from that, the file structure and the files themselves look very similar. The end-result routes defined are the exact same as well.

/api/v1/posts    AS api.v1.posts.index
/api/v1/series   AS api.v1.series.index

Moving The API Group To Routes/API

If you'd like to move the /api/v1 group into a file within the /start/routes/api directory you can absolutely do that using this approach as well. There's one thing to keep in mind, though. If your routes.ts file isn't just import statements, then you'll need to take into consideration how you import from ./routes/api. If the precedence of your routes allows you to import using import ‘./pathhere’ you can do that. Otherwise, you may want to use require directly where you need the routes imported.

So, let's say you created a file at /start/routes/api/index.ts and move your /api/v1 route group definition into that file.

// start/routes/api/index.ts

import Route from '@ioc:Adonis/Core/Route'

Route.group(() => {
  require('./v1/posts')
  require('./v1/series')
}).prefix('/api/v1').as('api.v1')

You can import it using import inside your routes.ts if this works with the precedence order of your other route definitions. Remember, a request will stop and use the first route definition matching the requested url.

// start/routes.ts

// ... other imports

import './routes/api'

// ... other imports / routes

If using import will cause problems with your route precedence, then you can use an IIFE.

// ... other imports / routes

require('./routes/api')

// ... other routes

Next Up

So, we've covered a couple of ways you can share a group definition with multiple files without redefining a group. This cuts back on redundancies and also gives you more control over the other of your routes as opposed to defining your groups over again in each file. In the next lesson, we'll get back to our regularly scheduled lessons by covering how to generate route URLs.


Corrections

Thanks to Arthur Emanuel Rezende for correcting me about require being synchronous instead of asynchronous. I had a mental lapse there haha :)