Deblogger

Otimização na utilização do Axios

Introdução

Axios é uma biblioteca muito usada para fazer requisições HTTP. Ele usa promessas por padrão e é executado no cliente e no servidor, o que o torna apropriado para buscar dados, podendo ainda combiná-lo com async/await para obter uma API concisa e fácil de usar. Embora a biblioteca seja ótima, existem alguns pontos que acho relevantes para otimização na utilização do Axios.

Mas porque usar Axios? Na minha (humilde) opinião, a necessidade de utilização de uma biblioteca é bem relativa. Nesse cenário, existe a opção de usar a Fetch API, que é bem poderosa, mas que não possui compatibilidade total (ainda). Dessa forma, a facilidade de se trabalhar com o Axios, aliada a necessidade de compatibilidade, além da necessidade de uma biblioteca madura e em constante atualização leva essa biblioteca a ser a queridinha dentre boa parte dos desenvolvedores JavaScript.

Reutilização e SOLID

Embora seja possível importar a biblioteca diretamente para fazer qualquer chamada, eu gosto de criar services específicas, trabalhando um pouco a parte de reutilização de código, tratamento de erros, redirecionamentos, tudo num único lugar. Por exemplo, se você precisa de autenticação no sistema, incluir o token no cabeçalho da requisição toda vez que for fazer uma chamada HTTP pode ser bem custoso. Outro exemplo de comportamento genérico é o redirecionamento para a página de login do seu sistema caso o token esteja inválido.

Então, o que costumo fazer é concentrar todas as lógicas que envolvem o contexto das requisições de uma forma genérica num único arquivo, o httpClient. Isso permite não só a reutilização de código, mas atende ao Princípio da Substituição de Liskov, que abre caminho para a substituição da biblioteca em caso de defasagem ou por qualquer outra necessidade.

import axios from 'axios';

const httpClient = axios.create({
  baseURL: process.env.VUE_APP_API_URL, // URL padrão da sua aplicação
  headers: {
    'Content-Type': 'application/json'
  }
});

httpClient.interceptors.request.use(async (config) => {
  // Validar se a request é privada e adicionar token à requisição
  return config;
});

httpClient.interceptors.response.use(
  (response) => {
    // Retornar apenas os dados para a resposta no 'then()', dessa forma os arquivos que
    // consumirem essa service terão apenas os dados necessários no momento de consulta
    return response.data;
  },
  (error) => {
    // É possível lidar com as requisições que dão erro aqui, lidando com códigos de retorno
    //  específicos ou de maneira geral
    return Promise.reject(error);
  }
);

export default httpClient;

Em tese, essa service apenas cria uma instância Axios e trabalha com alguns interceptadores. O interceptador é um recurso que permite a aplicação interceptar solicitações ou respostas antes de serem manipuladas pelo .then() ou pelo .catch(). Por que isso é útil? Bem, como mencionei acima, suponha que cada solicitação HTTP precise de uma propriedade de cabeçalho anexada a ela, a fim de verificar se o aplicativo que está solicitando os dados tem permissão para acessar esses dados. Ou suponha que às vezes a resposta HTTP que retorna seja uma resposta negativa, como o status 400 ou 500. Nesses momentos, você pode querer repetir algum comportamento padrão da sua aplicação.

Interceptors

Sendo assim, vamos à um exemplo de interceptador que adiciona o token JWT ao cabeçalho da requisição:

httpClient.interceptors.request.use(async (config) => {
  const token = tokenService.get();
  config.headers.Authorization = `Bearer ${token}`;
  return config;
});

Bem fácil né? Aqui apenas consumimos outra service que tem a responsabilidade de guardar o token do usuário logado, e adicionamos ele à requisição. Agora, suponhamos que o token que enviamos aqui está inválido, e sua aplicação retornou o status de erro 401. Um comportamento bem comum é o redirecionamento para a página de login, dessa forma a aplicação poderá coletar um token válido para continuar fazendo as requisições. A implementação desse comportamento também é bem simples:

httpClient.interceptors.response.use(
  (response) => {
    return response.data;
  },
  (error) => {
    if (error.response) {
      switch (error.response.status) {
        case 401:
          tokenService.remove();
          window.location.href = "https://site.com/login";
          break;
        // Tratamentos de outros status de erro podem ser adicionados aqui
        default:
          break;
      }
    }
    return Promise.reject(error);
  }
);

Classe construtora

Com o meu cliente HTTP pronto, eu costumo trabalhar com outro facilitador: uma classe construtora. Isso porque a maior parte das aplicações com as quais já trabalhei seguiam padrões de API muito similares para diferentes entidades. Isto é: uma consulta por ID, uma consulta de todas as entidades, uma requisição para modificação, uma para exclusão outra para criação, e por aí vai. Além disso, normalmente, essas requisições seguiam o mesmo padrão de rota. Vamos ao código:

import { mountUrl } from '../helpers/httpHelper';
import httpClient from './httpClient';

export default class BaseService {
  constructor(BASE_REF) {
    this.BASE_REF = BASE_REF;
  }

  get(queryParams) {
    const url = mountUrl(this.BASE_REF, queryParams);
    return httpClient.get(url);
  }

  getById(id) {
    return httpClient.get(`${this.BASE_REF}/${id}`);
  }

  post(data) {
    return httpClient.post(this.BASE_REF, data);
  }

  put(id, data) {
    return httpClient.put(`${this.BASE_REF}/${id}`, data);
  }

  delete(id) {
    return httpClient.delete(`${this.BASE_REF}/${id}`);
  }
}

Mas afinal, que ganho eu tenho com isso? A resposta é: chamadas simples e claras:

const UserService = new BaseService('users');

// Consulta todos os registros
UserService.get();

// Consulta registro pelo ID
UserService.getById(13);

// Cadastra novo registro
UserService.post({
  nome: 'Mauro',
  username: 'mauroV',
  password: 'q1w2e3'
})

// Edita um registo
UserService.put(13, {
  nome: 'Mauro Oliveira',
  username: 'mauroV',
  password: 'q1w2e3'
})

// Exclui um registro
UserService.delete(13)

Conclusão

Em síntese, a otimização na utilização do Axios consiste em trabalhar com princípios comuns da programação. De maneira geral, você pode separar a utilização de uma biblioteca de terceiros da lógica da sua aplicação, reutilizar código comum e deixar mais claro o que está sendo feito aplicando essas pequenas dicas. De fato, todo sistema possui suas peculiaridades, mas existem maneiras mais eficiente de se trabalhar com algumas ferramentas. Nunca se esqueça de que fazer algo funcionar é ótimo, mas manter funcionando pode ser complicado se não pensarmos com carinho na implementação.

Avalie:
5 (3)

Publicado por Mauro Oliveira

Desenvolvedor apaixonado por JavaScript, especialista em VueJS. Atualmente focado em padrões de projeto e código, estudando sobre testes unitários, Clean Code e segurança em aplicações Web, Mobile e Desktop.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *