Let's start dipping our toes in the code now by creating some routes and controllers.
For anyone unaware of the term routing. In terms of web development, it is a mapping of URLs and their handlers that you want your app to handle. URLs outside of this mapping will result in a 404.
Defining routes
Routes in AdonisJS are defined inside the start/routes.ts
file. Using this file is a convention and not a technical limitation. Let's open the file and replace its contents with the following code snippet.
import Route from '@ioc:Adonis/Core/Route'
Route.get('/', async () => {
return 'This is the home page'
})
Route.get('/about', async () => {
return 'This is the about page'
})
Route.get('/projects', async () => {
return 'This is the page to list projects'
})
- We begin by importing the
Route
module. - Using the
Route.get
method, we define a total of 3 routes. - A typical route accepts a route pattern and a handler to respond to the requests.
- In the above example, the handler is an inline function. Later we will look into using controllers as well.
- Finally, the return value of the function is sent back to the client making the request.
Let's give this code a try by visiting the URLs for the registered routes.
Supported data types
You can return most of the Javascript data types from the route handler and AdonisJS will properly serialize them for you.
Route.get('/', async () => {
// return 28
// return new Date()
// return { hello: 'world' }
// return [1, 2, 3]
// return false
// return '<h1> Hello world </h1>'
})
HTTP Context
Every route handler receives an instance of the HTTP context as the first parameter. The context holds all the information related to the current request, along with the response
object to customize the HTTP response.
Route.get('/', async (ctx) => {
console.log(ctx.inspect())
return 'handled'
})
Following is the output of the ctx.inspect()
.
If you are coming from a framework like express, then there is no req
and res
objects in AdonisJS. Instead you have access to ctx.request
and ctx.response
.
Also note that the API of request
and response
is not compatible with express and neither it is a goal for us.
The HTTP context has an extendible API and many AdonisJS packages add their properties to the context. For example: If you install the @adonisjs/auth module, it will add the ctx.auth
property.
Using controllers
Controllers in AdonisJS are vanilla ES6 classes stored inside the app/Controllers/Http
directory. You can create a new controller by running the following ace command.
node ace make:controller TodoController
# CREATE: app/Controllers/Http/TodosController.ts
Let's open the newly created file and replace its contents with the following code snippet.
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class TodosController {
public async index(ctx: HttpContextContract) {
return 'Hello world from the todos controller'
}
}
How should we now go about using this controller inside our routes file?
Let's begin with zero magic and simply import the controller inside the routes file. Open the start/routes.ts
file and add another route using the controller.
import Route from '@ioc:Adonis/Core/Route'
import TodosController from 'App/Controllers/Http/TodosController'
Route.get('/todos', (ctx) => new TodosController().index(ctx))
Visit the http://localhost:3333/todos URL and you will surely see the return value from the controller method.
Lazy loading controllers
Now, imagine an app with 40-50 controllers. Every controller will also have its own set of imports, making the routes file a choke point.
Lazy loading is the perfect solution to the above problem. Instead of importing all the controllers at the top level, we can lazily import them within the route's handler.
import Route from '@ioc:Adonis/Core/Route'
Route.get('/todos', async (ctx) => {
const TodosController = (await import('App/Controllers/Http/TodosController'))
.default
return new TodosController().index(ctx)
})
Now, the TodosController
is only loaded when the request for the /todos
route comes in. Since the import/require statements are cached in Node.js, you don't have to worry about reading the same file multiple times from the disk.
Are are you happy with the above code?
I am personally not. There is too much boilerplate and you would expect a framework to do a better job here and cleanup things for you and AdonisJS does that.
Replace the previously written route with the following code snippet.
Route.get('/todos', 'TodosController.index')
This is the recommended way of referencing controllers within your routes file.
- We already know that your controllers are inside the
app/Controllers/Http
directory and hence there is no need to define the complete path. - You only need to define the file name and the method to be called on the exported class.
- Behind the scenes, AdonisJS will lazily import the controller. Creates an instance of it and executes the referenced method.
What about the type safety?
The verbose implementation has the extra benefit of being type safe. This is something missing when using the string based expression. Or I will say, it is missing for now.
We need two things to achieve type safety when referencing controller.method
as a string expression.
- The ability to tokenize the expression and create a full path to the controller and its method. This is achievable with Typescript 4.1 beta release. Here is a proof of concept for the same.
- Next is the ability to have an
Import
type with support for generics. There is an open issue for it and I am positive that it will make its way to the Typescript in the future, as it adheres to the Typescript design goals.
To summarize, we bet in the future of Typescript and decided to remove all the extra boilerplate required to reference controllers within the routes file and expose a simple to use API.
Wrap up
Alright, let's wrap up this post. In the next post, we will begin designing the web page for our todo app.
Meanwhile, lemme share some code examples for commonly required tasks that you may perform when creating a web app.
Render views
Render views using the AdonisJS template engine
Route.get('todos', async ({ view }) => {
return view.render('todos/index', {
todos: [{ id: 1, title: 'Writing an article', isCompleted: false }],
})
})
Modify outgoing response
Route.get('/', async ({ response }) => {
response.redirect('/to/a/url')
response.status(301)
response.header('x-powered-by', 'my-app-name')
response.cookie('foo', 'bar')
})
Stream files from the disk
Route.get('/', async ({ response }) => {
response.download('/path/to/some/file.txt')
})
Read request data
Route.get('/', async ({ request }) => {
console.log(request.url())
console.log(request.method())
console.log(request.cookie('cookie-name'))
// request body + query string
console.log(request.all())
// get a single file & validate it too
const avatar = request.file('avatar', {
size: '2mb',
extnames: ['jpg', 'png', 'jpeg'],
})
// All uploaded files as an object
console.log(request.allFiles())
})
Top comments (2)
Outdated
how to use imported routes.
user-routes.ts
admin-routes.ts
routes.ts -- default
import user, admin ................... routes to routes.ts
but how to use them in routes group in routes.ts file