VUE API QUERY: Forma simples e elegante de criar requisições para API REST

Este pacote ajuda a construir rapidamente requisições para API REST. Mova sua lógica e requisições ao backend para classes dedicadas. Matenha seu código simples e elegante.

🔥 Se você usa Laravel como backend, este pacote combina perfeitamente com spatie/laravel-query-builder.

Repositório oficial

O conteúdo a seguir foi publicado como um artigo no site VUE JS BRASIL. Você deve acompanhar todas as futuras atualizações desta biblioteca diretamente no repositório oficial.

🔗 https://github.com/robsontenorio/vue-api-query

Uso básico

Devolva o resultado para determinados critérios, inclua algumas entidades, acrescente alguns campos extras e ordene-o!

// GET /posts?filter[status]=ACTIVE&include=user,category&append=likes&orderBy=-created_at,category_id

let posts = await Post
  .where('status', 'ACTIVE')
  .include('user', 'category')
  .append('likes')
  .orderBy('-created_at', 'category_id')  
  .get()

Agora dê-me apenas a primeira ocorrência do resultado:

// GET /posts?filter[status]=ACTIVE

let post = await Post
  .where('status', 'ACTIVE')
  .first()

Legal! Agora eu quero um objeto específico:

// GET /posts/1

let post = await Post.find(1)

Edite isso e envie o objeto devolta ao backend:

// PUT /posts/1 

post.title = 'Awsome!'
post.save()

Ops, vamos apagar!

// DELETE /posts/1

post.delete()

Vamos criar um novo objeto e postá-lo:

let post = new Post()

// ou

let post = new Post({title: 'Cool!'})


// POST /post

post.title = 'Another one'
post.save()

Nós podemos usar relacionamentos:


// GET /users/1
let user = await User.find(1)

// GET users/1/posts
let posts = await user
  .posts()
  .get()

Instalação

yarn add vue-api-query

NUXT

Crie um plugin ~/plugins/vue-api-query.js

// injete a instância global do axios como cliente http do Model

import { Model } from 'vue-api-query'

export default function (ctx, injext) {  
  Model.$http = ctx.$axios
}

E registre-o em nuxt.config.js

plugins: [
  '~plugins/vue-api-query'
]

VUE

Configure em src/main.js

[...]

import axios from 'axios'
import { Model } from 'vue-api-query'

// injete a instância global do axios como cliente http do Model
Model.$http = axios

[...]

Configuração

Defina um modelo base

Seu modelo base deve estender da classe Model do pacote vue-api-query. Usar modelos base é uma boa prática uma vez que as configurações dos seus modelos de domínios são abstraídas.

models/Model.js

import { Model as BaseModel } from 'vue-api-query'

export default class Model extends BaseModel {

  // defina uma url base para a API REST
  baseURL () {
    return 'http://my-api.com'
  }

  // implemente o método de request padrão
  request (config) {
    return this.$http.request(config)
  }
}

Defina seus modelos de domínio

Apenas estenda do seu modelo base … e pronto!

Ele automaticamente pluraliza baseado no nome da classe. Então, o recurso base da API REST para a classe User seria /users.

models/User.js

import Model from './Model'

export default class User extends Model {

}

Se você precisa customizar o nome do recurso implemente o método resource().

import Model from './Model'

export default class User extends Model {

  resource()
  {
    return 'userz'
  }

}

Claro que você pode adicionar métodos extras e propriedades computadas desse jeito:

import Model from './Model'

export default class User extends Model {
  
  // propriedades computadas são reativas -> user.fullname
  // certifique-se de usar o prefixo "get"
  get fullname()
  {
    return `${this.firstname} ${this.lastname}`
  }

  // método -> user.makeBirthday()
  makeBirthday()
  {
    this.age += 1
  }

}

Você pode configurar relacionamentos:

import Model from './Model'
import Post from './Post'

export default class User extends Model {

  posts () {
    return this.hasMany(Post)
  }
}

Tudo bem se, em algumas situações, você precisar chamar um recurso personalizado de um modelo já definido. Você pode sobrescrever dinamicamente o recurso padrão chamando o método custom ().

// GET /posts
let posts = await Post.get()

// GET /posts/latest
let latest = await Post
  .custom('posts/latest')
  .first()  

Exemplo completo

/models/Post.js

import Model from './Model'

export default class Post extends Model {
  // pronto :)
}

/models/User.js

import Model from './Model'
import Post from './Post'

export default class User extends Model {  
  posts () {
    return this.hasMany(Post)
  }

  // propriedades computadas :)
  get fullname()
  {
    return `${this.firstname} ${this.lastname}`
  }

  // métodos :)
  makeBirthday()
  {
    this.age += 1
  }
}

Se o backend responde com …

