AdonisJS Tutorial: The Ultimate Guide (2023)

This is the most comprehensive tutorial on the AdonisJS framework online.

In this AdonisJS tutorial, you will learn AdonisJS 5 from scratch to an advanced level.

You will learn how to build and deploy your first AdonisJS app.

AdonisJS Tutorial: The Ultimate  Guide (2023)

Chapter 1: Complete AdonisJS Overview

In this Adonis JS tutorial, you will learn a very interesting framework in the Node.js and JavaScript ecosystem.

This AdonisJS tutorial will teach you AdonisJS 5 from scratch to an advanced level, you will learn how to build and deploy your first AdonisJS application.

Before we delve in, if you’re a backend developer or looking at delving into this career path, join other developers to receive daily articles on backend development that will boost your productivity.

What is AdonisJS?

AdonisJS is a Node.js framework that is focused on developers’ ergonomics, stability, and speed. AdonisJS is written from the ground up with a strong principle and goals in mind to be a strong integrated system.

It also follows the same MVC principle used by many popular frameworks such as Laravel, Rails, and Spring. It focuses on developer experience, stability, and speed.

If you’re coming from Laravel or Rails, then you will definitely find AdonisJS very easy to navigate.

In fact, I started using AdonisJS a day after looking at my client’s project codebase from the previous developer without any AdonisJS tutorial. 

As a complete web framework, AdonisJS comes inbuilt with great features and addons that make it different from other great Node.js frameworks.

Let’s dive into them:

Features of AdonisJS

AdonisJS 5 has many great features of web development built in such as:

Database

AdonisJS has a well thought and robust ORM. It comes with Query builder, migrations and active record models.

It also support a lot more such as:

SQL First Design:

AdonisJS treats SQL as a first-class citizen and supports all the mainstream SQL servers such as MySQL, PostgreSQL, MSSQL, etc.

Lucid Active Record ORM:

AdonisJS supports a great ORM that is inspired by Laravel Eloquent and Rails Active Record. It offers a great API for consuming complex SQL queries and managing relationships.

Migrations, Seeds, and Factories:

The feature should be familiar to you already, and Yes! Adonis supports it too. 

HTTP

AdonisJS has one of the most advanced Routing System in the Node.js ecosystem, it comes with built-in Route Groups, Subdomains, pattern matching, and resourceful routes.

It also supports a lot more such as:

Form Validator:

Since AdonisJS is TypeScript’s first framework, AdonisJS creates and exposes runtime validations in the request body and extras the static types at the same time. 

JSON Serializers:

AdonisJS is the first Node.js framework to support JSON: API first hand. If your project requires JSON: API then AdonisJS has you covered already.

Security

AdonisJS 5 has some Web Security best practices built into the framework, such as CRSF Protection, web shield for common attacks such as XSS, ClickJacking, Script Injection, and many more.

Another great thing is that AdonisJS 5 allows you to manage the security settings for Cross-Origin HTTP Request (CORs) to make sure the right and authorized users are allowed to access the application.

Auth

AdonisJS 5 provides inbuilt authentication using the Multi Driver Auth that gives developers the flexibility of defining different authentication drivers such as Sessions, Opaque token, and basic authentication. 

It also provides InBuilt RBAC which is a Role-Based Access Control great for handling authorization.

There are other great features of AdonisJS 5 such as Health Check which checks and monitors application well-being and can be configured to report to Kubernetes etc.

Why you should learn AdonisJS

Okay!

I know these questions pop into your head a lot, is it not just another Node.js framework? And why should I even bother?

Here is why:

Firstly, there are many reasons why you should learn a particular framework or not from your own perspective.

I will only point out a few general reasons and also point out my personal reasons and experiences with AdonisJS and other Node.js frameworks.

First of all, AdonisJS follows the standards used by most popular frameworks such as Laravel and Ruby on Rails. So if you’re coming from these frameworks to the JavaScript, TypeScript, or Node.js world then you can likely get started with it in a day.

Building Rapid Applications and Developers’ ergonomics is a top focus for the Adonis team, so if your project or team cares more about these then AdonisJS is your top choice.

