DEV Community

Tobias Mesquita for Quasar Framework Brasil

Posted on

QPANC - Parte 14 - Quasar - Consumindo a API

QPANC são as iniciais de Quasar PostgreSQL ASP NET Core.

30 Axios

Durante a criação do projeto do Quasar, você pode optar por criar ou não o boot do axios, mas mesmo que tenha o criado, ele não está adequado para uma aplicação SSR.

O nosso primeiro passo, será configurar uma rede interna no arquivo de configuração do docker-compose, para que o aplicativo do Quasar possa se comunicar com a API.

QPANC/docker-compose.yml

services:
  qpanc.api:
    networks:
      qpanc.network:
      qpanc.internal:
        ipv4_address: "172.18.18.3"

  qpanc.app:
    networks:
      qpanc.network:
      qpanc.internal:
        ipv4_address: "172.18.18.6"

  qpanc.database:
    networks:
      qpanc.network:
      qpanc.internal:
        ipv4_address: "172.18.18.9"

networks:
  qpanc.internal:
    internal: true
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.18.18.0/24
  qpanc.network:

Durante o desenvolvimento, o comando quasar dev não é executado durante a construção da imagem, ele será executado quando tentamos levantar o container com a imagem de desenvolvimento.

por isto, precisamos setar as variáveis API_CLIENT_URL e API_SERVER_URL direto no comando, junto ao quasar dev. lembrando que esta alteração deve ser feita no docker-compose.override.yml.

QPANC/docker-compose.override.yml

services:
  qpanc.app:
    command: /bin/sh -c "yarn && API_CLIENT_URL=https://localhost:34513/ API_SERVER_URL=https://172.18.18.3:443/ quasar dev -m ssr"

Agora, precisamos modificar o quasar.config.js > build > extendWebpack, para injetamos a URL da API.

QPANC.App/quasar.config.js

const { DefinePlugin } = require('webpack')

module.exports = function (ctx) {
  return {
    build: {
      extendWebpack (cfg, { isServer }) {
        const definePlugin = cfg.plugins.find(plugin => plugin instanceof DefinePlugin)
        const apiUrl = JSON.stringify(isServer ? process.env.API_SERVER_URL : process.env.API_CLIENT_URL)
        definePlugin.definitions['process.env'].API_URL = apiUrl
      }
    }
  }
}

Estamos usando o extendWebpack, pois o API_URL deve ter um valor diferente no build: CLIENT quando comparado com o build: SERVER, quando não é necessário fazer esta diferenciação, podemos usar o quasar.config.js > build > env.

Agora, voltemos a nossa atenção para o boot do axios.

import axios from 'axios'
import inject from './inject'
import { Notify } from 'quasar'

export default inject(({ store, router }) => {
  const instance = axios.create({
    baseURL: process.env.API_URL
  })

  instance.interceptors.request.use((config) => {
    const token = store.state.app.token
    const locale = store.getters['app/locale']
    config.headers['Accept-Language'] = locale
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  }, (error) => {
    return Promise.reject(error)
  })

  instance.interceptors.response.use((response) => {
    return response
  }, (error) => {
    const { status } = error.response || {}
    let message = store.$t('http.generic')
    switch (status) {
      case 400: message = store.$t('http.badRequest'); break
      case 401: message = store.$t('http.unauthorized'); break
      case 403: message = store.$t('http.forbidden'); break
      case 422:
        if (error.response.data) {
          const { title, errors } = error.response.data
          message = title
          message += '<ul>'
          for (const key in errors) {
            const error = errors[key]
            for (const msg of error) {
              message += '<li>' + msg + '</li>'
            }
          }
          message += '</ul>'
          store.$root.$emit('unprocessable', errors)
        } else {
          message = store.$t('http.unprocessable')
        }
        break
      case 500: message = store.$t('http.serverError'); break
      case 503: message = store.$t('http.serviceUnavailable'); break
    }
    Notify.create({
      type: 'negative',
      html: true,
      message: message
    })
    if (status === 401) {
      store.commit('app/token', undefined)
      router.push('/login')
    }
    return Promise.reject(error)
  })

  return {
    axios: instance
  }
})

