Skip to content

Instantly share code, notes, and snippets.

@Jessmaxim303
Last active July 15, 2020 19:41
Show Gist options
  • Save Jessmaxim303/5c3ff89859794cdbc8069b9898d0df18 to your computer and use it in GitHub Desktop.
Save Jessmaxim303/5c3ff89859794cdbc8069b9898d0df18 to your computer and use it in GitHub Desktop.
### 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
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment