AdonisJs. Beginner’s Guide

1. Creating new AdonisJs project

npm i -g @adonisjs/cli
adonis new projectname
// Scaffold project for api server
adonis new projectname --api-only
adonis new projectname --api
adonis serve --dev
ENV_PATH=.env.dev adonis serve --dev
npm i mysql url-parse
//insert those line after Helpresconst Url = use('url-parse')
const CLEARDB_DATABASE_URL = new Url(Env.get('CLEARDB_DATABASE_URL'))
// and before module.exports {
mysql: {
client: 'mysql',
connection: {
host: Env.get('DB_HOST', CLEARDB_DATABASE_URL.host),
port: Env.get('DB_PORT', ''),
user: Env.get('DB_USER', CLEARDB_DATABASE_URL.username),
password: Env.get('DB_PASSWORD', CLEARDB_DATABASE_URL.password),
database: Env.get('DB_DATABASE', CLEARDB_DATABASE_URL.pathname.substr(1))
}
},

2. Configuring Heroku app

RuntimeException: E_MISSING_ENV_KEY: Make sure to define environment variable APP_KEY.App key is a randomly generated 16 or 32 characters long string required to encrypted cookies, sessions and other sensitive data.

3. Adding new Routes

Returning simple text message

Route.get('/simple-text', () => 'Hello Adonis')

Returning html page

Route.on('/html-page').render('custom')

Dynamic routes

Route.get('dynamic/:id', ({ params }) => {
return `Post ${params.id}`
})
Route.get('dynamic/:drink?', ({ params }) => {
// use Coffee as fallback when drink is not defined
const drink = params.drink || 'Coffee'
return `One ${drink}, coming right up!`
})

4. Adding data to the Database

// Database
const Database = use('Database')
Route.get('users/:username', async ({ params }) => {
const userId = await Database
.table('users')
.insert({ username: params.username })
return userId
}).formats(['json'])
Error: insert into `users` (`username`) values ('custom-name') - ER_NO_DEFAULT_FOR_FIELD: Field 'email' doesn't have a default value
class UserSchema extends Schema {
up () {
this.create('users', table => {
table.increments()
table.string('username', 80).notNullable().unique()
table.string('email', 254).notNullable().unique()
table.string('password', 60).notNullable()
table.timestamps()
})
}
down () {
this.drop('users')
}
}

5. Migrations

Creating schema

adonis make:migration users
> Create table // for creating new table
Select table //
for altering old table
adonis migration:run // executes up() function defined in Schema

This only works in .alterTable() and is not supported by SQlite or Amazon Redshift.

NODE_ENV=testing adonis migration:status
$ NODE_ENV=testing adonis migration:run

First variant:

NODE_ENV=testing adonis make:migration user
> Select table
class UserSchema extends Schema {
up () {
this.table('users', table => {
table.dropColumn('email')
})
}
}
NODE_ENV=testing adonis make:migration user
> Select table
class UserSchema extends Schema {
up () {
this.table('users', table => {
table.string('email', 254).nullable().alter()
})
}
down () {
this.table('users', table => {
table.string('email', 254).notNullable().alter()
})
}
}
NODE_ENV=testing adonis migration:run

Production

adonis migration:run --force
adonis migration:reset --force --log
Queries for ***_user_schema.js
alter table `users` modify `email` varchar(254) not null
Queries for ***_token.js
drop table `tokens`
Queries for ***_user.js
drop table `users`

6. Controllers

Route.get('users/:user/:password/:email', async ({ params }) => {
const userId = await Database
.table('users')
.insert({
username: params.username,
password: params.password,
email: params.email,
});
const user = await User.find(userId)Database.close(['mysql'])return user
}).formats(['json'])
Route.get('users', 'UserController.index')
class UserController {
index () {
return 'Some custom response'
}
}

Name of a function could be any you like, there is just a name conventions that are preferable

