Routing Introduction

We'll learn how to define routes in Adonis using the Route module. We'll cover defining routes for specific HTTP Methods (like GET, POST, PUT/PATCH, and DELETE), Caveats, and Brisk Routes.

Published
Sep 11, 21
Duration
6m 14s

Developer, dog lover, and burrito eater. Currently teaching AdonisJS, a fully featured NodeJS framework, and running Adocasts where I post new lessons weekly. Professionally, I work with JavaScript, .Net C#, and SQL Server.

Adocasts

Burlington, KY

Routes are how we can go about defining URLs that will be accessible via our application. So, in order to add a page to our application, we'd need to first define a route for that page. This route definition would register a URL for that page and tell our server what to do when that URL is requested.

The Default Route

By default, Adonis starts our route definitions within the start directory within a file called routes.ts. Out-of-the-box we're provided a single route definition for the welcome page we saw when we booted our application to ensure everything installed okay.

// start/routes.ts

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

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

Note, in the last lesson we changed this slightly to show how you can expand your HttpContext. I've undone those changes here.

So, What's Happening Here?

First, we're importing the Route module from @ioc:Adonis/Core/Route, which we'll use to define our routes. Then, we're calling a HTTP method called get off our Route module. This allows us to define a route specifically for get requests (viewing pages, getting info from an API, etc).

The first argument within our get method is the URL pattern we want to match for the route. So, by providing /, we're defining this route for the "home page". If we're running our server locally that's http://localhost:3333. If we were to define a route with a pattern of /test, that route would be accessible via http://localhost:3333/test.

The second argument is a callback function that describes what we want that route to do whenever the requested URL matches this route definition. As we covered in the last lesson, this callback function is provided an HttpContext object unique to a single individual request.

Defining A Route

So, if you have your application running, using the node ace serve --watch command, and head into the browser and visit http://localhost:3333 your request will match the default home page route we have defined. Additionally, you'll see the welcome page that the default home page route is handling our route with.

If we try to visit http://localhost:3333/test, since we don't have any routes defined matching the pattern of /test we're going to get a 404 error stating the page could not be found. So, let's define that route.

// start/routes.ts

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

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

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

Here we're defining a get request by using the get method off of the Route module. The pattern is /test, so we now have a route defined for http://localhost:3333/test. Lastly, our route handler, the callback function, is returning back a string of working.

So, if we now attempt to visit http://localhost:3333/test within our application our request will now match our /test route definition pattern and we'll get back a page displaying 'working'. Another thing to note is that we're actually getting back JSON here since we're returning a string directly from our route handler. The same would apply if we returned back an object, array, or number.

HTTP Methods

With Adonis, we're not just limited to defining get requests. Off of the Route module we also have access to methods for defining routes for post, put, patch, delete, and options requests as well.

Route.get('/posts',      () => 'listing posts')             // GET    http://localhost:3333/posts
Route.get('/posts/1',    () => 'get a single post')         // GET    http://localhost:3333/posts/1
Route.post('/posts',     () => 'creating a post')           // POST   http://localhost:3333/posts
Route.put('/posts/1',    () => 'updating a post')           // PUT    http://localhost:3333/posts/1
Route.patch('/posts/1',  () => 'partially updating a post') // PATCH  http://localhost:3333/posts/1
Route.delete('/posts/1', () => 'deleting a post')           // DELETE http://localhost:3333/posts/1

Here we have six different types of route definitions, each containing identical route patterns as one another. The reason we can share the same route pattern for each of these route definitions is due to the different HTTP methods assigned to the requests.

For example, Route.get('/posts', () => /* ... */) *and Route.post('/posts', () => /* ... */) both have the same pattern of /posts. However Route.get assigned the first specifically for GET requests while Route.post assigned the second specifically for POST requests.

HTTP Method Purposes

At this point, you might be asking, "what's the difference?". Great question! Let's run through what each of these different HTTP Methods are meant for.

  • GET (Route.get)
    Used to retrieve data from our server or requesting a page. Typically used to either request a page, a list of similar records, or a single record.

  • POST (Route.post)
    Used to send data to our server for creation, typically used to provide data for our server so it can store one or more new records, whether that be in a database or elsewhere.

  • PUT (Route.put) & PATCH (Route.patch)
    Used to send data to our server for mutation, typically used to provide data for our server so it can update one or more existing records, whether that be in a database or elsewhere. PUT is typically used for routes where we're going to be providing the entire record to be updated while PATCH is typically used when we're only going to be providing partial data to be updated. Typically, when updating a single record, that record has a unique identifier provided as a route parameter.

  • DELETE (Route.delete)
    Used to instruct our server to delete one or more records. Typically, when deleting a single record, that record has a unique identifier provided as a route parameter.