// response from API for /users/1
{
  id: 1,
  firstname: "John",
  lastname: "Doe",
  age: 25
}

Nós podemos fazer isso:

//GET /users/1
let user = await User.find(1)

console.log(user.fullname) // John Doe

user.makeBirthday()
user.save()

Então o método save() enviará de volta ao backend o seguinte payload:

// PUT /users/1
{
  firstname: "John",
  lastname: "Doe",
  age: 26 //<--- alterado
}

Explore os relacionamentos:

// GET /users/1
let user = await User.find(1)

// GET /users/1/posts
let posts = await user.posts().get()

// Sim, você pode fazer isso antes de recuperar os posts do usuário
let posts = await user
  .posts()
  .where(...)
  .append(...)
  .include(...)
  .orderBy(...)
  .get()

Você támbem pode fazer isso:

//GET /posts?filter[status]=ACTIVE,ARCHIVED

let posts = await Post
  .whereIn('status', ['ACTIVE', 'ARCHIVED'])
  .get()

Se você gosta do estilo “promessa”, faça assim:


// objeto único

let user

User
  .where('status', 'ACTIVE')
  .first()
  .then(response => {
    user = response
  })

// array de objetos

let users

User
  .where('status', 'ACTIVE')
  .get()
  .then(response => {
    users = response
    
    // ou (dependendo da resposta do backend)

    users = response.data 
  })

E em alguma página/componente:

<template>
  User: 
  <code>
    
  </code>

  Posts from user:
  <code>
    
  </code>
</template>
<script>
import User from '@/models/User'

export default {
  data()
  {
    return {
      user: {},
      posts: {}
    }
  },
  async mounted()
  {
    this.user = await User.find(1)
    this.posts = await this.user.posts().get()
  }
}
</script>

Paginação

// GET /users?sort=firstname&page=1&limit=20

let users = await User        
        .orderBy('firstname')
        .page(1) 
        .limit(20)
        .$get() // algumas vezes você vai preferiar usar $get()

Dica legal

Você pode construir algo como queries com escopo:

import Model from './Model'

export default class Post extends Model {
    
   // certifique-se de declarar como um método estático
   static active()  
   {
      // aqui você poderia encadear mais métodos de vue-query-api
      return this.where('status', 'active')
   }
}

Então, você pode fazer isso:

let activePosts = await Post
  .active()
  .get()

Resposta do backend

Este pacote manipula automaticamente a resposta do backend e converte-a em uma instância de tal modelo.

Objeto único

Se o seu backend responder com um único objeto como ELEMENTO RAIZ desse jeito:

{
  id: 1,
  firstname: 'John',
  lastname: 'Doe',
  age: 25
}

Então, os métodos find() e first() automaticamente converterão a resposta de backend em uma instância do modelo User.

let user = await User.find(1)

// ou

let user = await User.first()

// funcionará, porque uma instância de User foi criada a partir da resposta

user.makeBirthday()

Isto NÃO SERÁ convertido em uma instância do modelo User, porque os dados principais não estão no elemento raiz.

user: {  
    id: 1,
    firstname: 'John',
    lastname: 'Doe',
    age: 25
}

Array de objetos

Um array de itens do backend seria convertido da mesma forma, SOMENTE se responder nesses formatos:

let user = await User.get()
// funciona - array de objeto é o elemento raiz
[
  {
    id: 1,
    firstname: 'John',
    lastname: 'Doe',
    age: 25
  },
  {
    id: 2,
    firstname: 'Mary',
    lastname: 'Doe',
    age: 22
  }
]
// funciona - `data` existe na raiz e contém o array de objetos
{
  data: [
    {
      id: 1,
      firstname: 'John',
      lastname: 'Doe',
      age: 25
      },
    {
      id: 2,
      firstname: 'Mary',
      lastname: 'Doe',
      age: 22
    }
  ],
  someField: '',
  anotherOne: '',  
}

// Normalmente você lidaria com a resposta assim

let response = User.get()
let users = response.data


// ou assim

const { data } = User.get()
let users  = data

// mas você pode usar "fetch style request" com o método "$get()"

let users = await User
  .where('status', 'ACTIVE')
  .$get() // <---- AQUI

Isto NÃO SERÁ em uma array de modelos do tipo User.

{
  users: [
    {
      id: 1,
      firstname: 'John',
      lastname: 'Doe',
      age: 25
      },
    {
      id: 2,
      firstname: 'Mary',
      lastname: 'Doe',
      age: 22
    }
  ],
  someField: '',
  anotherOne: '',  
}

Agradecimentos

Por quê outro pacote se já temos esses? Porque atualmente (março de 2018) eles restringem a resposta do backend à especificação JSON API.

Contato

Twitter @robsontenorio