22
Créer un RESTFul API avec AdonisJS 5.0 (incluant l'authentification par token)
Si vous désirez plus de contenu francophone comme celui-ci, cliquer Follow ou suivez-moi sur Twitter
AdonisJS est à Javascript ce que Laravel est à PHP. Adonis est donc un framework backend construit avec l'idée d'apporter une expérience développeur (DX) extraordinaire.
Adonis rend disponible tous les outils que vous avez besoin pour bâtir une application fullstack de A à Z
Aujourd'hui vous allez découvrir comment bâtir un RESTful API avec Adonis. Vous allez même découvrir comment intégré l'authentification par token à votre API.
L'API que vous allez construire est une gestion de tâches. Vous aller créer un API pour créer des tâches, lire, effacer et mettre à jour des tâches
Vous allez également créer un API qui pourra créer un user et faire l'authentification. Ce dernier point peut vous sembler compliqué mais en faite Adonis à un package Auth qui s'occupe de presque tout.
Pré-requis
Avoir des connaissances de base sur les Restful API et sur les frameworks backend MVC
Partie 1 : Création du projet et de l'authentification
Créer un nouveau projet Adonis
$ npm init adonis-ts-app@latest my-project-name
choisir project structure API
Une fois le project créé, vous pouvez lancer le serveur local :
cd my-project-name
node ace serve -w
Installer et configurer le module de base de données
npm i @adonisjs/lucid
node ace configure @adonisjs/lucid
choisir SQLite
Ici Adonis va créer une base de donnée SQLite qui sera pré-configuré et accessible de votre application
Installer and configurer le module auth
npm i @adonisjs/auth
node ace configure @adonisjs/auth
- choisir Lucid
- Saisir modèle User
- choisir API Tokens
- choisir créer la migration
- choisir utiliser une base de donnée et créer la table pour stocker les Tokens
Le module Auth va vous permettre faire un login avec token
Ajouter le champ username au modèle User
(app/models/user.ts)
@column()
public username: string
Par défaut le champ username n'est pas créer, vous allez donc en créer un.
Ajouter le champ username au fichier de migration User
(database/migrations/xxxxxxxxx_users.ts)
table.string('username', 255).notNullable()
table.string('email', 255)->notNullable().unique()
Lancer la migration (afin de créer la table users)
node ace migration:run
La migration s'occupe de créer et mettre à jour les tables et champs de votre base de données.
Installer le module pour hasher le mot de passe
npm i phc-argon2
Ce module vous servira à crypter le mot de passe de l'utilisateur
Création de la route post pour permettre d'ajouter un User
(start/routes.ts)
Route.post('users', 'AuthController.register')
Création du validator:
(validators/Auth/StoreUserValidator.ts)
node ace make:validator Auth/StoreUser
Les Validator permettent d'annuler un requête si cette requête ne passe pas la validation.
Les Validator retournent également un message d'erreur de validation si la validation de passe pas
import { schema, rules } from @ioc:Adonis/Core/Validator
public schema = schema.create({
email: schema.string({ trim: true }, [
rules.email(),
rules.unique({ table: 'users', column: 'email ' }),
]),
username: schema.string({ trim: true }),
password: schema.string(),
})
Création du controller
node ace make:controller Auth
Les controller contient toutes les fonctions que les routes vont exécuter
Ajouter une fonction 'register' au controller
(app/controllers/Http/AuthController.ts)
public async register({ request, response } : HttpContextContract) {
const payload = await request.validate(StoreUserValidator)
const user = await User.create(payload.user)
return response.created(user) // 201 CREATED
}
Cette fonction permet de créer un 'user' selon les informations soumis par l'API (email, username et password)
Login
Route.post('users/login', 'AuthController.login')
Création du validator: Validators/Auth/LoginValidator.ts
node ace make:validator Auth/Login
import { schema, rules } from @ioc:Adonis/Core/Validator
public schema = schema.create({
email: schema.string({}, [rules.email()]),
password: schema.string()
})
Création de la fonction login dans le controller Auth
public async login({ auth, request, response }: HttpContextContract) {
const { email, password } = await request.validate(LoginValidator)
const token = await auth.attempt(email, password)
const user = auth.user!
return response.ok({
"token": token,
...user.serialize(),
})
}
Cette fonction s'occupe de faire l'authentification et retourne un token que le client pourra utiliser pour accéder aux routes protégé.
Get user (start/route.ts)
Route.get('user', 'AuthController.me').middleware(['auth'])
public async me({auth, response} : HttpContextContract) {
return response.ok({ auth.user })
}
Auth middleware (start/kernel.ts)
Server.middleware.registerNamed({ auth: () => import('App/Middleware/Auth') })
Création du middleware qui permettre de vérifier le token
Création de la route put pour update User
Route.put('users', 'AuthController.update').middleware(['auth'])
Création du validator: Validators/Auth/UpdateUserValidator.ts
node ace make:validator Auth/UpdateUser
import { schema, rules } from @ioc:Adonis/Core/Validator
public schema = schema.create({
email: schema.string.optional({ trim: true }, [
rules.email(),
rules.unique({ table: 'users', column: 'email' }),
]),
username: schema.string.optional({ trim: true }),
password: schema.string.optional(),
})
Création de la fonction update du controller Auth
(app/Controllers/Http/AuthController.ts)
public async update({ auth, request, response } : HttpContextContract) {
const payload = await request.validate(UpdateUserValidator)
const user = await auth.user!.merge(payload).save()
return response.ok(user) // 200 OK
}
Partie 2 - Création du modèle Task
Création du modèle, de la migration et du controller
node ace make:model Task -cm
L'option -cm va créer le fichier de la migration et le fichier du controller
Ouvrir la migration et ajouter les champs voulus:
public async up () {
this.schema.createTable(this.tableName, (table) => {
table.increments('id')
table.integer('user_id').unsigned().references('users.id').onDelete('CASCADE')
table.string('name').notNullable()
table.boolean('is_done').defaultTo(false)
table.timestamp('created_at', { useTz: true })
table.timestamp('updated_at', { useTz: true })
})
}
Ouvrir le modèle et ajouter les columns et la relation belongTo
import { DateTime } from 'luxon'
import { BaseModel, BelongsTo, belongsTo, column, hasMany, HasMany } from '@ioc:Adonis/Lucid/Orm'
import User from './User'
export default class Task extends BaseModel {
@column({ isPrimary: true })
public id: number
@column.dateTime({ autoCreate: true })
public createdAt: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true })
public updatedAt: DateTime
@column()
public name: string
@column()
public is_done: boolean
@belongsTo(() => User)
public user: BelongsTo<typeof User>
}
Ouvrir le fichier modèle User et ajouter le HasMany
@hasMany(() => Task)
public tasks: HasMany<typeof Task>
Créer les routes pour le CRUD de Tasks
Route.resource('tasks', 'TaskController').apiOnly()
// Cette ligne de code va créer 5 chemin urls pour le CRUD
// Liste des tâches: GET /tasks (tasks.index)
// Sauvegarder une tâches: POST /tasks (tasks.store)
// Lire une tâche: GET tasks/:id (tasks.show)
// Mise à jour d'une tâche: PUT tasks/:id (tasks.update)
// Effacer une tâche: DELETE tasks/:id (tasks.destroy)
Dans le fichier TasksController créer les 5 actions CRUD
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Task from 'App/Models/Task'
import StoreTaskValidator from 'App/Validators/Tasks/StoreTaskValidator'
import UpdateTaskValidator from 'App/Validators/Tasks/UpdateTaskValidator'
export default class TasksController {
public async index ({response}: HttpContextContract) {
const tasks = await Task.all()
return response.ok(tasks)
}
public async store ({ request, response }: HttpContextContract) {
const payload = await request.validate(StoreTaskValidator)
const task = await Task.create(payload)
return response.created(task)
}
public async show ({ response, params }: HttpContextContract) {
console.log(params)
const task = await Task.findOrFail(params.id)
return response.ok(task)
}
public async update ({ request, response, params }: HttpContextContract) {
const task = await Task.findOrFail(params.id)
const payload = await request.validate(UpdateTaskValidator)
task.merge(payload).save()
return response.ok(task)
}
public async destroy ({ response, params }: HttpContextContract) {
const task = await Task.findOrFail(params.id)
task.delete()
return response.ok(task)
}
}
Créer le StoreTaskValidator
node ace make:validator Tasks/StoreTask
public schema = schema.create({
name: schema.string(),
is_done: schema.boolean(),
})
Créer le UpdateTaskValidator
node ace make:validator Tasks/UpdateTask
public schema = schema.create({
name: schema.string.optinal(),
is_done: schema.boolean.optional(),
})
Conclusion
Comme vous avez sans douter constater, la syntaxe de Adonis pour créer un Restful API est très propre. Adonis est un des premiers, sinon le premier framework javascript qui rappel le plaisir et l'efficacité pour les développeurs d'utiliser un framework comme Laravel.
22