adonis make:controller UsersSelect controller type 
For HTTP requests
For Websocket channel
class UserController {
customName ({ request }) {
return {
host: request.header('host'),
url: request.url(),
originalUrl: request.originalUrl(),
method: request.method(),
intended: request.intended(),
ip: request.ip(),
subdomains: request.subdomains(),
'user-agent': request.header('user-agent'),
accept: request.header('accept'),
hello: request.header('hello'),
isAjax: request.ajax(),
hostname: request.hostname(),
protocol: request.protocol()
}
}
...
Route.get('request', 'UserController.customName').formats(['json'])

7. Lucid Models

const User = use('App/Models/User')class UserController {
async index () {
return await User.all()
}
async show ({ params }) {
return await User.find(params.id)
}
async store () {}
async update () {}
async destroy () {}
}
Route.get('users', 'UserController.index').formats(['json'])
Route.get('users/:id', 'UserController.show').formats(['json'])
class UserController {
...
async store ({ request }) {
const { username, password, email } = request.post()
const user = new User()
user.username = username
user.password = password
user.email = email
await user.save()
}
async update ({ request, params }) {
const user = await User.find(params.id)
const { username, password, email } = request.post()user.username = username
user.password = password
user.email = email
await user.save()
}
async destroy ({ params }) {
const user = await User.find(params.id)
await user.delete()
}
}
Route.post('users', 'UserController.store').formats(['json'])
Route
.patch('users/:id', 'UserController.update').formats(['json'])
Route.delete('users/:id', 'UserController.destroy').formats(['json'])

8. CSRF

403:Forbidden “HttpException: EBADCSRFTOKEN: Invalid CSRF token”
CREATE
UPDATE
DELETE
Route
.resource('users', 'UserController')
.apiOnly()
Route.group(() => {
Route.get('request', 'CustomController.someCustom')
.formats(['json'])
Route
.resource('users', 'UserController')
.apiOnly()
})
.prefix('api/v1/')
csrf: {
enable: true,
methods: ['POST', 'PUT', 'DELETE'],
filterUris: ['/api/(.*)'],
cookieOptions: {
httpOnly: false,
sameSite: true,
path: '/',
maxAge: 7200
}
}

9. Security

authenticator: 'jwt',
jwt: {
algorithm: 'HS256', // by default
serializer: 'lucid',
model: 'App/Models/User',
scheme: 'jwt',
uid: 'email',
password: 'password',
options: {
secret: Env.get('APP_KEY'),
expiresIn: "1m", // 1 minute
},
},
// We don't want our logged-in user to access this
Route
.
post('login', 'AuthController.login').
middleware('guest');
Route.
resource('users', 'UserController').
apiOnly().
middleware('auth');
adonis make:controller Auth
async login ({ auth, request }) {
const { email, password } = request.post();
return auth.withRefreshToken().
attempt(email, password);
}
/**
* A hook to hash the user password before saving
* it to the database.
*/

this.addHook('beforeSave', async (userInstance) => {
if (userInstance.dirty.password) {
userInstance.password = await Hash.make(userInstance.password);
}
})
;
async login ({ auth, request }) {
const { refreshToken, email, password } = request.post();
if (refreshToken) {
return await auth.
generateForRefreshToken(refreshToken);
}
return auth.withRefreshToken().
attempt(email, password);
}

10. Data validation

adonis install @adonisjs/validator
const providers = [
...
'@adonisjs/validator/providers/ValidatorProvider'
]
class UserController {
async store({ request }) {
const rules = {
email: 'required|email|unique:users,email',
password: 'required',
};
const validation = await validate(request.post(), rules);if (validation.fails()) {
return validation.messages();
}
...
}
const messages = {
'email.required': 'Email is not present in request',
'email.email': 'Enter a valid email address.',
'email.unique': 'Email is already present in db',
};
const validation = await validate(request.post(), rules, messages);
const { validateAll } = use('Validator');
...
const validation = await validateAll(request.post(), rules, messages);
adonis make:validator StoreUser
class StoreUser {
// use validateAll function instead of validate
get validateAll () {
return true;
}
get rules () {
return {
email: 'required|email|unique:users,email',
password: 'required',
};
}
get messages () {
return {
'email.required': 'Email is not present in request',
'email.email': 'Enter a valid email address.',
'email.unique': 'Email is already present in db',
};
}
get sanitizationRules () {
return {
email: 'normalize_email',
};
}
async fails (errorMessages) {
return this.ctx.response.send(errorMessages);
}
}
Route
.resource('users', 'UserController')
.middleware('auth')
.validator(new Map([
[[
'users.store'], ['StoreUser']],
]))
.apiOnly();
async store ({ request }) {
const { username, password, email } = request.post();
const user = new User();
user.username = username;
user.password = password;
user.email = email;
await user.save();
}

11. Testing

adonis install @adonisjs/vow
test('make sure 2 + 2 is 4', async ({ assert }) => {
assert.equal(2 + 2, 4);
});
const aceProviders = [
...
'@adonisjs/vow/providers/VowProvider'
]
adonis test
adonis make:test UserController
Unit test
> Functional test
const user = await User.create({
username: 'some',
email: EMAIL,
password: PASSWORD,
});
const login = await client
.post('api/v1/login')
.send({
email: EMAIL,
password: PASSWORD,
})
.end();
// You must call "end" to execute HTTP client requests.
const response = await client
.post('api/v1/users')
.header('accept', 'application/json')
.header('Authorization', `Bearer ${loginResponseJson.token}`)
.send({
username: 'someName',
email: 'hello@gmail.com',
password: '123',
})
.end();
trait('DatabaseTransactions');
trait('Test/ApiClient');

Java, Spring, Node.js, AdonisJs, React.js and Flutter developer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store