AdonisJS CRUD App Notes
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')
})
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