In this lesson, we're going to be focusing on the basics of Adonis routing. So, in the last lesson, we learned that we have a start
directory that contains a routes.ts
file where our route definitions will reside. When we create a new project we're given a starter route that renders our welcome page.
import Route from '@ioc:Adonis/Core/Route' Route.on('/').render('welcome')
Copied!
Note that the example route is calling a method called on
, which is given a path pattern of /
. This on
method is a shorthand method that will allow us to quickly render a page without doing any business logic or additional handling.
Chained off the on
method is the render
method, which accepts a view name from our resource/views
directory. The provided view name will then be used to render that view for the defined path pattern.
The render
method is only chainable off on
, since on
is a shorthand route definition that doesn't allow route handling.
HTTP Methods
In most cases, you're going to have some business logic to perform for your routes. This is where HTTP Methods come into play. HTTP Methods allow us to define routes for particular actions.
GET - Used to fetch data or render a page.
POST - Used to store new data or dispatch data to the server.
PUT/PATCH - Used to update existing data.
DELETE - Used to delete existing data.
Using these HTTP Methods when defining our routes allows us to keep our route paths clean and concise because we can utilize the same path for each method. We can have a route for GET /users
, POST /users
, PUT /users
, and DELETE /users
and the server will understand which route to use based on the HTTP Method the request was sent with.
HTTP Methods Route Definitions
With Adonis, we have access to define the HTTP Method for our route directly off the Route module. Unlike the on
method, the HTTP Methods all require some form of route handler for each route. For example, if we wanted to replicate the default welcome route using the get
method, we can do so like this:
import Route from '@ioc:Adonis/Core/Route' import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' Route.get('/', async ({ view }: HttpContextContract) => { return view.render('welcome') })
Copied!
The first argument is still the path pattern, but instead of chaining render
off get
we instead pass in our route handler callback function. Our callback function is given an argument of type HttpContextContract
, which contains properties like view, request, response, params, etc. We can use the view property to then access the render
method.
This is the structure for all our route HTTP Methods; get, post, put, patch, and delete.
Dynamic Routing with Route Parameters
Dynamic routes allow us to use the same route for different data, think a blog post or user profile page. We can define a dynamic route in Adonis by adding a colon before our route parameter name. For example, if we want to render a user profile page, we could define a route with a route parameter of the user's id. We can then use the params property off our HttpContextContract
to access our route parameter.
Route.get('/users/:id', async ({ view, params }: HttpContextContract) => { const userId = params.id; const user = // TODO: get the user from the db return view.render('profile', { user }) })
Copied!
Note here that the route parameter name (/:id
) and the key the value is stored within on the params
object is a one-to-one relationship. They'll always match.
Optional Route Parameter
We can make the route parameter optional by adding a question mark behind it. If we have a route parameter value the value will be on the params object. If we don't have a parameter value the value won't be on the params object.
Route.get('/users/:id?', async ({ view, params }: HttpContextContract) => { const userId = params.id; let user = null; if (userId) { user = // TODO: get the user from the db } return view.render('profile', { user }) })
Copied!
By making the route parameter id
optional this route will handle requests for both /users
and /users/1
, where 1 is a user's id.
Naming Routes
Naming routes is a powerful tool that allows us to reference a route without statically mentioning its path. To give a route a name all we need to do is chain the as
method of the route and provide the as method a name string.
Route.get('/users', async ({ view }) => {/* */}).as('users'); Route.get('/users/:id', async ({ view }) => {/* */}).as('users.show');
Copied!
A route's name can be formatted to your liking; snake-case, camel-case, etc. The dot syntax is something I've personally always used.
Sending A JSON Response
So, we know we can use view.render('name')
to render out a view, and sending a JSON response isn't much different. Instead of the view
property, we'll want to use the response
property and on the response
property, we have access to both a json
and jsonp
method. Using one of these two methods we can either send a JSON or JSONP response.
Route.get('/', async ({ response }: HttpContextContract) => { return response.json({ hello: 'world' }); })
Copied!
Grouping
Let's say we have a number of routes whose paths all start the exact same, like user resource routes.
Route.get('/users', async ({ view }: HttpContextContract) => { return view.render('users') }).as('users.index') Route.get('/users/:id', async ({ view, params }: HttpContextContract) => { return view.render('profile', {}) }).as('users.show') Route.post('/users', async ({ request, response }: HttpContextContract) => { }).as('users.post') Route.put('/users/:id', async ({ request, response }: HttpContextContract) => { }).as('users.update') Route.delete('/users/:id', async ({ request, response }: HttpContextContract) => { }).as('users.delete')
Copied!
It can become pretty repetitive to constantly repeat the shared definitions over and over, especially if we have a lot of routes. To simplify this we can use route grouping to group like-routes. A group can be used to apply a name prefix, a route prefix, middleware, subdomain, and even namespace to a number of routes at the same time.
For example, we can simplify the above to just:
Route.group(() => { Route.get('/', async ({ view }: HttpContextContract) => { return view.render('users') }).as('index') Route.get('/:id', async ({ view, params }: HttpContextContract) => { return view.render('profile', {}) }).as('show') Route.post('/', async ({ request, response }: HttpContextContract) => { }).as('post') Route.put('/:id', async ({ request, response }: HttpContextContract) => { }).as('update') Route.delete('/:id', async ({ request, response }: HttpContextContract) => { }).as('delete') }).prefix('/users').as('users.')
Copied!
Here we're prefixing all routes within the group with the path prefix of /users
and name prefix of users.
.
Order Matters
When defining our routes it's important to keep in the back of our minds that the order of our routes does matter. Adonis will use the first route definition, from the top down, it finds that matches the current request.
This most notably comes into play when using optional route parameters, since the route can also be used for the path with the parameter omitted.
For example, if we make a request for the page at /users
with the below route definitions our request will stop at the /users/:id?
definition since the route parameter is optional. It's the first defined route that matches our request, so it's the one that'll be used.
Route.get('/users/:id?', async ({ view, params }: HttpContextContract) => { return view.render('profile', {}) }).as('users.show') Route.get('/users', async ({ view }: HttpContextContract) => { return view.render('users') }).as('users.index')
Copied!
Next Up
Right now you might be thinking, "gosh, defining and handling all my routes in this routes.ts
file is going to get massive!" Fear not, in the next lesson we'll be massively simplifying our routes.ts
file by extracting our route definitions out into Controllers. Using Controllers, our route definitions will shrink down to about a single line per-definition.
Join The Discussion! (0 Comments)
Please sign in or sign up for free to join in on the dicussion.
Be the first to Comment!