Route.get('/', () => 'Hello world')
You are viewing the legacy version of AdonisJS. Visit https://adonisjs.com for newer docs. This version will receive security patches until the end of 2021.
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.
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.
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.
The PUT
and PATCH
methods are used to update a resource.
Route.put('/', async () => {
})
Route.patch('/', async () => {
})
Also, you can create routes that respond to multiple verbs using the Route.route
method.
Route.route('/', async () => {
}, ['GET', 'POST'])
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`
})
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 should be defined before the wildcard route. For example:
Route.get('/api/v1/users', 'UserController.index')
// wildcard route
Route.any('*', ({ view }) => view.render('main'))
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
✔ 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
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.
const namedMiddleware = {
auth: 'Adonis/Middleware/Auth'
}
Click here to learn more about middleware.
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 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.
Return an array of users in JSON.
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.
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 |
|
users/create Render a form to be used for creating a new user |
GET |
users.create |
|
users Create/save a new user. |
POST |
users.store |
|
users/:id Display a single user |
GET |
users.show |
|
users/:id/edit Render a form to update an existing user. |
GET |
users.edit |
|
users/:id Update user details. |
PUT or PATCH |
users.update |
|
users/:id Delete a user with id. |
DELETE |
users.destroy |
|
Nested resources can be created with dot (.)
notation.
Route.resource('posts.comments', 'CommentsController')
You can limit the number of routes a resource should create by chaining handful of methods.
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()
Remove all other routes but not the ones passed to the only
method.
Route
.resource('users', 'UsersController')
.only(['index', 'show'])
Remove route for names passed to the except
method.
Route
.resource('users', 'UsersController')
.except(['index', 'show'])
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([
[['users.store', 'users.update', 'users.delete'], ['auth']]
]))
Here we have defined the auth
middleware on store, update and delete routes.
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'])
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.
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.
Define middleware on a group of routes. All group middleware are executed before the middleware defined on a single route.
Route
.group(() => {
})
.middleware(['auth'])
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')