Regardless of the HTTP Method you're defining a route for, the arguments are the same. The first argument being the route pattern, and the second being the route handler callback function.

Any HTTP Method

What if we need to define a route where we don't care what the HTTP Method is? Say, maybe a catch-all route like a 404 page. We can make use of a method off of the Route module called any. This will define a route definition that doesn't care about the HTTP Method, it'll only check for a pattern match.

So, for example, we could replace all of the above post route definitions with the below. Of course, please note typically you'd use the HTTP Methods covered above to define routes this way, this is simply for demonstration purposes.

Route.any('/posts', ({ request, response }) => {
  const httpMethod = request.method()
  
  switch (httpMethod) {
    case 'GET':
      return 'listing posts'
    case 'POST':
      return 'creating a post'
  }

  return response.status(404)
})

Route.any('/posts/1', ({ request, response }) => {
  const httpMethod = request.method()
  
  switch (httpMethod) {
    case 'GET':
      return 'get a single post'
    case 'PUT':
      return 'updating a post'
    case 'PATCH':
      return 'partially updating a post'
    case 'DELETE':
      return 'deleting a post'
  }

  return response.status(404)
})

Here we're defining two routes /posts and /posts/1. Both routes grab their requested HTTP Method and perform a switch matching the ones they support. The first, GET and POST, and the second GET, PUT, PATCH, and DELETE. If the request isn't for one of the HTTP Methods in the switch statement the request will return a 404 response, designating that the page couldn't be found.

Don't Duplicate Route Definitions

One thing to keep in mind when defining your route definitions is that you don't want duplicate route definitions defined. When attempting to find a route definition for the incoming request, Adonis searches the route definitions for a match from the top down in the order they're defined.

So, if we have duplicate route definitions, matching both in HTTP Method and pattern, then the second definition will never be reached. Additionally, Adonis will throw an error when we boot our application to warn us of this.

So, for example:

Route.get('/posts', () => /* ... */)
Route.on('/posts',  () => /* ... */)

Since on matches all HTTP Methods, we have a duplicate route definition on get requests for /posts.

Route Shorthand / Brisk Routes

The Route module also comes with a method that allows us to define routes without defining a route handler. This shorthand can be used anytime you want to render a page that doesn't need any data, redirect to another page within your application, or redirect to a page outside of your application.

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

So, here we're defining a route that'll be accessible via http://localhost:3333/testing that, when requested, will render out our welcome page from within our resources/views directory.

Route.on('/testing').redirect('/')

Now instead of rendering our welcome page when http://localhost:3333/testing is requested we're going to redirect the user from http://localhost:3333/testing to our home page at http://localhost:3333.

Route.on('/blog/post/1').redirect('/posts/:id', { id: 1 }, 301)

The redirect method accepts up to three arguments, the first being the pattern to redirect to. The second is any route parameters needed for that route (we'll be covering route parameters in the next lesson). The third is a custom status code you can provide.

Route.on('/testing').redirectToUrl('https://duckduckgo.com')

Lastly, instead of either of the previous two, now when http://localhost:3333/testing is requested we're redirecting the user out of our application to DuckDuckGo.

Route.on('/testing').redirectToUrl('https://duckduckgo.com', 301)

The redirectToUrl method accepts up to two arguments, the first being the URL to redirect to. The second is a custom status code you can provide

Next Up

We've covered the different methods within the Route module that we can utilize to define routes for the different HTTP Methods for our defined route patterns. Which, are then handled as we define within our route handler.

In the next lesson, we'll be focusing specifically on the route pattern definition by covering route parameters. Route parameters are going to allow us to define dynamic parameters within our application's route definitions. So, we'll be able to replace the route pattern /posts/1, where 1 would represent a record's id, with /posts/:id where :id represents any record's id.

Join The Discussion! (3 Comments)

Please sign in or sign up for free to join in on the dicussion.

  1. Commented 1 year ago

    "… within our application our request will now match our /test route definition pattern and we'll get back a page displaying 'testing'…." displaying 'working', right?

    thanks for such a great tutorial!

    2

    Please sign in or sign up for free to reply

    1. Commented 1 year ago

      Yes, thank you! Lol, I normally do test and testing, so I threw myself off there.

      1

      Please sign in or sign up for free to reply

      1. Commented 1 year ago

        thank YOU!

        1

        Please sign in or sign up for free to reply

Playing Next Lesson In
seconds