Обработка HTTP-запросов в крупном приложении на Vue.js

Обработка HTTP-запросов в крупном приложении на Vue.js

В своей предыдущей статье я говорил о построении правильной и легко масштабируемой архитектуре приложения на Vue.js. То есть, о том, как организовать код в крупном приложении на Vue.js модульным и масштабируемым образом. В этой части серии статей я расскажу о том, как следуя подобной архитектуре, выполнять HTTP запросы в едином формате и какую библиотеку следует использовать в приложении для работы с HTTP-запросами на Vue.js.

HTTP запросы являются важной частью любого крупного SPA (или даже обычного приложения, работающего с API), потому что это является единственным способом взаимодействия фронтенда с сервером для обмена информацией (если вы имеете дело с реальными данными), в ином же случае вам могут потребоваться веб сокеты, а не HTTP API.

Перед тем, как начать, рассмотрим общие распространенные проблемы возникающие, когда мы имеем дело с работой по API путём HTTP-запросов:

  • Нужен общий интерфейс запросов, через который мы можем пропускать все HTTP-вызовы и обрабатывать их, в соответствии с логикой приложения.
  • Должен быть способ простого управления автообновлением токена авторизации при каждом HTTP запросе к API.
  • Нужно иметь возможность передавать какой-то уникальный, общий заголовков при каждом вызове API
  • У нас должна быть возможность обрабатывать общие ошибки, когда они возникают: северные 50x, валидации, и тому подобные. И всё это должно происходить в одной точке для любого из HTTP вызовов.
  • Возможность переопределение и гибкого задания базового URL-адреса API, в зависимости от среды запуска приложения.
  • Обработка нескольких базовых URL-адресов в случае большого приложения, которое работает с несколькими серверов одновременно.
  • Кросс-браузер/платформенная поддержка

В современной JavaScript разработке метод Fetch API также очень популярен, однако, в этой статье я не использую его, потому что он всё ещё имеет проблемы с кросс-браузерностью (в IE, в частности) а так же другие функции, такие как перехватчики запросов, приходится разрабатывать самостоятельно.

В этой статье (как и во Vue.js, Laravel по дефолту) будем использовать JavaScript HTTP-клиент Axios. Это мощная библиотека, которая из коробки предоставляется нам множество полезных, готовых функций.

Подключая axios, мы сразу же, из коробки получим множество функций, которые необходимы при проектировании архитектуры приложения на JavaScript:

  • Кросс-браузерная/платформенная поддержка. Вы можете использовать Axios как для браузера, так и в среде nodejs на бекенде
  • Встроенные механизмы перехвата запросов и ответов, и гибкая настройка их обработки
  • Возможность отклонение запросов (отмены)
  • Полная поддержка промисов (Promise API)
  • Возможность создания многочисленных экземпляров Axios клиентов с различными настройками (часто используется для создания нескольких клиентов, настроенные на разную конфигурацию/серверы)

В предыдущей статье рассматривался тонкий вопрос о структуре папок, но сейчас вопрос в том, как можно в описанную структуру внедрить логику, связанную с Axios и работой с HTTP? Я думаю, у вас уже есть идея, где это можно хранить, а если и нет, то не волнуйтесь, я все объясню.

Ранее я говорил о сервисах. Да, один из них был в конкретном модуле, а другой - в общей shared папке приложения. Поскольку HTTP клиент будет использоваться во всем приложении, я собираюсь поместить его в app/shared/services, а код прототипа будет выглядеть следующим образом:

import axios from 'axios';

/** Базовая конфигурация axios */
let config = {
    // Базовый URL-адрес сервера
    baseURL: 'http://localhost:3000/'
};

/** Создание экземпляра axios */
const httpClient = axios.create(config);

/** Добавление токена аутентификации, и обновление его, если это требуется */
const authInterceptor = config => {
    // достаём токент аутентификации пользователя, с LocalStorage, или cookies, например
    const authToken = '...';
    config.headers.Authorization = `Bearer ${authToken}`;

    return config;
};

/** добавлени логгера при каждом axios запросе */
const loggerInterceptor = config => {
    /** как-то обрабатываем логи */
    return config;
}