Super small and unopinionated frameworks like Koa or Express are great in the Node.js ecosystem, but if you have ever benefited or in need of a full-stack framework like Laravel, Rails in Node.js then AdonisJS is your top choice.

If you care more about writing Integrated Systems rather than writing Glue Codes then you need to consider choosing AdonisJS in your next project.

Lastly, the syntax is super easy and adaptable as it supports TypeScript as a first-class citizen. It can strengthen newcomers to TypeScript to pick up real quick, also it supports JavaScript too.

Obviously, those are great reasons to choose or learn AdonisJS as your next Node.js framework or build your next project with it but the choice is yours.

Next, we are going to compare AdonisJS with other Node.js Frameworks in this tutorial to give you a clear picture of where AdonisJS can come into your project.

AdonisJS Framework vs Other Frameworks

Comparing AdonisJS with other Node.js frameworks such as adonis js vs nestjs, adonis js vs expressjs, adonisjs vs koa etc, with this video from Chimezie Enyinnaya of Adonis Mastery shows how great AdonisJS is compared to other frameworks.

Now you have completed the overview of the framework, let’s dive into the framework itself.

When it comes to learning and mastering AdonisJS, this course Learn AdonisJs: from zero to deploy is my top recommendation. You will Learn Adonis Js by building a production-ready application completely from scratch.

Take a break and subscribe to get access to our free NodeJS tips that will improve your productivity.

Chapter 2: AdonisJS The Framework

In this chapter, we will discuss a little in-depth about the AdonisJS framework in this tutorial.

In this Adonis JS tutorial, we will discuss the structure of the framework, how it interacts with other components of the framework and how to understand the framework from a beginner’s point of view.

As with other frameworks like Laravel, AdonisJS also uses the MVC architectural pattern, and it’s very easy to understand.

I have outlined all you need to know about MVC architectural patterns when I wrote Laravel Framework: The Ultimate Guide or you can check out Wikipedia.

One thing you need to keep at the back of your mind is that AdonisJS is a complete web framework that is built with lots of tooling to handle web HTTP requests.

So what is HTTP requests?

When you type in a web address (www.masteringbackend.com) into your web browser address bar and hit the enter button, you’re simply sending an HTTP request to the server and that is what AdonisJS is built to handle, that HTTP request.

Here, you will get a clear view of how your HTTP requests are handled and how other pieces of the framework work together to achieve it.

Let’s state a simple analogy to aid our understanding:

When you send that HTTP request, it goes to our web server and AdonisJS gets to it via the Router, then from Router to our Controllers, then from Controllers to Models, and every other part of AdonisJS, then the request returns back to you as a Response.

The Response could be displaying a web page or displaying a JSON object.

Aside from Controllers, Models, and Views which you should already understand by reading this article.

AdonisJS uses other components to handle your HTTP requests perfectly before sending out Response back to you viz:

Router

Routes are the starting point of your applications, it defines all the URLs or endpoints in the application and also points each request coming to the URL to the appropriate Controllers to handle the request.

The Router can sometimes define the type of response to be sent back to the client.

You can start by defining the URLs inside start/routes.ts file.

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

Route.get('/', async () => {
  return 'Home page'
})

Route.get('/about', async () => {
  return 'About page'
})

Middleware

A middleware has different definitions based on different functionalities and different usage.

But in AdonisJS, a middleware is a function that is executed before the HTTP request reaches the route handler.

Remember when I said that your HTTP request gets to the Router first then to Controller, etc.

You can place a middleware before a request reaches the Router or even before it reaches the Controller to perform different tasks.

You middleware can be set up to perform task such as:

  • Checks whether a user is logged in or not.
  • Finds specific information of a User.
  • A middleware to log HTTP requests.
  • A middleware to transform a Request body
  • And many different custom middlewares you will create.

Let’s take a look at this Middleware that checks if a user is authenticated or not.


import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

export class AuthMiddleware {
  public async handle ({ session, response }: HttpContextContract, next) {
    if (!session.userId) {
      return response.status(401).send('Unauthorized')
    }

    await next()
  }
}

