Javascript

Build a Task List with Authentication Using SQL, Node.js and Adonis (Part 2)

August 24th, 2018 | By Connor Lech | 5 min read

This is the second part of a two-part series on building a task list with authentication (app), complete with authentication, where guests can view all the tasks and click in to view an individual task.

In the first part, we went through the initial setup and the creation of a local database, adding authentication, and seeding data. In this second part, we will go over defining controllers, handling authentication, and adding task views.

Define controller

At this stage, our route endpoints are defined, but if we fire up the dev server and head to localhost:3333/tasks we see an error that "Cannot find module TaskController". To create a task controller, run:

$ ./ace make:controller Task --resource 


This command generates a file in app/Http/Controllers/TaskController.js that stubs out the various methods we defined above.

The methods will be blank after we generate the command. Take the liberty of filling them out. We're going to use Lucid, which is Adonis' implementation of ActiveRecord. Essentially, it is an ORM so we don't have to write raw SQL queries to fetch our data.

'use strict'

class TaskController {

  static get inject () { 
    return ['App/Model/Task', 'App/Model/User']  
  }

  constructor (Task, User) { 
    this.Task = Task
    this.User = User
  }

  * index(request, response) {
    const tasks = yield this.Task.all() 
    yield response.sendView('tasks.index', { tasks: tasks.toJSON() })
  }

  * create(request, response) {
    const isLoggedIn = yield request.auth.check()

    if (!isLoggedIn) {
      response.redirect('/login')
    }

    yield response.sendView('tasks.create')
  }

  * store(request, response) {
    const isLoggedIn = yield request.auth.check()

    if (!isLoggedIn) {
      response.redirect('/login')
    }

    let task = request.only('title', 'description');

    const newTask = new this.Task({
      title: task.title,
      description: task.description,
      user_id: request.currentUser.id
    })

    yield newTask.save()

    response.redirect('/tasks')
  }

  * show(request, response) {
    const task = yield this.Task.find(request.param('id'))

    const owner = yield this.User.find(task.user_id)

    if (task) {
      yield response.sendView('tasks.show', { task: task.toJSON(), owner })
      return
    }

    response.send('Sorry, cannot find the selected found')
  }

  * destroy(request, response) {
    const isLoggedIn = yield request.auth.check()

    if (!isLoggedIn) {
      response.redirect('/login')
    }

    const task = yield this.Task.findBy('id', request.param('id'))
    yield task.delete()
  }

}

module.exports = TaskController


This task controller contains all the logic for creating, reading, and deleting tasks. We also include some logic for access control using authorization.

For instance, we only want authenticated users to be able to create tasks. We ran some commands that generated a user model and authentication logic, but we have not fully set it up. We'll do that in the next section.

Authentication continued

Please note that there is an Adonis blueprint that sets up authentication scaffolding out of the box. Additionally, Prosper's Auth0 blog post covers more details on Adonis authentication.

First, let's define some routes for registering, logging in, showing the forms, and logging out:

Route.get('/login', 'AuthController.showLogin')
Route.post('/login', 'AuthController.login')

Route.get('/register', 'AuthController.showRegister')
Route.post('register', 'AuthController.register')

Route.get('/logout', 'AuthController.logout')


Next, we need to set up a controller to handle these routes:

$ ./ace make:controller Auth 


Then, we need to define the controller to render views and take database actions:

'use strict'

const User = use('App/Model/User')
const Hash = use('Hash')

class AuthController {

  * showLogin(request, response) {
    yield response.sendView('auth.login')
  }

  * login(request, response) {
    const email = request.input('email')
        const password = request.input('password')

        const attemptUser = yield User.findByOrFail('email', email)

        // Attempt to login with email and password
        const authCheck = yield request.auth.login(attemptUser)
        if (authCheck) {
            return response.redirect('/')
        }

        yield response.sendView('auth.login')
  }

  * showRegister(request, response) {
    yield response.sendView('auth.register')
  }

