Routing

AdonisJs routing favors REST conventions, but still, give enough room to create and register custom routes. In this guide, we learn about creating routes, binding controller methods and different ways to organize them.

All of the application routes are registered inside start/routes.js file. Also, you are free to create multiple files for routes and just require them inside the routes.js file.

Basic example

The most basic route requires a URL path and closure to be executed. The returned value from the closure is sent back as the response.

Route.get('/', () => 'Hello world')

After starting the server using adonis serve --dev, if you visit localhost:3333, you see the Hello world in the browser.

Route methods/verbs

Restful routes make use of different HTTP methods to indicate the type of request. For example: POST method is used to create a record and GET is used to fetch record(s).

You can define routes for different HTTP methods using one of the following methods.

get

Create a route with GET method.

Route.get('/', async () => {
})

post

Create a route with POST method.

Route.post('/', async () => {
})

put/patch

The PUT and PATCH methods are used to update a resource.

Route.put('/', async () => {
})

Route.patch('/', async () => {
})

delete

The DELETE method indicates removing a resource.

Route.delete('/', async () => {
})

route

Also, you can create routes that respond to multiple verbs using the Route.route method.

Route.route('/', async () => {

}, ['GET', 'POST'])

Route parameters

Creating routes with static paths are not quite helpful, and often you need a way to accept dynamic data as part of the URL, for example:

/posts/1
/posts/2
/posts/300

You would need a way to fetch the post id from the URL and render the appropriate post. The same is achieved by defining route parameters.

Route.get('posts/:id', async ({ params }) => {
  const post = await Post.find(params.id)
  return post
})

The :id is a route parameter which is passed as part of the params object. Also, you can make it optional by adding ? to the parameter. For example:

Route.get('make/:drink?', async ({ params }) => {
  // use Coffee as fallback when drink is not defined
  const drink = params.drink || 'Coffee'

  return `Will make ${drink} for you`
})

Routing for SPA’s

Quite often you find yourself creating a SPA ( single page application ), where you want to render a single view from the server and handle routing on front-end using your favorite front end framework.

This can be done by defining a wildcard route.

Route.any('*', ({ view }) => view.render('main'))

If you have any other specific routes, they are be defined before the wildcard route. For example:

Route.get('/api/v1/users', 'UserController.index')

// wildcard route
Route.any('*', ({ view }) => view.render('main'))

Binding controllers

Defining closures as the route actions are not scalable, since writing all the code inside a single file is never desired and neither practical.

AdonisJs being an MVC framework offers a nice abstractions layer called Controllers to keep all the request handling logic inside custom ES6 classes.

Let’s create a controller using the make:controller command.

adonis make:controller Posts
Output
✔ create  app/Controllers/Http/PostController.js

The next step is to bind the controller method to the route. It is defined as a dot (.) separated string.

Route.get('posts', 'PostController.index')

Finally, we need to create the index method on the controller class.

'use strict'

class PostController {
  index () {
    return 'Dummy response'
  }
}

module.exports = PostController

Route middleware

You can apply selected middleware to routes by calling the middleware method.

Route
  .get('profile', 'UserController.profile')
  .middleware(['auth'])

The middleware method accepts an array of named middleware, which is defined inside start/kernel.js file.

start/kernel.js
const namedMiddleware = {
  auth: 'Adonis/Middleware/Auth'
}

Click here to learn more about middleware.

Named routes

Routes are defined inside start/routes.js file but referenced everywhere inside your application. For example: Defining a form action to submit to a particular URL.

Route.post('users', 'UserController.store')

Inside the template

<form method="POST" action="/users">
</form>

Now if you change your route path from /users to something else, you have to remember to come back and change it inside the template as-well.

To overcome this problem, you can name your routes uniquely and reference them in other part of the program.

Route
  .post('users', 'UserController.store')
  .as('storeUser')

The as method gives your route a name. Now inside your template, you can reference it using a view global.

<form method="POST" action="{{ route('storeUser') }}">
</form>

Route formats

Route formats open up a new way for Content negotiation, where you can accept the response format as part of the URL.

Route format is a contract between the client and the server on which type of response to be created. For example:

Route
  .get('users', async ({ request, view }) => {
    const users = await User.all()

    if (request.format() === 'json') {
      return users
    } else {
      return view.render('users.list', { users })
    }

  })
  .formats(['json'])