You can learn more about middleware here

Now that we understand the inner workings of the Framework from the tutorial above, let’s start building our first project following the structure.

When it comes to learning and mastering AdonisJS, this course Learn AdonisJs: from zero to deploy is my top recommendation. You will Learn AdonisJs by building a production-ready application completely from scratch.

Take a break and subscribe to get access to our free NODEJS TIPS that will improve your productivity.

Chapter 3: Building an App with AdonisJS

Now that we understand how the framework works internally, let’s move on to building your first application using the AdonisJS framework.

If you’re just starting out creating applications with your computer, you need to install Node.js. You can read through how to install and set them up properly here.

We are going to demonstrate how to use AdonisJS to create a simple Todo application.

In this AdonisJS tutorial, we are going to explore how to set up AdonisJS, how to explore AdonisJS Requests and Responses, we will also discuss the Controller component and how it interacts with Models and Views.

Setting up AdonisJS

A lot has changed with AdonisJS since the release of AdonisJS 5, but since we are probably starting out with AdonisJS, we will only focus on AdonisJS 5.

But you can review the previous version here.

It is worth noting that as of the time of writing this tutorial, AdonisJS 5 is currently at the preview stage.

AdonisJS requires Node.js 12.x.x and NPM 6.x.x, so you should check the version of your Node.js to make sure it corresponds with the requirement.

Now you can create a new AdonisJS 5 project by simply running this command.


npm init adonis-ts-app adonisjs-todo

//or Yarn

yarn create adonis-ts-app adonisjs-todo

If you’re asked to choose the type of web application, select Web application and press Enter on the other options.

The Web application comes with @adonisjs/view, @adonisjs/session and @adonisjs/shield which is essentially what a traditional web application should have.

I will develop a complete tutorial on Building a RESTful API with AdonisJS or you can take the Node. Js: REST APIs Development with AdonisJs course.

Next, change your directory to the current adonisjs-todo folder you just created and open it with any code editor of your choice (Using VSCode).

cd adonisjs-todo

code .

Lastly, start your development server to see your first AdonisJS web application.

node ace serve --watch

The serve command just starts our HTTP server and perform all the conversion and compilation of TypeScript to JavaScript.

Additionally, the --watch flag simply watches our files for changes.

Visit http://127.0.0.1:3333 in your browser to test your newly created AdonisJS project.

If you see this page:

Screenshot-from-2021-02-05-14-50-55-1.png

Cheers!

Setting up Database

Next, we are going to configure our database to communicate with our AdonisJS project.

Setting up a database in AdonisJS is a little different from a traditional way of doing it in other frameworks.

Firstly, you need to install a package called Lucid, which is a powerful ORM used by AdonisJS.

npm i @adonisjs/lucid@alpha

// Yarn

yarn add @adonisjs/lucid@alpha

Next, you need to run the invoke command and choose your database type to set up Lucid and default configurations.

node ace invoke @adonisjs/lucid

For set up instructions, choose any of one and it will be generated and preview for you.

Next, create a new database using any Database Client of your choice and note the login credentials.

Next, open the .env file (or create a new one if not exists), update the following information.

DB_CONNECTION=mysql
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_USER= //DB_USER
MYSQL_PASSWORD= //DB_PASSWORD
MYSQL_DB_NAME= //DB_NAME

You can always go to config/database.ts to configure some credentials, for this article, we will stick with the defaults.

Setting up Schema

The next step is to set up your database schemas and create the different database relationships that are required.

Let’s see how we can achieve this easily with Lucid:

To create our first schema for Users, we will run the following command.

node ace make:migration users

The command will generate a schema file inside database/migrations, let’s open it and add the following columns to it.

import BaseSchema from "@ioc:Adonis/Lucid/Schema";
export default class Users extends BaseSchema {
  protected tableName = "users";
  public async up() {
    this.schema.createTable(this.tableName, (table) => {
      table.increments("id");
      table.string("email").unique().notNullable();
      table.string("password").notNullable();
      table.string('remember_me_token').nullable()
      table.timestamps(true);
    });
  }
  public async down() {
    this.schema.dropTable(this.tableName);
  }
}