/** Добавляем экземпляру созданные обработчики для аутентификации и логов */
httpClient.interceptors.request.use(authInterceptor);
httpClient.interceptors.request.use(loggerInterceptor);

/** Добавление обработчика для результатов или ошибок при запросах */
httpClient.interceptors.response.use(
    response => {
        /** Как-то обрабатываем успешный результат */
        return response;
    },
    
    error => {
        /** Как-то обрабатываем результат, завершенный ошибкой */
        return Promise.reject(error);
    }
);

export { httpClient };

В приведённом выше примере кода я добавил много интересных вещей, и на первый взгляд подобная конструкция может показаться сложной или бесполезной. Однако давайте разберём этот код подробнее с пошаговым объяснением.

При использовании Axios, мы можем передать некоторые, общие для всех настройки, такие как базовый URL-адрес, стандартные заголовки, параметры URL и т.д., которые будут применяться для каждого запроса, выполняемого в этом экземпляре Axios.

Я создаю экземпляр Axios, передавая ему все необходимые настройки. Итак, у вас в голове возникнет вопрос, зачем создавать экземпляр, почему я не использую его напрямую? Большинство людей используют синглтоны экземпляра Axios, просто импортируя и используя их непосредственно в том месте, где им нужно обращаться к API. Такой подход не имеет существенных недостатков, если только вы не работаете в крупном проекте и не нуждаетесь в гибкости, масштабируемости и удобстве обслуживания.

Итак, подход, который я предлагаю использовать - экспортировать httpClient из этого файла вместо Axios напрямую каждый раз, когда вам необходимо будет выполнить запрос к Api.

Главными преимуществами такого подхода заключается в следующем:

  • Всё ваше приложение будет использовать общий интерфейс http-клиента вместо прямого импорта Axios, поэтому в будущем, если вы решите сменить библиотеку, и перестать использовать axios, то вам достаточно просто изменить экспорт в одном файле. Всё, что вам для этого потребуется - это просто интегрировать другую библиотеку, с соблюдением интерфейса клиента.
  • Так же для добавления/изменения глобальных параметров для всех компонентов использующих axios, достаточно лишь изменить всего один файл.
  • Давайте ещё представим, что вашему приложению необходимо выполнять HTTP-запросы по API к 2-м различным серверам с разными настройками авторизации, различными базовыми параметрами и настройками. Благодаря подобной архитектуре, становится очень легко создавать и управлять отдельными экземплярами http-клиента для обоих серверов. Например, просто задавая нужные конфиги для server1-http-клиента и server2-http-клиента и так далее, импортируя конкретный из них в месте, где он требуется. Мы так же можем управлять перехватчиками запросов, навешивать миддлверы, и хранить настройки этих клиентов в отдельных файлах, чтобы мы могли в дальнейшем повторно использовать их в рамках нескольких http-клиентов.

В текущем примере была продемонстрирована лишь идея создания перехватчиков запросов и ответов (миддлверы), такие как: authInterceptor, loggerInterceptor. Перехватчики обеспечивают механизм перехвата запроса или ответа с возможностью просмотра, обработки, модификации данных запроса/ответа. Это очень похожая концепция миддлверов в средах, где мы можем выполнять такие операции, как кэширование, протоколирование, аутентификация и т.д. Конечная реализация подобных перехватчиков зависит от бизнес-задач вашего приложения.

Резюме

Конечно же, это не единственный способ обрабатывать HTTP-запросы и создавать клиенты различной конфигурации. Это не единственный способ решения описанных выше пунктов. Но, лично мне этот подход действительно помогает справляться с этими проблемами в крупных приложениях, и я надеюсь, это также поможет и вам улучшить собственную архитектуру, и получить некоторое представление по разработке архитектуры масштабного приложения.

И я надеюсь, что именно эта статья помогла вам понять, как организовать структуру работы с API и выполнение HTTP запросов в крупных приложения на Vue.js, используя axios. А так же, думаю, вы прониклись идеей того, как создавать несколько различных клиентов axios, с индивидуальными настройками, и, даже, конфигурацией с различными серверами.