Оглавление
Введение
В большинстве фронтенд-приложений мы должны интегрироваться с бэкендом, и с этим приходит несколько библиотек, которые мы можем использовать, такие как fetch, ajax, axios и другие, и каждая из них имеет свои характеристики, преимущества и недостатки.

Но независимо от того, какой из них мы собираемся использовать в нашем приложении, мы должны продумать моменты, которые помогут нам в обслуживании и лучшей коммуникации, чтобы не повлиять на удобство работы пользователя.
В этой заметке я буду использовать axios с react и применять концепции, которые я считаю чрезвычайно важными и которые мы должны использовать в наших приложениях. Я приму во внимание, что у вас уже есть проект react с установленным axios.

Инкапсулировать услугу
Мы должны создать общий сервис с именем библиотеки, которую мы решили использовать для интеграции, и просто использовать его в приложении, с той же идеей компонентов, таких как карточка, входы и другие, что мы уже делаем.

Сначала мы должны создать файл http.js или http.ts (помня, что вы можете задать другое имя, если хотите) для экспорта axios с уже настроенным базовым url нашего бэкенда.
import Axios from 'axios';
const http = Axios.create({
baseURL: process.env.REACT_APP_URL,
});
export default http;
Теперь мы должны создать еще один файл index.js или index.ts, куда мы экспортируем наиболее часто используемые http-методы, уже обернутые в try catch blog, чтобы разобраться с ошибками вызовов прямо здесь. Здесь мы уже используем созданный нами выше файл http.ts для запуска axios с параметрами, в последующих постах мы будем развивать этот файл.
import http from './http';
export default {
async get(url: string) {
try {
const response = await http.get(url);
return response;
} catch (err: any) {
return false;
}
},
async post(url: string, send: object) {
try {
const response = await http.post(url, send);
return response;
} catch (err: any) {
return false;
}
},
async put(url: string, send: object) {
try {
const response = await http.put(url, send);
return response;
} catch (err: any) {
return false;
}
},
async delete(url: string) {
try {
await http.delete(url);
return true;
} catch (err: any) {
return false;
}
},
};
В итоге мы получим следующую структуру папок.

Таким образом, мы сможем вызывать метод на наших компонентах.
await services.post( ’/authenticate’, { email, password } );
Но почему необходимо использовать именно такой подход?