We will repeat the same for the Todo schema and add the following columns to it too.

import BaseSchema from "@ioc:Adonis/Lucid/Schema";

export default class Todos extends BaseSchema {
  protected tableName = "todos";
  public async up() {
    this.schema.createTable(this.tableName, (table) => {
      table.increments("id");
      table.string("title");
      table.text("desc").nullable();
      table.integer("status").defaultTo(0);
      table.integer("user_id");
      table.timestamps(true);
    });
  }
  public async down() {
    this.schema.dropTable(this.tableName);
  }
}

Lastly, before you migrate the database, note that you can set up Database Seeders to generate fake data for your database, or clone my repository since I have configured database seeders already.

Now, stop the server and start it again before running the migration:

node ace serve --watch

// Then

node ace migration:run

Set up User Authentication

AdonisJS comes with a full-fledged authentication system using either basic, token, or traditional sessions, but unlike other frameworks, AdonisJS allows the developer to create and customize the register and login pages using any stack of their choices.

Let’s dive into how to set up authentication with AdonisJS:

Install Auth Package

Firstly, running any the command to install the Auth package.

npm i @adonisjs/auth@alpha

// Or Yarn

yarn add @adonisjs/auth@alpha

Next, run this command to invoke and set up the Auth package.

node ace invoke @adonisjs/auth

The command will prompt you to select the database provider and guard to be used, in the case of this tutorial, since we are building a web app.

We select Lucid and Web. Then type in the name of the model you want to use for authentication, in my case, we will type in User.

Make sure to register the Auth package to the start/kernel.ts file.

Server.middleware.registerNamed({
  //......
  auth: "App/Middleware/Auth",
});

Now, you can choose to set up the user’s schema here, but we have already done that, so I will ignore it.

Install Session Package

Next, install the @adonisjs/session since we are building a web app, we will use it for authentication.

npm i @adonisjs/session@alpha

//Or Yarn

yarn add @adonisjs/session@alpha

Install Shield Package

Next, install the @adonisjs/shield for CSRF protection.

npm i @adonisjs/shield@alpha

//Or Yarn

yarn add @adonisjs/shield@alpha

Run this command to invoke and set up the Shield package.

node ace invoke @adonisjs/shield

Follow the instructions provided and add the package to the start/kernel.ts file.

Server.middleware.register([
  "Adonis/Core/BodyParserMiddleware",
  "Adonis/Addons/ShieldMiddleware",
]);

Now. open your start/routes.ts file and add the following routes for user registration.

Route.on('register').render('register')
Route.post('register', 'AuthController.register')

Route.get("/dashboard", async ({ auth }) => {
  const user = await auth.authenticate();
  return `Hello user! Your email address is ${user.email}`;
});

Route.on("login").render("login");
Route.post("/login", "AuthController.login");

Next, make the authentication controller by running this command:

node ace make:controller Auth

The command will create a controller file in app/Controllers/Http, open it and put the following lines of code.

import User from "App/Models/User";
import { schema, rules } from "@ioc:Adonis/Core/Validator";
import { HttpContextContract } from "@ioc:Adonis/Core/HttpContext";

export default class AuthController {
  public async register({ request }: HttpContextContract) {
    /**
     * Validate user details
     */
    const validationSchema = schema.create({
      email: schema.string({ trim: true }, [
        rules.email(),
        rules.unique({ table: "users", column: "email" }),
      ]),
      password: schema.string({ trim: true }, [rules.confirmed()]),
    });
    const userDetails = await request.validate({
      schema: validationSchema,
    });
    /**
     * Create a new user
     */
    const user = new User();
    user.email = userDetails.email;
    user.password = userDetails.password;
    await user.save();
    await auth.login(user);
    response.redirect("/dashboard");
  }

  public async login({ auth, request, response }: HttpContextContract) {
    const email = request.input("email");
    const password = request.input("password");
    await auth.attempt(email, password);
    response.redirect("/dashboard");
  }
}

