Skip to content

Instantly share code, notes, and snippets.

@jbenner-radham
Last active March 14, 2022 00:44
Show Gist options
  • Save jbenner-radham/41d714f2e8d2352cf300c395d33fb2af to your computer and use it in GitHub Desktop.
Save jbenner-radham/41d714f2e8d2352cf300c395d33fb2af to your computer and use it in GitHub Desktop.
AdonisJS CRUD app notes.

AdonisJS CRUD App Notes

Initial Setup

Create your app scaffolding:

yarn create adonis-ts-app $APP_NAME

Install Lucid (the database module):

yarn add @adonisjs/lucid

Configure Lucid:

node ace configure @adonisjs/lucid

Update /env.ts with the following properties for the Env.rules object argument:

export default Env.rules({
  DB_CONNECTION: Env.schema.string(),
  PG_HOST: Env.schema.string({ format: 'host' }),
  PG_PORT: Env.schema.number(),
  PG_USER: Env.schema.string(),
  PG_PASSWORD: Env.schema.string.optional(),
  PG_DB_NAME: Env.schema.string(),
})

Install Auth:

yarn add @adonisjs/auth

Configure Auth (name your model User):

node ace configure @adonisjs/auth

Install Session:

yarn add @adonisjs/session

Configure Session:

node ace configure @adonisjs/session

Verify that /env.ts contains the following property for the Env.rules object argument:

export default Env.rules({
  SESSION_DRIVER: Env.schema.string(),
})

Install phc-argon2 hashing module (undocumented):

Note: Make a PR to fix the docs!

yarn add phc-argon2

Create a layout template:

mkdir resources/views/layouts && touch resources/views/layouts/main.edge

Populate the layout template:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ title }}</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]">
  </head>
  <body>
    @!section('body')
    <style>
      body {
        min-height: 100vh;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        font-family: sans-serif;
        text-align: center;
        margin: .75rem;
      }
    </style>
  </body>
</html>

Create a login view:

node ace make:view login

Populate the login view:

@layout('layouts/main')
@set('title', 'Login')

@section('body')
  <main class="login">
    <h1>Login</h1>

    <form class="form" action="/login" method="POST">
        <label class="form__label" for="form__email">Email</label>
        <input
            id="form__email"
            name="email"
            placeholder="[email protected]"
            type="email"
            autofocus
            required
        >

        <label class="form__label" for="form__password">Password</label>
        <input
            id="form__password"
            name="password"
            placeholder="secret"
            type="password"
            required
        >

        <br>

        <button class="form__button--submit" type="submit">
            Log In
        </button>
    </form>
  </main>

  <style>
    .form__button--submit {
      margin-top: .75rem;
    }

    .form__label {
      display: block;
      font-weight: bolder;
      margin-bottom: .5rem;
    }

    .form__label:first-of-type {
      margin-top: .75rem;
    }

    .form__label:not(:first-of-type) {
      margin-top: .5rem;
    }
  </style>
@end

Add the login routes to /start/routes.ts:

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

Route.post('/login', async ({ auth, request, response }) => {
  const email = request.input('email')
  const password = request.input('password')

  try {
    await auth.use('web').attempt(email, password)
    response.redirect('/dashboard')
  } catch {
    return response.badRequest('Invalid credentials')
  }
})

Create UsersController:

node ace make:controller --resource true user

Create users/create view:

node ace make:view users/create

Populate the users/create view:

@layout('layouts/main')
@set('title', 'Create User')

@section('body')
  <main class="create-user">
    <h1>Create User</h1>

    <form class="form" action="/users" method="POST">
        <label class="form__label" for="form__email">Email</label>
        <input
            id="form__email"
            name="email"
            placeholder="[email protected]"
            type="email"
            autofocus
            required
        >

        <label class="form__label" for="form__password">Password</label>
        <input
            id="form__password"
            name="password"
            placeholder="secret"
            type="password"
            required
        >

        <br>

        <button class="form__button--submit" type="submit">
          Create Account
        </button>
    </form>
  </main>

  <style>
    .form__button--submit {
      margin-top: .75rem;
    }

    .form__label {
      display: block;
      font-weight: bolder;
      margin-bottom: .5rem;
    }

    .form__label:first-of-type {
      margin-top: .75rem;
    }

    .form__label:not(:first-of-type) {
      margin-top: .5rem;
    }
  </style>
@end

Populate /app/Controllers/Http/UsersController.ts with the following:

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

export default class UsersController {
  public async index ({}: HttpContextContract) {}

  public async create (ctx: HttpContextContract) {
    return ctx.view.render('users/create')
  }

  public async store (ctx: HttpContextContract) {
    await User.create({
      email: ctx.request.input('email'),
      password: ctx.request.input('password'),
    })
  }

  public async show ({}: HttpContextContract) {}

  public async edit ({}: HttpContextContract) {}

  public async update ({}: HttpContextContract) {}

  public async destroy ({}: HttpContextContract) {}
}

Add the UsersController to /start/routes.ts:

Route.resource('/users', 'UsersController')

Make dashboard view:

node ace make:view dashboard

Populate dashboard view:

@layout('layouts/main')
@set('title', 'Dashboard')

@section('body')
  <main class="dashboard">
    <h1>Dashboard</h1>
    @if(auth.isLoggedIn)
      <p>Hello {{ auth.user.email }}</p>
    @end
  </main>
@end

Add the dashboard route to /start/routes.ts:

Route.get('/dashboard', async ({ auth, view }) => {
  await auth.use('web').authenticate() // <-- This makes the session stick!
  return view.render('dashboard')
})

Alternatively, register the auth middleware in start/kernel.ts and attach it to the route:

Server.middleware.registerNamed({
  auth: () => import('App/Middleware/Auth')
})
Route.get('/dashboard', async ({ auth, view }) => {
  return view.render('dashboard')
}).middleware('auth')

See here for details.

For resourceful routes you need to attach the auth middleware like so:

Route.resource('/examples', 'ExamplesController').middleware({
  '*': ['auth']
})

See here for details.

Create a logout route:

Route.post('/logout', async ({ auth, response }) => {
  await auth.use('web').logout()
  response.redirect('/login')
})

Miscellaneous Notes

When creating a model you can also create a corresponding migration and controller:

node ace make:model --controller true --migration true $MODEL_NAME

Check if a user is logged in inside a view:

@if(auth.isLoggedIn)
  {{/* Do something... */}}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment