Я не являюсь профессиональным программистом. Но так как моя компания маленькая, то приходится заниматься много чем, в том числе и написанием кода. Здесь буду выкладывать свои решения, которые оказались работающими и, возможно, будут полезны таким же как я.
Здесь используются: Express.js, TypeScript, Axios, Mongoose.
В рамках автоматизации некоторых процессов работы с сервисом Мой Склад (moyskald.ru) потребовалось написать блок для работы с их API. Сервис позволяет получать и обновлять токен, который потом нужно добавлять в заголовки всех запросов.
Первоначальный вариант
Первоначально полученный token хранился в файле конфета и код создания инстанса выглядел так:
// moyskladHttp.ts
import axios from 'axios'
import * as moySkaldConfig from '../../../config/moySklad'
const moyskladHttp = axios.create({
baseURL: moySkaldConfig.baseUrl,
headers: {
'Authorization': `Bearer ${moySkaldConfig.token}`
},
validateStatus: () => true
})
export default moyskladHttp
В этом варианте не устраивала потенциальная возможность смены токена. Пришлось бы править код и заново разворачивать все на хостинге. Поэтому было решено хранить токен в базе данных (используется Mongo). Здесь его можно оперативно изменить напрямую или через через веб-интерфейс.
Исправленный вариант
Простой вариант - создать инстанс без заголовка авторизации и добавлять его уже при каждом запросе. Выглядит это так:
const moyskladHttp = axios.create({
baseURL: moySkaldConfig.baseUrl,
validateStatus: () => true
})
moyskladHttp.interceptors.request.use(async (config) => {
const token = await getToken()
return {
...config,
headers: { ...config.headers, Authorization: `Bearer ${token}` }
}
})
функция getToken() делает асинхронный запрос к нашей базе и возвращает токен. Она выглядит так:
const getToken = async () => {
const doc = await Credential.findOne({ key: 'moyskladToken' })
return doc?.value || ''
}
Credential - это модель mongoose для нашей базы. Токены хранятся в коллекции ‘credentials’ и документы имеют вид: {key: string, value: string}
Имеющееся решение плохо тем, что возникает дополнительная задержка при каждом запросе к API сервиса Мой Склад. Так как нужно получить сначала токен из базы. С одной стороны, специфика наших задач не требует максимальной скорости работы сервиса. То есть проблема совсем не критичная. С другой, решение вопроса довольно простое.
Для этого обернем функцию getToken() вспомогательной функцией _getToken(), которая будет хранить единожды полученный токен. Выглядит она так:
const _getToken = () => {
let token = ''
return async () => {
if (token !== '') return token
const doc = await Credential.findOne({ key: 'moyskladToken' })
token = doc?.value || ''
return token
}
}
const getToken = _getToken()
Переменная token доступна хранится в функции _getToken() и доступна при этом внутри нашей getToken(). Здесь дополнительно производится проверка: если токен еще не задан, то вытягиваем его из базы. Если получилось, то записываем его в переменную token (сам токен или пустую строку, в случае какой-либо ошибки). Таким образом, единожды полученный токен будет выдаваться сразу, минуя обращение к базе.
Целиком код выглядит так:
import axios from 'axios'
import * as moySkaldConfig from '../../../config/moySklad'
import Credential from '../../../models/credential.models'
const _getToken = () => {
let token = ''
return async () => {
if (token !== '') return token
const doc = await Credential.findOne({ key: 'moyskladToken' })
token = doc?.value || ''
return token
}
}
const getToken = _getToken()
const moyskladHttp = axios.create({
baseURL: moySkaldConfig.baseUrl,
validateStatus: () => true
})
moyskladHttp.interceptors.request.use(async (config) => {
const token = await getToken()
return {
...config,
headers: { ...config.headers, Authorization: `Bearer ${token}`}
}
})
export default moyskladHttp