Next, let’s create a Register and Login View for our registration form by running this command.

node ace make:view register
node ace make:view login

A register.edge file will be created at resources/views, open it and put in the following codes.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Register</title>
</head>
<body>

  <form action="{{ route('AuthController.register') }}" method="post">
    <div>
      <label for="email">Email</label>
      <input type="text" name="email" value="{{ flashMessages.get('email') || '' }}" />
      <p>{{ flashMessages.get('errors.email') || '' }}</p>
    </div>

    <div>
      <label for="password">Password</label>
      <input type="password" name="password" />
      <p>{{ flashMessages.get('errors.password') || '' }}</p>
    </div>

    <div>
      <label for="password_confirmation">Re-Enter Password</label>
      <input type="password" name="password_confirmation" />
      <p>{{ flashMessages.get('errors.password_confirmation') || '' }}</p>
    </div>

    <div>
      <button type="submit">Create Account</button>
    </div>
  </form>

</body>
</html>

Open the login.edge file in resources/views and add the following codes:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Login</title>
</head>
<body>
  <form action="{{ route('AuthController.login') }}" method="post">
    <div>
      <label for="email">Email</label>
      <input type="text" name="email" value="{{ flashMessages.get('email') || '' }}" />
      <p>{{ flashMessages.get('auth.errors.uid') || '' }}</p>
    </div>
    <div>
      <label for="password">Password</label>
      <input type="password" name="password" />
      <p>{{ flashMessages.get('auth.errors.password') || '' }}</p>
    </div>
    <div>
      <button type="submit">Login</button>
    </div>
  </form>
</body>
</html>

You can now visit /register and /login to test out your application.

If you encounter any error saying Cannot find module 'phc-argon2' please run the following command to install the package.

npm install phc-argon2

If you’re able to register and login successfully, Congratulations.

Creating Models  

The app/Models folder will contain all the models that will be created in the course of developing your application.

If you look inside the folder and see a User.ts file inside, that’s exactly what a model is:

Just a file that contains the properties and methods to interact with our database schema, for example, inside the User.ts file, you will notice that it inherits the BaseModel an object that contains all the methods and properties to interact with our Database Schema.

The model can also do a lot more than interacting with Database Schema, we can define relationships, configure our model, etc.

We can use the User model to retrieve or create a new user in our application by providing the data needed in the User schema at database/migrations/xxxx_users.ts file.

Let’s take a look:

To retrieve all the users in our database, we will simply do:

const users = await User.all();
// SQL: SELECT * from "users" ORDER BY "id" DESC;

To retrieve a particular user based on ID:

const user = await User.find(1)
// SQL: SELECT * from "users" WHERE "id" = 1 LIMIT 1;

Create a new User (No need for SQL insert statement)

const user = new User()
user.name = 'Solomon Eseme'
user.email = '[email protected]'
user.password = 'password'

await user.save()

The user.save() method will perform the insert query and save the user to the database.

To delete a User from your Database, simply run:

const user = await User.findOrFail(1)
await user.delete()

Now that we have a glimpse of what AdonisJS Models are, let’s create our own Todo Model to interact with the Todo schema we create above.

Open your project terminal and run the following command.

node ace make:model Todo

The command will generate a new Todo Model inside app/Models folder.

Open the file and paste in the following code:

import { DateTime } from "luxon";
import { BaseModel, column, belongsTo, BelongsTo } from "@ioc:Adonis/Lucid/Orm";
import User from "App/Models/User";

export default class Todo extends BaseModel {
  @column({ isPrimary: true })
  public id: number;

  @column()
  public title: string;

  @column()
  public desc: string;

  @column()
  public status: number;

  @column()
  public userId: number;

  @belongsTo(() => User)
  public user: BelongsTo<typeof User>;

  @column.dateTime({ autoCreate: true })
  public createdAt: DateTime;

  @column.dateTime({ autoCreate: true, autoUpdate: true })
  public updatedAt: DateTime;
}

The code above simply maps the different columns we have in our database todos table and also defines a revised one to many relationship to our User model.

Creating Controllers

Again, controllers are like the middleman between requests (views) and models.