Um pequeno detalhamento sobre o que está sendo feito acima:

  const instance = axios.create({
    baseURL: process.env.API_URL
  })

Aqui estamos verificando se estamos do lado do servidor ou do cliente, e criando uma instancia do axios com a URL apropriada.

  instance.interceptors.request.use((config) => {
    const token = store.state.app.token
    const locale = store.getters['app/locale']
    config.headers['Accept-Language'] = locale
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  }, (error) => {
    return Promise.reject(error)
  })

Aqui estamos interceptando todas as requisições feitas com o axios, e adicionando o header de autenticação Authorization: Bearer ${token}, assim como a linguagem definida pelo usuário (que pode ser diferente da do browser/OS).

  instance.interceptors.response.use((response) => {
    return response
  }, (error) => {
    const { status } = error.response || {}
    let message = i18n.t('http.generic')
    switch (status) {
      case 400: message = store.$t('http.badRequest'); break
      case 401: message = store.$t('http.unauthorized'); break
      case 403: message = store.$t('http.forbidden'); break
      case 422:
        ...
        break
      case 500: message = store.$t('http.serverError'); break
      case 503: message = store.$t('http.serviceUnavailable'); break
    }
    Notify.create({
      type: 'negative',
      html: true,
      message: message
    })
    if (status === 401) {
      ...
    }
    return Promise.reject(error)
  })

Neste ponto, estamos interceptando as requisições que falharam, e exibindo uma notificação não intrusiva.

      case 422:
        if (error.response.data) {
          const { title, errors } = error.response.data
          message = title
          message += '<ul>'
          for (const key in errors) {
            const error = errors[key]
            for (const msg of error) {
              message += '<li>' + msg + '</li>'
            }
          }
          message += '</ul>'
          store.$root.$emit('unprocessable', errors)
        } else {
          message = store.$t('http.unprocessable')
        }
        break

Aqui, estamos adicionando um tratamento adicional para o erro 422, estaremos personalizando a notificação para exibir os erros retornados pela API, assim como, emitindo um erro com estes eventos. Não estamos usando o $t para localizar a mensagem, por que a API já está retornando o texto localizado.

Este evento será interceptado pelos componentes, para que eles possam atualizar a UI para exibir o erro da maneira correta.

    if (status === 401) {
      store.commit('app/token', undefined)
      router.push('/login')
    }

E caso a aplicação retorne um 401, possivelmente houve a tentativa de acessar um recurso protegido usando um token invalido, note que, uma falha na autorização resultaria em um error 403

E como estamos regionalizando as mensagens de erro, precisamos incluir os devidos textos os arquivos de internacionalização.

Quasar.App/src/i18n/en-us/index.js

export default {
  http: {
    generic: 'Something not right happened',
    badRequest: 'We aren\'t able of to do your request, please review all the fields',
    unauthorized: 'You aren\'t authorized, please login',
    forbidden: 'You aren\'t allowed to do that action',
    unprocessable: '@:http.badRequest',
    serverError: 'An unexpected error occurred at the API',
    serviceUnavailable: 'An error occurred at the API, mostly because a third party service'
  }
}

Quasar.App/src/i18n/pt-br/index.js

export default {
  http: {
    generic: 'Algo de errado aconteceu',
    badRequest: 'Não foi possivel realizar esta ação, por favor revise os campos',
    unauthorized: 'Você não está logado',
    forbidden: 'Você não está autorizado a realizar esta ação',
    unprocessable: '@:http.badRequest',
    serverError: 'Ocorreu um erro inesperado na API',
    serviceUnavailable: 'Ocorreu um erro na API, possivelmente ao tentar acessar um serviço externo'
  }
}

Heroku

Amplify your impact where it matters most — building exceptional apps.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (0)

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

👋 Kindness is contagious

Explore a trove of insights in this engaging article, celebrated within our welcoming DEV Community. Developers from every background are invited to join and enhance our shared wisdom.

A genuine "thank you" can truly uplift someone’s day. Feel free to express your gratitude in the comments below!

On DEV, our collective exchange of knowledge lightens the road ahead and strengthens our community bonds. Found something valuable here? A small thank you to the author can make a big difference.

Okay