Когда мы работаем с общим сервисом и только импортируем его в наше приложение, его становится проще поддерживать и изменять в дальнейшем. Ниже показано, как мы можем это сделать.
Добавьте заголовки ко всем запросам
Теперь давайте настроим заголовки для всех наших запросов, нам понадобится этот пункт для передачи токена и другой информации, которая может понадобиться вашему бэкенду в качестве бизнес-правил.
Давайте создадим для этого перехватчики axios, так как это лучший способ не повторять код. Смотрите пример ниже.
import Axios, { AxiosRequestConfig } from 'axios';
const http = Axios.create({
baseURL: process.env.REACT_APP_URL,
});
http.interceptors.request.use((config: AxiosRequestConfig) => {
const token = window.localStorage.getItem('token');
if (!token) return config;
if (config?.headers) {
config.headers = { Authorization: `Bearer ${token}` };
}
return config;
});
export default http;
Здесь мы уже извлекли токен localstorage и добавили его во все обращения к бэкенду.
Перенаправление неавторизованного или не прошедшего аутентификацию пользователя
Мы должны иметь стратегии перенаправления пользователей, когда у пользователя нет авторизации или разрешения, чтобы у него не было необходимости делать это в наших компонентах.
Для этого мы должны создать еще один перехватчик, который будет заниматься этим процессом.
import Axios, { AxiosRequestConfig } from 'axios';
const http = Axios.create({
baseURL: process.env.REACT_APP_URL,
});
http.interceptors.request.use((config: AxiosRequestConfig) => {
const token = window.localStorage.getItem('token');
if (!token) return config;
if (config?.headers) {
config.headers = { Authorization: `Bearer ${token}` };
}
return config;
});
http.interceptors.response.use(
(value) => {
return Promise.resolve(value);
},
(error) => {
const { isAxiosError = false, response = null } = error;
if (isAxiosError && response && response.status === 401) {
// User redirection rule for login page
return Promise.reject(error);
}
if (isAxiosError && response && response.status === 403) {
// User redirection rule for disallowed page
return Promise.reject(error);
}
return Promise.reject(error);
}
);
export default http;
Оставьте открытым место, куда вы хотите отправить пользователя для 401(Unauthenticated) и 403(Unauthorized). Таким образом, даже если пользователю удастся получить доступ к странице, на которую он не мог попасть, когда запрос от бэкенда вернется с кодом статуса, система уже направит его. Этот подход также работает для случаев, когда срок действия токена истекает, с чем мы разберемся позже.
Шаблон повторного запроса
Теперь нам нужно применить шаблон повторных попыток к нашим запросам, чтобы наш конечный пользователь не страдал от нестабильности приложения, поскольку в момент вызова оно может находиться в процессе развертывания или автоматического масштабирования инфраструктуры. Для этого мы определяем количество попыток в случае, если система возвращает ошибку 500 или выше. Пример ниже.
import Axios, { AxiosRequestConfig } from 'axios';
const http = Axios.create({
baseURL: process.env.REACT_APP_URL,
});
http.interceptors.request.use((config: AxiosRequestConfig) => {
const token = window.localStorage.getItem('token');
if (!token) return config;
if (config?.headers) {
config.headers = { Authorization: `Bearer ${token}` };
}
return config;
});
http.interceptors.response.use(
(value) => {
return Promise.resolve(value);
},
(error) => {
const { isAxiosError = false, response = null } = error;
if (isAxiosError && response && response.status === 401) {
// Regra de redirecionamento de usuário para página de login
return Promise.reject(error);
}
if (isAxiosError && response && response.status === 403) {
// Regra de redirecionamento de usuário para página de não permitido
return Promise.reject(error);
}
return Promise.reject(error);
}
);
let counter = 1;
http.interceptors.response.use(
(response) => {
return response;
},
(error) => {
if (
error.response.status >= 500 &&
counter < Number(process.env.REACT_APP_RETRY)
) {
counter++;
return http.request(error.config);
}
counter = 1;
return Promise.reject(error);
}
);
export default http;
Перехватчик был создан таким образом, что он имеет повторную попытку в соответствии с количеством, определенным в process.env.REACT_APP_RETRY раз, когда запрос имеет код состояния больше 500.
Обновить токен
Когда мы работаем с аутентификацией, хорошей практикой и правилом безопасности является наличие токенов, срок действия которых истекает, чтобы пользователь не оставался залогиненным навсегда, даже не используя приложение.
Однако мы должны решить проблему, что если срок действия токена истекает, когда пользователь не может просто попросить его войти снова, для этого у нас есть маршруты для обновления токена.
Мы можем улучшить наш файл index.ts так, чтобы он делал это автоматически во время вызовов маршрутов вашего приложения.
import http from './http';
async function refreshToken() {
const value = Number(localStorage.getItem('expired'));
if (value && new Date(value) < new Date()) {
const result = await http.get('/refresh');
localStorage.setItem('token', result.data.token);
localStorage.setItem(
'expired',
String(new Date().setSeconds(result.data.expired))
);
}
}
export default {
async get(url: string) {
try {
await refreshToken();
const response = await http.get(url);
return response;
} catch (err: any) {
return false;
}
},
async post(url: string, send: object) {
try {
await refreshToken();
const response = await http.post(url, send);
return response;
} catch (err: any) {
return false;
}
},
async put(url: string, send: object) {
try {
await refreshToken();
const response = await http.put(url, send);
return response;
} catch (err: any) {
return false;
}
},
async delete(url: string) {
try {
await refreshToken();
await http.delete(url);
return true;
} catch (err: any) {
return false;
}
},
};
Мы создали функцию refreshToken(), которая всегда будет вызываться перед всеми вызовами нашего приложения. Она будет проверять, не истек ли уже срок действия токена, и если да, то делать новый вызов бэкенда, обновляя токен и его срок действия. Помня, что эта логика работает в зависимости от бэкенда, а маршрут обновления, например, имеет ограничение по времени после перехода от истечения срока действия к обновлению токена, это будет скорее бизнес-правило.
В этом посте мы рассмотрели пять способов улучшить нашу связь с бэкендом, принимая во внимание лучший опыт для конечного пользователя, существует множество других подходов, которые могут улучшить нашу службу обращений к бэкенду, но просто реализовав эти концепции, мы получим лучшее обслуживание и удобство использования нашей системы. В следующих статьях мы рассмотрим, как еще больше улучшить этот сервис.

Axios - https://axios-http.com/docs/intro
React - https://reactjs.org/