When a user sends a request to your backend either by clicking a button or submitting a form, the request passes through the routes to the controller and the controller calls out to your model to serve the request and returns a response back to the user (View).

With this flow in mind, let’s create our first controller to handle any request for the Todos:

Run the following command in your project terminal to create a new controller.

node ace make:controller Todo

The command will create a new controller inside app/Controllers/Http/TodosController.ts, open it and paste in the following codes.

import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Todo from "App/Models/Todo";

export default class TodosController {
      public async index({view, request}: HttpContextContract)
    {
        const todos = await Todo.query().preload('user');
        return view.render('dashboard' {todos});
    }
    public async byUserId({view, auth, request}: HttpContextContract)
    {
        const user = await auth.authenticate();
        await user.preload('todos')
        const todos = user.todos
        return view.render('dashboard', {todos});
    }
    public async show({view, request, params}: HttpContextContract)
    {
        try {
            const todo = await Todo.find(params.id);
            
            await todo.preload('user')
            return view.render('show', {todo});
        } catch (error) {
            console.log(error)
        }
        
    }
    public async edit({view, request, params}: HttpContextContract)
    {
        const todo = await Todo.find(params.id);
        await todo.preload('user')
        return view.render('edit', {todo});
    }

    public async update({view, auth, request, params}: HttpContextContract)
    {
        const todo = await Todo.find(params.id);
        if (todo) {
            todo.title = request.input('title');
            todo.desc = request.input('desc');
            todo.status = request.input('status') == 'on' ? 1 : 0;
            if (await todo.save()) {
                await todo.preload('user')
                return view.render('show', {todo});
            }
            return; // 422
        }
        return; // 401
    }
    public async create({view, request}: HttpContextContract)
    {
        return view.render('add');
    }
    public async store({view, auth request, response}: HttpContextContract)
    {
        const user = await auth.authenticate();
        const todo = new Todo();
        todo.title = request.input('title');
        todo.desc = request.input('desc');
        await user.related('todos').save(todo)) 
        response.redirect('/todos/'+todo.id);
    }
    public async destroy({response, auth, request, params}: HttpContextContract)
    {
       const user = await auth.authenticate();
       const todo = await Todo.query().where('user_id', user.id).where('id', params.id).delete();
       return response.redirect('/dashboard');
    }
}

The code above just perform a simple CRUD operation using the Todo model and render the different Views will be creating soon.

Also note that the controller does not include any Validation or proper error handling, I just wanted to keep it as basic as possible.

Creating Routes

Next, we are going to create routes that map user’s requests to controller’s methods based on specific user requests.

Now, open the start.ts file inside the start folder and add the following codes:

import Route from "@ioc:Adonis/Core/Route";

Route.on("/").render("welcome");
Route.on("register").render("register");
Route.post("register", "AuthController.register");

Route.group(() => {
  Route.get("/dashboard", "TodosController.index").as("dashboard");
  Route.get("/todos/user", "TodosController.byUserId");
  Route.resource("todos", "TodosController");
}).middleware("auth");

Route.on("login").render("login");
Route.post("/login", "AuthController.login");
Route.post("/logout", "AuthController.logout").as("logout");

When a user clicks on a button or submits a form, how does AdonisJS know which method to call or which controller to send the request to?

Well, everything is defined and mapped using a Routing System.

Creating Views

Views represent how the Information is displayed, it is used for all the UI logic of the software. You are right if you say that the View represents the Frontend of your web page.

To create a View, run the following command:

node ace make:view dashboard

That command will create a dashboard.edge file inside resources/views, open it and paste in the following codes.

@layout('layouts/app')
@section('main')
<div class="py-12">
    <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
        <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
            <div class="p-6 bg-white border-b border-gray-200">
                <a href="/todos/create" class="btn btn-primary">Add new todo</a>
            </div>
        </div>
    </div>
</div>

