Skip to content

About

Cheatsheet for AdonisJs (https://adonisjs.com) for simple API creation and management, with personal notes.

Resources

Stars

24 stars

Watchers

1 watching

Forks

Latest commit

Β 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 

Repository files navigation

lysla's API with AdonisJS cheatsheet

Simple indexed manual with notes.

installation

npm i -g @adonisjs/cli

To execute in project directory

adonis new . --api-only

Then start it

adonis serve --dev

configuring database connection

MySQL example (on localhost)

npm i --save mysql

In config/database.js

//...
connection: Env.get('DB_CONNECTION', 'mysql')
//...
mysql: {
    client: 'mysql',
    connection: {
      host: Env.get('DB_HOST', 'localhost'),
      port: Env.get('DB_PORT', ''),
      user: Env.get('DB_USER', 'root'),
      password: Env.get('DB_PASSWORD', ''),
      database: Env.get('DB_DATABASE', 'lysla-cms-api')
    },
    debug: Env.get('DB_DEBUG', false)
}

In .env file

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_DATABASE=lysla-cms-api

creating a resource

Each resource it's made up of its model, its controller and its migration

adonis make:model Item --migration --controller

🎈 In the created controller, we can remove 'create' and 'edit' method since we are developing just an API and we don't need any form rendering

Then we want to define its schema via the migration we just created

class ItemSchema extends Schema {
	up () {
		this.create('items', (table) => {
			table.increments()
			table.timestamps()
			// my own resource's fields
			table.string('name')
			table.text('content')
		})
	}

	down () {
		this.drop('items')
	}
}

🎈 Remind we got to create and run proper database structure for all kind of relationships (foreign keys and pivot table for many to many) manually with migrations

migrations

Run migrations to populate, or update, database

adonis migration:run

Or roll em back

adonis migration:rollback

Create a migration for each schema change

adonis make:migration Item
> Select or Create (if a create already exists)

Then update fields and structure in the Schema

class ItemSchema extends Schema {
	up () {
		this.table('items', (table) => {
			// alter table
			table.dropColumn('name')
		})
	}

	down () {
		this.table('items', (table) => {
			// reverse alternations
			table.text('name')
		})
	}
}

🎈 Remind that Adonis migration syntax is developed on the top of knexjs.org whom enable us to define migration for tables with relationship via foreign keys

foreign key

Migration with foreign keys

class CardSchema extends Schema {
	up() {
		this.create('cards', (table) => {
			table.increments()
			table.timestamps()

			table.string('brand')
			table.string('weight')
			// foreign key to relate to items table
			table.integer('item_id').unsigned()
			table.foreign('item_id').references('items.id')
		})
	}

	down() {
		this.drop('cards')
	}
}

We can also add a foreign key to an existing table with a new migration

class CardSchema extends Schema {
	up() {
		this.table('cards', (table) => {
			table.integer('item_id').unsigned()
			table.foreign('item_id').references('items.id')
		})
	}

	down() {
		this.table('cards', (table) => {			
		 	table.dropForeign('item_id')
		})
	}
}

pivot table

Migration for pivot table

adonis make:migration item_tag
> Create table
class ItemTagSchema extends Schema {
	up() {
		this.create('item_tag', (table) => {
			table.integer('item_id').unsigned()
			table.foreign('item_id').references('items.id')
			table.integer('tag_id').unsigned()
			table.foreign('tag_id').references('tags.id')
		})
	}

	down() {
		this.drop('item_tag')
	}
}

Migration for pivot model

adonis make:migration item_box
> Create table
class ItemBoxSchema extends Schema {
	up() {
		this.create('item_box', (table) => {
			table.increments()
			table.timestamps()

			table.integer('item_id').unsigned()
			table.foreign('item_id').references('items.id')
			table.integer('box_id').unsigned()
			table.foreign('box_id').references('boxes.id')

			table.integer('quantity')			
		})
	}

	down() {
		this.drop('item_box')
	}
}

models (πŸ’₯ wip)

Each model mediates under the hood for every normal table field. Getters and setters will override its default process, as long as they respect the naming constraint.

A field named 'title' will need a getter 'getTitle' and a setter 'setTitle' (camecase)

//example πŸ’₯wip

More then getters and setters, models allows computed properties

//example πŸ’₯wip

relationships

Where the Adonis models works as ORM we always need to create proper migrations manually for each relationship, following standard RDBMS school.

one to one - 1:1

Where every Item has one and only one Card

With foreign key in Card

class Item extends Model {
	card () {
		return this.hasOne('App/Models/Card')
	}
}

With foreign key in Item

class Card extends Model {
	item () {
		return this.belongsTo('App/Models/Item')
	}
}

one to many - 1:n

Where every Category can contain one or more Item(s)

class Category extends Model {
	items () {
		return this.hasMany('App/Models/Item')
	}
}
class Item extends Model {
	category () {
		return this.belongsTo('App/Models/Category')
	}
}

many to many - n:m

Where every Item(s) can belong to one or more Tag(s)

class Item extends Model {
	tags () {
		return this.belongsToMany('App/Models/Tag')
	}
}
class Tag extends Model {
	items () {
		return this.belongsToMany('App/Models/Item')
	}
}

pivot model

With pivot models we can save and access more data throu the pivot table relation

🎈 Given we already created the proper pivot table migration, we can create its model

adonis make:model ItemBox

And then define the many to many relation in the model

class Box extends Model {
	items () {
		return this
			.belongsToMany('App/Models/Item')
			.pivotModel('App/Models/ItemBox')
	}
}
class Item extends Model {
	boxes () {
		return this
			.belongsToMany('App/Models/Box')
			.pivotModel('App/Models/ItemBox')
	}
}

routes

Defining routing for each available data api

In start/routes.js
Route.resource('items', 'ItemController').apiOnly()

🎈 this auto generate common routing named as follow

Route.get('items', 'ItemController.index').as('items.index')
Route.post('items', 'ItemController.store').as('items.store')
Route.get('items/:id', 'ItemController.show').as('items.show')
Route.put('items/:id', 'ItemController.update').as('items.update')
Route.patch('items/:id', 'ItemController.update')
Route.delete('items/:id', 'ItemController.destroy').as('items.destroy')

controllers

Controller configuration

Remind the declarations of const model class

'use strict'
const Item = use('App/Models/Item')
//...

index method (GET)

Show all records

async index ({ request, response, view }) {

	// retrieving all data
	const allItems = await Item.all()

	// api response
	response.json({
		message: 'Success',
		data: allItems
	})

}

store method (POST)

Creating a new record

//...
async store ({ request, response }) {

	// retrieving data to store
	const { name, content } = request.all();

	// creating new instance
	const newItem = new Item()
	// assigning data
	newItem.name = name
	newItem.content = content

	// save the data to database
	await newItem.save()

	// api response
	response.json({
		message: 'Success',
		data: newItem
	})
}
// ...

Alternative with selective income values

//...
async store ({ request, response }) {

	// retrieving data to store
	const newData = request.only(['title', 'content'])

	// assigning and saving data
	const newItem = await Item.create(newData)

	// api response
	response.json({
		message: 'Success',
		data: newItem
	})
}
// ...

show method (GET)

Show a single record by a given id

async show ({ params, request, response, view }) {

	// retrieve the data by given id
	const theItem = await Item.find(params.id)

	// api response
	response.json({
		message: 'Success',
		data: theItem
	})

}

update method (PUT or PATCH)

Edit an existing record by a given id and new data

async update ({ params, request, response }) {

	// retrieve the data by given id
	const theItem = await Item.find(params.id)

	// retrieving new data
    const newData = request.only(['name', 'content'])

	// updating data
	theItem.name = newData.name
	theItem.content = newData.content

	// save the data to database
	await theItem.save()

	// api response
	response.json({
		message: 'Success',
		data: theItem
	})

}

destroy method (DELETE)

Remove an existing record by a given id

async destroy ({ params, request, response }) {

	// retrieve the data by given id
	const theItem = await Item.find(params.id)

	// delete the record
	await theItem.delete()
	
	// api response
	response.json({
		message: 'Success',
		data: theItem
	})
}

querying (πŸ’₯ wip)

Querying complex data throu modeled relationships, retrieving a proper json structure

Querying a specific record via its id, including its relationship(s)

const jsonData = await Item.query().where('id', newRecord.id).with('category').fetch()

middlewares

Creating a middleware

🎈 Adonis apiOnly boilerplate comes with authentication middlewares out of the box so we don't need to create them, the following it's just an example for a custom middleware

adonis make:middleware MyMiddleware
> HTTP Requests

Then in kernel.js we need to register the middleware

// as named middleware (only for some routes)
const namedMiddleware = {
	//...
	myMiddleware: 'App/Middleware/My'
}

Then in the routes.js, in the specific route you want to plate the middleware within

Route.get('my_resources/:id', 'MyResourceController.show').as('my_resources.show').middleware(['myMiddleware'])

Or using resource that autogenerates routes

Route.resource('my_resources', 'MyResourceController')
	.apiOnly()
	.middleware(new Map([
		[['show','update','destroy'], ['myMiddleware']]
	]))

Then in the middleware file

async handle ({ request }, next) {
	
	// do somethign with the request before advancing
	console.log('My Middleware Fired')

	// call next to advance the request
	await next()
}

authentication (πŸ’₯ wip)

Adonis provide JWT authentication out of the box, with 'auth' and 'guest' middleware

Configure routing for login and guard others

Route
  .post('auth', 'UserController.auth')
  .middleware('guest')

Route.resource('my_resources', 'MyResourceController')
	.apiOnly()
	.middleware(['auth'])

Create and configure User controller (model and starter migration are out of the box)

adonis make:controller User --resource
> For HTTP requests

Then in the user controller

class UserController {

	async auth ({ request, response, auth }) {
		const { email, password } = request.only(['email', 'password'])
		
		const jwt = await auth.withRefreshToken().attempt(email, password)

		// api response
		response.json({
			message: 'Success',
			data: jwt
		})
	}
}

Like so, we receive a JWT token that will need to be passed throu Headers in requests to get authorization for guarded routes.
🎈 We also get a refresh token to use to keep log-in after the base JWT expires.

In Headers request:
Authorization = Bearer <mytoken>

🎈 There is no manual logout with JWT authentication, but we can configure token expiration time

In auth.js

jwt: {
	serializer: 'lucid',
	model: 'App/Models/User',
	scheme: 'jwt',
	uid: 'email',
	password: 'password',
	options: {
		secret: Env.get('APP_KEY'),
		// custom token duration (in seconds)
		expiresIn: 60
	}
}

About

Cheatsheet for AdonisJs (https://adonisjs.com) for simple API creation and management, with personal notes.

Resources

Stars

24 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors