Últimas entradas

Creacion de REST API con NodeJS, AdonisJS y JWT

AdonisJS es un framework orientado al desarrollo web, basado en Node.js, cuenta con una serie de características inspiradas en frameworks populares como: Ruby on Rails y Laravel.

En el siguiente post se explicara la forma de crear un api-rest usando AdonisJS, como framework de back-end, consumiendo información de usuarios y proyectos, almacenados en una base de datos MySQL, para tener consideraciones para el desarrollo de una api se puede revisar la siguiente entrada: Claves para desarrollar con API REST

Requerimientos

  • Node.js >= 8.0.0
  • npm >= 3.0.0
  • git
  • MySQL 5.x

Instalación


[root@jorgedison adonis]# adonis new rest-api --api-only
    _       _             _         _     
   / \   __| | ___  _ __ (_)___    | |___ 
  / _ \ / _` |/ _ \| '_ \| / __|_  | / __|
 / ___ \ (_| | (_) | | | | \__ \ |_| \__ \
/_/   \_\__,_|\___/|_| |_|_|___/\___/|___/

  [1/6] 🔬  Requirements matched [node & npm]
  [2/6] 🔦  Ensuring project directory is clean [rest-api]
  [3/6] 📥  Cloned [adonisjs/adonis-api-app]
  [4/6] 📦  Dependencies installed
  [5/6] 📖  Environment variables copied [.env]
  [6/6] 🔑  Key generated [adonis key:generate]

🚀   Successfully created project
👉   Get started with the following commands

$ cd rest-api
$ adonis serve --dev

Rutas

Adonisj maneja las rutas en el archivo route.js, ubicado en dentro de la carpeta start, para nuestra demostración estas serían las rutas, verbos, controladores y funciones a utilizar


'use strict'

const Route = use('Route')

Route.group(() => {
  Route.post('usuarios/', 'UserController.store');
  Route.post('usuarios/login', 'UserController.login');
  Route.get('usuarios/', 'UserController.index');
  Route.get('proyectos', 'ProyectoController.index').middleware('auth');
  Route.post('proyectos', 'ProyectoController.create').middleware('auth');
  Route.patch('proyectos/:id', 'ProyectoController.update').middleware('auth');
  Route.delete('proyectos/:id', 'ProyectoController.destroy').middleware('auth');
}).prefix('api/v1/');

Podemos hacer uso del comando adonis route:list para visualizar todas las rutas que se tienen definidas en nuestro proyecto


[root@jorgedison rest-api]# adonis route:list
┌────────────────────────┬──────────┬────────────────────────────┬────────────┬─────────────────┬────────┐
│ Route                  │ Verb(s)  │ Handler                    │ Middleware │ Name            │ Domain │
├────────────────────────┼──────────┼────────────────────────────┼────────────┼─────────────────┼────────┤
│ /api/v1/usuarios       │ POST     │ UserController.store       │            │ /usuarios       │        │
├────────────────────────┼──────────┼────────────────────────────┼────────────┼─────────────────┼────────┤
│ /api/v1/usuarios/login │ POST     │ UserController.login       │            │ /usuarios/login │        │
├────────────────────────┼──────────┼────────────────────────────┼────────────┼─────────────────┼────────┤
│ /api/v1/usuarios       │ HEAD,GET │ UserController.index       │            │ /usuarios       │        │
├────────────────────────┼──────────┼────────────────────────────┼────────────┼─────────────────┼────────┤
│ /api/v1/proyectos      │ HEAD,GET │ ProyectoController.index   │ auth       │ /proyectos      │        │
├────────────────────────┼──────────┼────────────────────────────┼────────────┼─────────────────┼────────┤
│ /api/v1/proyectos      │ POST     │ ProyectoController.create  │ auth       │ /proyectos      │        │
├────────────────────────┼──────────┼────────────────────────────┼────────────┼─────────────────┼────────┤
│ /api/v1/proyectos/:id  │ PATCH    │ ProyectoController.update  │ auth       │ /proyectos/:id  │        │
├────────────────────────┼──────────┼────────────────────────────┼────────────┼─────────────────┼────────┤
│ /api/v1/proyectos/:id  │ DELETE   │ ProyectoController.destroy │ auth       │ /proyectos/:id  │        │
└────────────────────────┴──────────┴────────────────────────────┴────────────┴─────────────────┴────────┘

Controladores

En nuestra demostración usaremos 02 controladores, donde se manejará la lógica de los usuarios y proyectos.


[root@jorgedison rest-api]# adonis make:controller User
> Select controller type For HTTP requests
  create  app/Controllers/Http/UserController.js
[root@jorgedison rest-api]# adonis make:controller Proyecto
> Select controller type For HTTP requests
  create  app/Controllers/Http/ProyectoController.js

Controlador UserController.js


'use strict'

const User = use('App/Models/User');

class UserController {
  async login({ request, auth }) {
    const { email, password } = request.all();
    const token = await auth.attempt(email, password);
    return token;
  }

  async store({ request }) {
    const { email, password } = request.all();
    const user = await User.create({
      email,
      password,
      username: email
    });
    return this.login(...arguments);
  };

  async index({ request }) {
    return await User.all()
  }

}

module.exports = UserController

Controlador ProyectoController.js


'use strict'

const Proyecto = use('App/Models/Proyecto');
const AutorizacionService = use('App/Services/AutorizacionService');

class ProyectoController {
  async index({ auth }) {
    const user = await auth.getUser();
    return await user.proyectos().fetch();
  }

  async create({ auth, request }) {
    const user = await auth.getUser();
    const { nombre } = request.all();
    const proyecto = new Proyecto();
    proyecto.fill({
      nombre
    });
    await user.proyectos().save(proyecto);
    return proyecto;
  }

  async destroy({ auth, params}) {
    const user = await auth.getUser();
    const { id } = params;
    const proyecto = await Proyecto.find(id);
    AutorizacionService.verificarPermiso(proyecto, user);
    await proyecto.delete();
    return proyecto;
  }

  async update ({ auth, params, request}) {
    const user = await auth.getUser();
    const { id } = params;
    const proyecto = await Proyecto.find(id);
    AutorizacionService.verificarPermiso(proyecto, user);
    proyecto.merge(request.only('nombre'));
    await proyecto.save();
    return proyecto;
  }

}

module.exports = ProyectoController

Migraciones

Para la comunicación con la base de datos utilizaremos MySQL, sin embargo podemos utilizar cualquiera de las bases de datos indicadas en la documentación de Adonisjs


[root@jorgedison adonis]# npm install pg --save
[root@jorgedison adonis]# npm install sqlite3 --save
[root@jorgedison adonis]# npm install mysql --save

Por defecto, se migrara las tablas de usuarios y token, esto es debido a que los modelos son generados al iniciar el proyecto.


[root@jorgedison rest-api]# adonis migration:run
migrate: 1503250034279_user.js
migrate: 1503250034280_token.js
Database migrated successfully in 3.08 s

Modelos

Usaremos 3 modelos para la demostración, User.js para el manejo de usuarios, Proyecto.js para el de proyectos y Token.js para la seguridad via JWT


[root@jorgedison rest-api]# adonis make:model Proyecto -m
✔ create  app/Models/Proyecto.js
✔ create  database/migrations/1590817474371_proyecto_schema.js

Modelo Userjs


'use strict'

const Model = use('Model')
const Hash = use('Hash')

class User extends Model {
  static boot () {
    super.boot()

    this.addHook('beforeSave', async (userInstance) => {
      if (userInstance.dirty.password) {
        userInstance.password = await Hash.make(userInstance.password)
      }
    })
  }

  tokens () {
    return this.hasMany('App/Models/Token')
  }

  proyectos () {
    return this.hasMany('App/Models/Proyecto')
  }
}

module.exports = User

Modelo Proyecto.js


'use strict'

/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */
const Model = use('Model')

class Proyecto extends Model {
  user () {
    return this.belongsTo('App/Models/User')
  }

  tareas () {
    return this.hasMany('App/Models/Tarea')
  }

}

module.exports = Proyecto

Para ejecutar la migración de la entidad proyecto, antes debemos realizar la migración.


'use strict'

/** @type {import('@adonisjs/lucid/src/Schema')} */
const Schema = use('Schema')

class ProyectoSchema extends Schema {
  up () {
    this.create('proyectos', (table) => {
      table.increments()
      table.integer('user_id').unsigned().references('id').inTable('users')
      table.string('nombre', 80).notNullable()
      table.timestamps()
    })
  }

  down () {
    this.drop('proyectos')
  }
}
module.exports = ProyectoSchema

[root@jorgedison rest-api]# adonis migration:run
migrate: 1590817474371_proyecto_schema.js
Database migrated successfully in 1.73 s

Servicios y Excepciones

Adonisjs nos proporciona la posibilidad de usar servicios y excepciones para controlar los permisos y tipos respuestas que son enviadas hacia nuestra api, para la demostración usaremos 02 excepciones para el manejo de los códigos de estado 403, acceso prohibido, y 404, recurso no encontrado.

Crearemos un servicio AutorizacionService.js para el control de permisos, de tal forma que pueda ser reutilizado dentro de nuestro proyecto, se realizará el uso de excepciones acceso prohibido y recurso no encontrado.


const AccesoProhibidoException = use('App/Exceptions/AccesoProhibidoException');
const RecursoNoEncontradoException = use('App/Exceptions/RecursoNoEncontradoException');

class AutorizacionService {
  verificarPermiso(recurso, user) {
    if (!recurso){
      throw new RecursoNoEncontradoException();
    }

    if (recurso.user_id !== user.id) {
      throw new AccesoProhibidoException();
    };
  }
}

module.exports = new AutorizacionService();

Creamos las excepciones vía terminal


[root@jorgedison rest-api]# adonis make:exception AccesoProhibido
✔ create  app/Exceptions/AccesoProhibidoException.js
[root@jorgedison rest-api]# adonis make:exception RecursoNoEncontrado
✔ create  app/Exceptions/RecursoNoEncontradoException.js

Excepción AccesoProhibidoException.js


'use strict'

const { LogicalException } = require('@adonisjs/generic-exceptions')

class AccesoProhibidoException extends LogicalException {
  /**
   * Handle this exception by itself
   */
  handle (error, { response }) {
    return response.status(403).json({
      error: 'Acceso No permitido al recurso'
    });
  }
}

module.exports = AccesoProhibidoException

Excepción RecursoNoEncontradoException.js


'use strict'

const { LogicalException } = require('@adonisjs/generic-exceptions')

class RecursoNoEncontradoException extends LogicalException {
  /**
   * Handle this exception by itself
   */
  handle (error, { response }) {
    return response.status(404).json({
      error: 'El recurso no existe'
    });
  }
}

module.exports = RecursoNoEncontradoException

Para probar los endpoint, se puede hacer uso de herramientas como Postman, para el caso de invocar desde la terminal se podrá hacer a través de CURL de acuerdo a lo siguiente:


curl -X GET http://127.0.0.1:3333/api/v1/usuarios/
curl -X POST http://127.0.0.1:3333/api/v1/usuarios/ -d '{"email": "prueba7@prueba.com", "password": "123456"}'
curl -X POST http://127.0.0.1:3333/api/v1/usuarios/login -d '{"email": "prueba5@prueba.com", "password": "123456"}'

curl -X GET http://127.0.0.1:3333/api/v1/proyectos -H 'authorization: Bearer token' 
curl -X POST http://127.0.0.1:3333/api/v1/proyectos -H 'authorization: Bearer token' -d '{ "nombre": "proyecto nuevo"}'
curl -X PATCH http://127.0.0.1:3333/api/v1/proyectos/2 -H 'authorization: Bearer token' -d '{"nombre":"proyecto_actualizado"}'
curl -X DELETE http://127.0.0.1:3333/api/v1/proyectos/1 -H 'authorization: Bearer token'

Repositio: https://github.com/jorgedison/api-rest-adonisjs
Fuente: https://adonisjs.c

Agregue un comentario

Su dirección de correo no se hará público. Los campos requeridos están marcados *