<div class="py-12">
    <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
        <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
            <div class="p-6 bg-white border-b border-gray-200">
                <div class="panel-bod">
                    <table class="table">
                        <!-- Table Headings -->
                        <thead>
                            <th>All Todos</th>
                            <th>
                                <a class="btn btn-warning" href="/todos/user">Show mine</a>
                            </th>
                            <th>
                                <a class="btn btn-info" href="/dashboard">Show All</a>
                            </th>
                        </thead>
                        <!-- Table Body -->
                        <tbody class="max-w-full">
                            @each(todo in todos)
                            <tr class="max-w-full">
                                <!-- Task Name -->
                                <td class="pt-5 pb-5 pr-5">
                                    <h1 class="sm:font-bold">{{ todo.title }}</h1>
                                    <p>{{ todo.desc }}</p>
                                    <p class="pt-2 text-gray-500"> Added By: {{ todo.userId == auth.user.id? 'You':todo.user.name  }}</p>
                                </td>
                                <td>
                                    <!-- TODO: Delete Button -->
                                    <div>
                                        <a href="/todos/{{todo.id}}" class="bg-green-500 btn btn-success">View</a>
                                        @if(todo.userId == auth.user.id)
                                        <a href=" /todos/{{todo.id}}/edit" class="bg-yellow-500 btn btn-primary">Edit</a>
                                        <form class="" method="POST" action="/todos/{{todo.id}}?_method=DELETE">
                                            {{ csrfField() }}
                                            <button class="bg-red-500  btn btn-danger">Delete</button>
                                        </form>
                                        @endif
                                    </div>
                                </td>
                            </tr>
                            @endeach
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

There are obviously, a lot more views that have been created to complete this project, such as add.edge, show.edge, edit.edge etc.

You can get access to the repository and clone it.

Preview

If you get everything correctly, you should be presented with a dashboard like this:

adonisjs-todo.png

Congratulations on making it this far!

When it comes to learning and mastering AdonisJS, this course Learn AdonisJs: from zero to deploy is my top recommendation. You will Learn AdonisJs by building a production-ready application completely from scratch.

Take a break and subscribe to get access to our free NODEJS TIPS that will improve your productivity.

Chapter 4: Deploying AdonisJS Project

Now, deploying your AdonisJS project can be tedious especially if it is your first time, but in this chapter, we will look at deploying your first AdonisJS 5 project easily and successfully.

To deploy the AdonisJS 5 project to Heroku is a rather fun and straightforward process, it comes with great benefits and supports auto-deployment and auto testing that’s why I have prepared a work through an article on deploying AdonisJS to Heroku.

Deploying Laravel to Shared Hosting

Deploying AdonisJS to shared hosting might be the cheapest option available right now for testing purposes, that’s why I have prepared a work through article on deploying AdonisJS to Shared Hosting.

Conclusion: AdonisJS

In this AdonisJS tutorial, we have looked at the nitty-gritty of AdonisJS 5 and have created a Todo application to practically demonstrate the knowledge we have gained so far.

We have even looked at how to deploy the application to different hosting platforms.

Now, it’s your turn to practice everything you have learned until you master them by building a real world application.

Let me know what you’ve learned from this AdonisJS tutorial and what you will be building, if none, just comment ‘AdonisJS is awesome’ we could connect from there.

When it comes to learning and mastering AdonisJS, this course Learn AdonisJs: from zero to deploy is my top recommendation. You will Learn AdonisJs by building a production-ready application completely from scratch.

Whenever you're ready

There are 4 ways we can help you become a great backend engineer:

The MB Platform

Join 1000+ backend engineers learning backend engineering. Build real-world backend projects, learn from expert-vetted courses and roadmaps, track your learnings and set schedules, and solve backend engineering tasks, exercises, and challenges.

The MB Academy

The “MB Academy” is a 6-month intensive Advanced Backend Engineering BootCamp to produce great backend engineers.

Join Backend Weekly

If you like post like this, you will absolutely enjoy our exclusive weekly newsletter, Sharing exclusive backend engineering resources to help you become a great Backend Engineer.

Get Backend Jobs

Find over 2,000+ Tailored International Remote Backend Jobs or Reach 50,000+ backend engineers on the #1 Backend Engineering Job Board

Backend Tips, Every week