Last active
July 15, 2020 19:41
-
-
Save Jessmaxim303/5c3ff89859794cdbc8069b9898d0df18 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
### 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