Как создать собственный простой API с поддержкой CORS из того, в котором CORS отключен

Нажмите здесь, чтобы опубликовать эту статью в LinkedIn »

Как фронтенд-разработчик, я часто использую различные сторонние API во время разработки. Эти API могут быть для погоды, цен на криптовалюту или последних комиксов XKCD.

Проблема с некоторыми из них заключается в том, что они не поддерживают запросы между источниками (CORS), что означает, что клиентские вызовы AJAX для этих служб не работают. Это неприятно, но это легко исправить с помощью нескольких строк кода в вашем собственном микросервисе.

Создание микросервиса

Все, что вам нужно для создания простого микросервиса, - это пакет под названием micro. Это очень простой пакет, который позволяет создавать асинхронные микросервисы, и если вы прочитаете файл readme проекта, вы увидите, что простой сервис можно создать, используя не более нескольких строк:

module.exports = (req, res) => {
  res.end(‘Hello world’)
}

Очевидно, что приведенное выше совершенно бесполезно, но позвольте мне показать, насколько легко использовать практически любой API, по крайней мере, любой API, который не нуждался в аутентификации, с использованием micro.

В этом примере я буду использовать бесплатный API из комикса XKCD, который нельзя использовать в вызовах AJAX на стороне клиента, потому что CORS отключен. Вызов URL https://xkcd.com/info.0.json возвращает последний комикс, например:

{
  "month": "2",
  "num": 1954,
  "link": "",
  "year": "2018",
  "news": "",
  "safe_title": "Impostor Syndrome",
  "transcript": "",
  "alt": "It's actually worst in people who study the Dunning–Kruger effect. We tried to organize a conference on it, but the only people who would agree to give the keynote were random undergrads.",
  "img": "https://imgs.xkcd.com/comics/impostor_syndrome.png",
  "title": "Impostor Syndrome",
  "day": "12"
}

И он может вернуть конкретный комикс, если передан правильный идентификатор комикса (https://xkcd.com/1500/info.0.json):

{
  "month": "3",
  "num": 1500,
  "link": "",
  "year": "2015",
  "news": "",
  "safe_title": "Upside-Down Map",
  "transcript": "((A mercator projection of the world map is shown. All the continents have been rotated one hundred eighty degrees.))\n\n((Cuba  is next to alaska, and alaska is touching the tip of south america, which is all near the equator. Mexico is now friends with greenland.\n\n((Iceland, the UK, and asia are all close together. Japan and Taiwan haven't moved with the asian continent, and are technically European.))\n\n((Siberia is now equatorial. Africa is pretty temperate, except for the north bits which are somewhat antarctic.))\n\nCaption: This upside-down map will change your perspective on the world!\n\n{{Title text: Due to their proximity across the channel, there's long been tension between North Korea and the United Kingdom of Great Britain and Southern Ireland.}}",
  "alt": "Due to their proximity across the channel, there's long been tension between North Korea and the United Kingdom of Great Britain and Southern Ireland.",
  "img": "https://imgs.xkcd.com/comics/upside_down_map.png",
  "title": "Upside-Down Map",
  "day": "18"
}

Все, что нужно сделать микросервису, - это передать любые запросы исходному API и установить несколько заголовков, чтобы разрешить запросы из разных источников, чтобы их можно было использовать в вызовах AJAX на стороне клиента, например:

const axios = require('axios')
const { send } = require('micro')
const microCors = require('micro-cors')
const cors = microCors({ allowMethods: ['GET'] })
const DOMAIN = 'https://xkcd.com/'
const handler = async function(req, res) {
  const params = req.url
  const path = `${DOMAIN}${params}`
  const response = await axios(path)
  send(res, 200, response.data)
}
module.exports = cors(handler)

Это 14 строк кода!

В приведенном выше примере в API передается любая информация о слаге (например, 1000/0.json), поэтому вызов https://xkcd.now.sh/1000/0.json (моя версия API) будет отображаться на https://xkcd.com/1000/0.json. Возможно, это конец нашего пути, но я хотел бы немного улучшить API UX, изменив конечные точки:

  • xkcd.now.sh должен вернуть последний комикс.
  • xkcd.now.sh/1000 должен вернуть идентификатор комикса 1000.

См. Ниже, как этого добиться:

const axios = require('axios')
const { send } = require('micro')
const microCors = require('micro-cors')
const cors = microCors({ allowMethods: ['GET'] })
const DOMAIN = 'https://xkcd.com/'
const PATH = 'info.0.json'
const handler = async function(req, res) {
  let id = req.url.replace('/', '')
  const comicId = id ? `${id}/` : ''
  const path = `${DOMAIN}${comicId}${PATH}`
  const response = await axios(path)
  id = response.data.num
  let newResponse
  if (id >= 1084) {
    newResponse = {
        ...response.data,
        imgRetina: `${response.data.img.replace('.png', '')}_2x.png`,
      }
    } else {
      newResponse = {
      ...response.data,
    }
  }
  send(res, 200, newResponse)
}
module.exports = cors(handler)

Это 29 строк кода! Смотрите здесь 👀

Выше видно, что помимо micro есть еще два пакета, на которые опирается служба:

  • axios для HTTP-запросов
  • micro-cors - это простой CORS для микро

Мой пример с API XKCD возвращает все исходные данные и фактически немного меняет данные ответа, а также способ использования API. Я решил добавить путь изображения сетчатки (если он есть), а также упростить вызов API. Поэтому вместо вызова xkcd.com/1894/info.0.json вы можете позвонить xkcd.now.sh/1894.

Так, например: при вызове https://xkcd.now.sh/1894 будет запрашиваться этот URL из исходного API XKCD: https://xkcd.com/1894/info.0.json.

{
  "month": "9",
  "num": 1894,
  "link": "",
  "year": "2017",
  "news": "",
  "safe_title": "Real Estate",
  "transcript": "",
  "alt": "I tried converting the prices into pizzas, to put it in more familiar terms, and it just became a hard-to-think-about number of pizzas.",
  "img": "https://imgs.xkcd.com/comics/real_estate.png",
  "title": "Real Estate",
  "day": "25",
  "imgRetina": "https://imgs.xkcd.com/comics/real_estate_2x.png"
}

💪 Код для этой службы размещен на GitHub по адресу https://github.com/mrmartineau/xkcd-api и может быть протестирован с помощью Postman здесь.

Размещение вашего нового API

Я использую сейчас от zeit для размещения своих различных приложений и API. Теперь поддерживает функции языка JavaScript, которые требуются этой микрослужбе (async / await), а также HTTPS из коробки. Если ваш хостинг не поддерживает эти функции, вам нужно будет перенести код обратно в версию, которую он поддерживает.

Другие примеры

В качестве примера еще более простого сквозного API вы можете увидеть мою версию API каналов Pinboard с поддержкой CORS. Код размещен на GitHub по адресу https://github.com/mrmartineau/pinboard-api.

Я благодарю Эндрю Уильямса, Эшли Нолан и Кьяран Парк за их помощь в написании названия этой публикации. Другие предложения от них включают:

  • Нет CORS для беспокойства: получение этого API
  • Be-CORS, ты того стоишь
  • COR Blimey gov’nr
  • CORS, тьфу, для чего он хорош
  • Просто CORS

🤦‍♂️