  * register(request, response) {
    const user = new User()
        user.username = request.input('username')
        user.email = request.input('email')
        user.password = yield Hash.make(request.input('password'))

        yield user.save()

        yield response.sendView('auth.register')
    
  }

    * logout(request, response) {
        yield request.auth.logout()

        return response.redirect('/')
    }
}

module.exports = AuthController


Now, we can define the views for registering and logging in users:

resources/views/auth/login.njk

{% extends 'master' %}

{% block content %}
  <h1>Login</h1>

    <form method="POST" action="/login">

      {{ csrfField }}


      <input type='email' placeholder='Email' name='email' class='input'>

      <input type='password' placeholder='Password' name='password' class='input'>

      {% if error %}
      <div class="notification is-danger">
      {{ error }}
    </div>
    {% endif %}

      <input type='submit' value='Submit'>

  </form>
{% endblock %}


Adonis provides a special Cross-Site Request Forgery (CSRF) field for submitting forms so that malicious attackers cannot hijack requests.

This helper checks that the request comes from the right people in the right places: learn more about CSRF.

resources/views/auth/register.njk

{% extends 'master' %}

{% block content %}
  <h1>Register</h1>

  <form method="POST" action="/register">

    {{ csrfField }}
    <input type='text' placeholder='Username' name='username' class='input'>

    <input type='email' placeholder='Email' name='email' class='input'>

    <input type='password' placeholder='Password' name='password' class='input'>

    <input type='submit' value='Submit'>

  </form>
{% endblock %}


Task views

Now that we have all of our routes, controllers, and authentication set up, we can add the views for showcasing tasks:

resources/views/tasks/create.njk

{% extends 'master' %}

{% block content %}
  <div class="content">
    <h1>Create task</h1>

    <form method='POST' action='/tasks'>
      {{ csrfField }}
      <input type='text' name='title' placeholder='Title of task' class='input'>

      <textarea name='description' placeholder='Description of task' class='input'></textarea>

      <input type='submit' value='Submit'>
    </form>

  </div>
{% endblock %}


resources/views/tasks/index.njk

{% extends 'master' %}

{% block content %}
  <div>
    <h1 class='title'>Tasks</h1>

    {% for task in tasks %} 
      <div class="content">
        <h2>
          {{ linkTo('tasks.show', task.title, { id: task.id }, '_blank') }}
        </h2> 
        <p> {{ task.description }} </p>
      </div>
    {% endfor %}
  </div>
{% endblock %}


resources/views/tasks/show.njk

{% extends 'master' %}

{% block content %}
  <div class="content">
    <h1>{{ task.title }}</h1>

    <p> {{ task.description }} </p>

    <p>task created by: <b>{{ owner.username }}</b></p>
  </div>
{% endblock %}


Conclusion

In this tutorial, we've set up a Node.js server that talks to a MySQL database. We can create, delete, and showcase tasks. Additionally, we can register and authorize users and define access control based on the current user's authentication status.

Adonis.js is a powerful framework for building Node.js applications.

While it is not as popular as other Node.js web servers like Koa and Express, Adonis provides more power within the confines of an opinionated tech stack.

Check out the source code on GitHub and definitely consider Adonis for future Node projects!

If you want to secure your JavaScript source code against theft and reverse-engineering, you can try Jscrambler for free.

Jscrambler

The leader in client-side Web security. With Jscrambler, JavaScript applications become self-defensive and capable of detecting and blocking client-side attacks like Magecart.

View All Articles

Must read next

Web Development

Build a Task List with Authentication Using SQL, Node.js and Adonis (Part 1)

In this tutorial, we create a Node.js server that communicates with a MySQL database to render a browser task app. We’ll add authentication using Adonis.

August 17, 2018 | By Connor Lech | 10 min read

Web Development

Build Database Relationships with Node.js and MongoDB

Let's dive deeper into MongoDB and understand how relationships work between MongoDB collections in a Node.js server application.

September 10, 2019 | By Connor Lech | 8 min read

Section Divider