Routes enable the outside world to interact with your app via URLs.

Routes are registered inside the start/routes.js file.

Basic Routing

The most basic route binding requires a URL and a closure:

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

The return value of the closure will be sent back to the client as a response.

You can also bind a route to a controller using a controller.method signature:

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

The above signature PostController.index refers to the App/Controllers/Http/PostController.js file’s index method.

Available Router Methods

Resourceful routes use different HTTP verbs to indicate the type of request:

Route.get(url, closure), closure)
Route.put(url, closure)
Route.patch(url, closure)
Route.delete(url, closure)

To register a route that responds to multiple verbs, use Route.route:

Route.route('/', () => {
}, ['GET', 'POST', 'PUT'])

To render a view directly (e.g. static pages), use Route.on.render:


In the example above, when the root / route is loaded, the resources/view/welcome.edge file is rendered directly.

Route Parameters

Required Parameters

For dynamic routes, you can define route parameters like so:

Route.get('posts/:id', ({ params }) => {
  return `Post ${}`

In the example above, :id is a route parameter.

Its value is then retrieved via the params object.

Optional Parameters

To define an optional route parameter, append a ? symbol to its definition:

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

  return `One ${drink}, coming right up!`

In the example above, :drink? is an optional route parameter.

Wildcard Route

You may want to render a single view from the server and handle routing using your preferred front-end framework:

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

Any specific routes need to be defined before your wildcard route:

Route.get('api/v1/users', closure)

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

Named Route

Though routes are defined inside the start/routes.js file, they are referenced everywhere else in the application (e.g. using the views route helper to make a URL for a given route).

By using the as() method, you can assign your route a unique name:

Route.get('users', closure).as('users.index')

This will enable you to use route helpers in your templates and code, like so:

<!-- before -->
<a href="/users">List of users</a>

<!-- after -->
<a href="{{ route('users.index') }}">List of users</a>
foo ({ response }) {
  return response.route('users.index')

Both route helpers share the same signature and accept an optional parameters object as their second argument:

Route.get('posts/:id', closure).as('')

route('', { id: 1 })

route helpers also accept a optional parameters object as third argument, it will handle protocol, domain and query options:

route('', { id: 1 }, {
  query: { foo: 'bar' }

// resulting in /post/1?foo=bar

// Without parameters:
route('auth.login', null, {
  domain: '',
  protocol: 'https',
  query: { redirect: '/dashboard' }

// resulting in

The same rules apply for the view.

<a href="{{ route('', { id: 1 }, {query: { foo: 'bar' }}) }}">Show post</a>
// href="/post/1?foo=bar"

Route Formats

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

A route format is a contract between the client and server for what type of response to return:

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

  if (request.format() === 'json') {
    return users

  return view.render('users.index', { users })

For the example above, the /users endpoint will be able to respond in multiple formats based on the URL:

GET /users.json     # Returns an array of users in JSON
GET /users          # Returns the view in HTML

You can also disable the default URL and force the client to define the format:

Route.get('users', closure).formats(['json', 'html'], true)

Passing true as the second argument ensures the client specifies one of the expected formats. Otherwise, a 404 error is thrown.

Route Resources

You will often create resourceful routes to do CRUD operations on a resource.

Route.resource assigns CRUD routes to a controller using a single line of code:

// This...
Route.resource('users', 'UserController')

// ...equates to this:
Route.get('users', 'UserController.index').as('users.index')'users', '').as('')
Route.get('users/create', 'UserController.create').as('users.create')
Route.get('users/:id', '').as('')
Route.put('users/:id', 'UserController.update').as('users.update')
Route.patch('users/:id', 'UserController.update')
Route.get('users/:id/edit', 'UserController.edit').as('users.edit')
Route.delete('users/:id', 'UserController.destroy').as('users.destroy')
This feature is only available when binding routes to a Controller.

You can also define nested resources:

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

Filtering Resources

You can limit the routes assigned by the Route.resource method by chaining one of the filter methods below.


Removes GET resource/create and GET resource/:id/edit routes:

Route.resource('users', 'UserController')


Keeps only the passed routes:

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


Keeps all routes except the passed routes:

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

Resource Middleware

You can attach middleware to any resource as you would with a single route:

Route.resource('users', 'UserController')

If you don’t want to attach middleware to all routes generated via Route.resource, you can customize this behavior by passing a Map to the middleware method:

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

In the example above, the auth middleware is only applied to the store, update and destroy routes.

Resource Formats

You can define response formats for resourceful routes via the formats method:

Route.resource('users', 'UserController')

Routing Domains

Your application may use multiple domains.

AdonisJs make it super easy to deal with this use-case.

Domains can be a static endpoint like, or a dynamic endpoint like

You can define the domain on a single route as well.
start/routes.js => {
  Route.get('/', ({ subdomains }) => {
    return `The username is ${subdomains.user}`

In the example above, if you visited, you would see The username is virk.

Route Groups

If your application routes share common logic/configuration, instead of redefining the configuration for each route, you can group them like so:

// Ungrouped
Route.get('api/v1/users', closure)'api/v1/users', closure)

// Grouped => {
  Route.get('users', closure)'users', closure)


Prefix all route URLs defined in the group:

start/routes.js => {
  Route.get('users', closure)   // GET /api/v1/users'users', closure)  // POST /api/v1/users


Assign one or many middleware to the route group:

start/routes.js => {
Group middleware executes before route middleware.


Prefix the namespace of the bound controller:

start/routes.js => {
  // Binds '/users' to 'App/Controllers/Http/Admin/UserController'
  Route.resource('/users', 'UserController')


Defines formats for all routes in the group:

start/routes.js => {
}).formats(['json', 'html'], true)


Specify which domain group routes belong to:

start/routes.js => {