Now the users endpoint can respond in multiple formats, based upon the URL.

/users.json

Return an array of users in JSON.

/users

Render the view and returns HTML

Also, you can disable the default URL and always force the client to define the format.

Route
  .get('/', async ({ request, view }) => {
    const users = await User.all()
    const format = request.format()

    switch (format) {
      case 'html':
        return view.render('users.list', { users })
      case 'json':
        return users
    }
  })
  .formats(['json', 'html'], true)

Passing true as the second parameter makes sure that the client defines one of the expected formats. Otherwise, a 404 is thrown.

Route resources

If you like building web apps around REST conventions then route resources helps you in defining conventional routes by writing less code.

It is required to bind a Controller to the resource. Binding a closure throws an exception.
Route.resource('users', 'UsersController')

The Route.resource method under the hood creates a total of 7 routes

Url Verb Name Controller Method

users Show a list of all the users

GET

users.index

UsersController.index

users/create Render a form to be used for creating a new user

GET

users.create

UsersController.create

users Create/save a new user.

POST

users.store

UsersController.store

users/:id Display a single user

GET

users.show

UsersController.show.

users/:id/edit Render a form to update an existing user.

GET

users.edit

UsersController.edit

users/:id Update user details.

PUT or PATCH

users.update

UsersController.update

users/:id Delete a user with id.

DELETE

users.destroy

UsersController.destroy

Nested resources can be created with dot (.) notation.

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

Filtering resources

You can limit the number of routes a resource should create by chaining handful of methods.

apiOnly

Limit the routes to only 5 by removing users/create and users/:id/edit. Since when writing an API server, you may want to render the forms within the API consumer (e.g., a mobile app, frontend web framework, etc.).

Route
  .resource('users', 'UsersController')
  .apiOnly()

only([names])

Remove all other routes but not the ones passed to the only method.

Route
  .resource('users', 'UsersController')
  .only(['index', 'show'])

except([names])

Remove route for names passed to the except method.

Route
  .resource('users', 'UsersController')
  .except(['index', 'show'])

Resource Middleware

You can attach middleware to the resource, just like you would do to a single route.

Route
  .resource('users', 'UsersController')
  .middleware(['auth'])

Since attaching auth middleware to all the routes is not always desired, you can customize the behavior by passing a map of values.

Route
  .resource('users', 'UsersController')
  .middleware(new Map([
    ['auth'], ['users.store', 'users.update', 'users.delete']
  ]))

Here we have defined the auth middleware on store, update and delete routes.

Resource Formats

Also, you can define the formats for all the resourceful routes, just like the way you do for a single route or a group of routes.

Route
  .resource('users', 'UsersController')
  .formats(['json'])

Routing domains

AdonisJs makes it super easy to serve multiple domains within a single codebase. The domains can be static endpoints like blog.adonisjs.com or dynamic endpoints like :user.adonisjs.com.

You can define the domain on a single route as well, but it is a good idea to group domain specific routes.
Route
  .group(() => {
    Route.get('users', async ({ subdomains }) => {
      return `The username is ${subdomains.user}`
    })
  })
  .domain(':user.myapp.com')

Now if you visit virk.myapp.com, You see the above route is executed.

Route groups

Quite often your application routes share common logic/configuration around them. So instead of re-defining the configuration on each route, it’s better to group them. For example:

Not desired

Route.get('/api/v1/users', 'UserController.index')
Route.post('/api/v1/users', 'UserController.store')

Instead, we can make use of the route’s group here.

Route
  .group(() => {
    Route.get('users', 'UserController.index')
    Route.post('users', 'UserController.store')
  })
  .prefix('api/v1')

Just like the prefix method, you can call the following methods on a group of routes.

middleware

Define middleware on a group of routes. All group middleware are executed before the middleware defined on a single route.

Route
  .group(() => {
  })
  .middleware(['auth'])

domain

Specify routes on a group of routes.

Route
  .group(() => {
  })
  .domain('blog.adonisjs.com')

formats

Define formats on a group of routes.

Route
  .group(() => {
  })
  .formats(['json', 'html'])

Route Renderer

Route renderer is a one liner to render a view without creating a controller method or binding a closure.

Instead of following

Route.get('/', async function ({ view }) {
  return view.render('welcome')
})

We can write

Route.on('/').render('welcome')