QPANC - Parte 14 - Quasar - Consumindo a API
Tags: quasar, postgres, aspnetcore, docker
Published: 2020-04-19 23:38:09

> **QPANC** são as iniciais de **Q**uasar **P**ostgreSQL **A**SP **N**ET **C**ore.

  * [Source](https://github.com/TobyMosque/qpanc)
  * [Introdução](https://dev.to/quasar-pt-br/arquiteturando-uma-aplicacao-com-quasar-postgresql-asp-net-core-e-docker-introducao-o7o)
  * [Parte I - ASP.NET - Inicializando os Projetos](https://dev.to/quasar-pt-br/qpanc-parte-1-asp-net-inicializando-os-projetos-lkm)
  * [Parte 2 - PostgreSQL](https://dev.to/quasar-pt-br/qpanc-parte-2-postgresql-51pb)
  * [Parte 3 - ASP.NET - Registrando Serviços e Lendo Variáveis de Ambiente](https://dev.to/quasar-pt-br/qpanc-parte-3-asp-net-registrando-servicos-e-lendo-variaveis-de-ambiente-3do2)
  * [Parte 4 - ASP.NET - Entity Framework e ASP.NET Core Identity](https://dev.to/quasar-pt-br/qpanc-parte-4-asp-net-entity-framework-e-asp-net-core-identity-40f9)
  * [Parte 5 - ASP.NET - Documentação Interativa com Swagger](https://dev.to/quasar-pt-br/qpanc-parte-5-asp-net-documentacao-interativa-com-swagger-4n0o)
  * [Parte 6 - ASP.NET - Regionalização](https://dev.to/quasar-pt-br/qpanc-parte-6-asp-net-regionalizacao-13p5)
  * [Parte 7 - ASP.NET - Autenticação e Autorização](https://dev.to/quasar-pt-br/qpanc-parte-7-asp-net-autenticacao-e-autorizacao-bpd)
  * [Parte 8 - ASP.NET - CORS](https://dev.to/quasar-pt-br/qpanc-parte-8-asp-net-cors-52dn)
  * [Parte 9 - Quasar - Criação e Configuração do Projeto](https://dev.to/quasar-pt-br/qpanc-parte-9-quasar-criacao-e-configuracao-do-projeto-5hbi)
  * [Parte 10 - Quasar - Configurações e Customizações](https://dev.to/quasar-pt-br/qpanc-parte-10-quasar-configuracoes-e-customizacoes-2i86)
  * [Parte 11 - Quasar - Componentes - Diferença entre SPA e SSR](https://dev.to/quasar-pt-br/qpanc-parte-11-quasar-componentes-diferenca-entre-spa-e-ssr-1a3c)
  * [Parte 12 - Quasar - Serviços](https://dev.to/quasar-pt-br/qpanc-parte-12-quasar-servicos-4e61)
  * [Parte 13 - Quasar - Regionalização e Stores](https://dev.to/quasar-pt-br/qpanc-parte-13-quasar-regionalizacao-e-stores-1kep)
  * [Parte 14 - Quasar - Consumindo a API](https://dev.to/quasar-pt-br/qpanc-parte-14-quasar-consumindo-a-api-fn6)
  * [Parte 15 - Quasar - Login](https://dev.to/quasar-pt-br/qpanc-parte-15-quasar-login-jc6)
  * [Parte 16 - Quasar - Áreas Protegidas](https://dev.to/quasar-pt-br/qpanc-parte-16-quasar-areas-protegidas-3jkn)
  * [Parte 17 - Quasar - Registro](https://dev.to/quasar-pt-br/qpanc-parte-17-quasar-registro-3b8c)
  * [Parte 18 - Docker - Maquina Virtual Linux](https://dev.to/quasar-pt-br/qpanc-parte-18-docker-maquina-virtual-linux-g5c)
  * [Parte 19 - Docker - Registro e Build](https://dev.to/quasar-pt-br/qpanc-parte-19-docker-registro-e-build-31i4)
  * [Parte 20 - Docker - Traefik e Publicação](https://dev.to/quasar-pt-br/qpanc-parte-20-docker-traefik-e-publicacao-3g3d)
  * [Demo Online](https://qpanc.tobiasmesquita.dev)

##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**
```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**
```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**
```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`.

```js
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:

```js
  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.

```js
  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).

```js
  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.

```js
      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.

```js
    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**
```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**
```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'
  }
}
```