6 хв читанняІнженерія

Як скоротити URL у JavaScript за допомогою fetch і Node

Скоротіть URL у JavaScript одним викликом fetch, потім додайте тайм-аут, повторні спроби та ідемпотентність - і дізнайтеся, чому браузер не підходить для зберігання ключа.

Marius Voß
DevRel · edge infra
Як скоротити URL у JavaScript: виклик fetch у Node, який надсилає URL призначення до API скорочувача та отримує коротке посилання, тоді як браузеру заборонено зберігати ключ API

Скорочення URL у JavaScript - це один HTTP POST. Надішліть довге посилання до API скорочувача за допомогою fetch, передайте ключ API як Bearer-токен і прочитайте коротке посилання з відповіді JSON. Починаючи з Node 18, пакет встановлювати не потрібно - fetch є глобальним об'єктом.

Важливо, де виконується цей код. Ключ API скорочувача дає змогу витрачати кошти, а клієнтський JavaScript є загальнодоступним, тому браузер відпадає. Усе нижче виконується на сервері: скрипт Node, обробник маршруту або безсерверна функція.

Це JavaScript-версія загального посібника зі скорочення URL, який натомість охоплює панель керування та сценарій у браузері. Якщо ви ще не обрали сервіс, огляд безкоштовного API скорочувача URL описує форму запиту, модель автентифікації та ліміти безкоштовного тарифу, які передбачає ця стаття. Наведені нижче назви полів належать Elido, але схема - надіслати POST із призначенням і отримати короткий URL - працює з більшістю сучасних скорочувачів.

Найшвидший спосіб: один виклик fetch у Node

Збережіть ключ у змінній середовища та надішліть призначення до ендпоінта посилань:

const res = await fetch("https://api.elido.app/v1/links", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ELIDO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    destination_url: "https://example.com/spring-sale?utm_source=newsletter",
  }),
  signal: AbortSignal.timeout(10_000),
});

if (!res.ok) throw new Error(`shorten failed: ${res.status}`);

const { short_url } = await res.json();
console.log(short_url); // -> https://s.elido.me/ab12cd

Запустіть його за допомогою node --env-file=.env shorten.js, і ключ ніколи не потрапить до вихідного коду.

Рядок if (!res.ok) - не декоративний. fetch відхиляє проміс лише через мережеву помилку або переривання, тому відповідь 401 або 500 повертається як повністю успішно виконаний проміс із ok: false. Пропустіть цю перевірку, і відкликаний ключ виглядатиме як успішний, доки якийсь наступний етап не спробує використати undefined як посилання. Той, хто звик до axios, зазвичай один раз на цьому обпікається.

AbortSignal.timeout - ще одна корисна звичка. Без нього зависле з'єднання блокує виконання, доки сокет не закриється сам, а в завданні cron це означає, що завдання просто зникне.

Чому браузер не підходить для цього

Вставте цей фрагмент у компонент React, і зламаються дві речі. Перша - CORS: API з автентифікацією за ключем не надсилає Access-Control-Allow-Origin для довільних сайтів, тому браузер блокує відповідь. Читання довідки MDN щодо CORS зазвичай змушує шукати проксі, який прибере цю помилку.

Помилка - лише симптом. Справжня проблема - друга: ключ у фронтенд-коді може прочитати кожен, хто відкриє DevTools або виконає пошук у бандлі. Бандлери його не приховують, префікси NEXT_PUBLIC_ прямо на нього вказують, а публічний ключ платного API стає чиєюсь безкоштовною квотою.

Виклик fetch із браузера до API скорочувача URL заблокований CORS, а ключ API відкритий у бандлі; поруч показано правильний шлях, де браузер викликає серверний маршрут, який зберігає ключ і пересилає запит

Робоча схема передбачає ваш маршрут посередині. Браузер надсилає звичайний URL до вашого ендпоінта, сервер додає ключ, а ключ залишається у змінній середовища, якої клієнт ніколи не бачить:

// app/api/shorten/route.js  (Next.js App Router)
export async function POST(request) {
  const { url } = await request.json();

  const res = await fetch("https://api.elido.app/v1/links", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.ELIDO_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ destination_url: url }),
  });

  const { short_url } = await res.json();
  return Response.json({ short_url });
}

Перевірте url перед пересиланням. Відкритий ендпоінт, який скорочує все, що хтось надсилає, є відкритим переспрямуванням із додатковими кроками, а матеріал про відкриті переспрямування пояснює, як зловмисники цим користуються.

Додайте тайм-аут, повторні спроби та ключ ідемпотентності

Щасливий шлях займає вісім рядків. Завдання, яке виконується без нагляду, має переживати 429, тимчасову помилку 5xx і ще неприємніший випадок, коли POST успішний, але відповідь не надходить: ваш код переривається за тайм-аутом, повторює запит, і тепер для одного призначення є два короткі посилання.

Ключ ідемпотентності усуває цю проблему. Згенеруйте один ключ для кожного URL, використовуйте його під час усіх повторних спроб цього URL, і API поверне початкове посилання замість створення другого.

import { randomUUID } from "node:crypto";

const API = "https://api.elido.app/v1/links";
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

export async function shorten(destinationUrl, { retries = 3 } = {}) {
  const idempotencyKey = randomUUID(); // one per URL, not per attempt

  for (let attempt = 0; attempt < retries; attempt++) {
    const res = await fetch(API, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.ELIDO_API_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": idempotencyKey,
      },
      body: JSON.stringify({ destination_url: destinationUrl }),
      signal: AbortSignal.timeout(10_000),
    });

    if (res.status === 429) {
      await sleep(Number(res.headers.get("Retry-After") ?? 2) * 1000);
      continue;
    }
    if (res.status >= 500) {
      await sleep(2 ** attempt * 1000); // exponential backoff
      continue;
    }
    if (!res.ok) {
      throw new Error(`shorten failed: ${res.status} ${await res.text()}`);
    }

    return (await res.json()).short_url;
  }

  throw new Error(`shorten failed after ${retries} attempts`);
}

Помилка 401 або 403 сама не виправиться, тому такі відповіді одразу спричиняють виняток, а не витрачають три спроби. Відповідь 429 чекає рівно стільки, скільки вказує заголовок Retry-After. Лише для 5xx застосовується експоненційна затримка. У докладному матеріалі про ліміти частоти та ідемпотентність пояснюється, чому наївний цикл повторних спроб перетворює збій одного постачальника на два.

Хочете запускати фрагменти як є? Створіть ключ на безкоштовному тарифі, експортуйте його як ELIDO_API_KEY, і кожен приклад на цій сторінці працюватиме без змін.

Масове скорочення URL без перевищення ліміту частоти

Очевидний варіант для масового скорочення - Promise.all(urls.map(shorten)). Він запускає всі запити в одному такті, тому список із 500 URL перетворюється на 500 одночасних з'єднань, і API відповідає кодом 429 на більшість із них. Невеликий пул вирішує проблему: фіксована кількість воркерів бере завдання зі спільної черги, тому паралельність не змінюється, якою б довгою не була черга.

export async function shortenAll(urls, concurrency = 8) {
  const queue = [...urls];
  const results = new Map();

  const worker = async () => {
    while (queue.length) {
      const url = queue.pop();
      try {
        results.set(url, await shorten(url));
      } catch (err) {
        results.set(url, `ERROR: ${err.message}`); // one bad URL must not sink the batch
      }
    }
  };

  await Promise.all(Array.from({ length: concurrency }, worker));
  return results;
}
Promise.all запускає всі запити скорочення одночасно та збирає помилки ліміту частоти; поруч показано обмежений пул воркерів, який підтримує фіксовану кількість активних запитів для безпечного масового скорочення URL

Ідентифікація результатів за початковим URL робить часткову помилку видимою та придатною для повторного запуску, а не непомітною прогалиною у вихідних даних. Встановіть concurrency відповідно до документації тарифу; вісім - безпечне початкове значення, а заголовки відповіді підкажуть, коли його потрібно зменшити.

Якщо ви не хочете підтримувати все це самостійно, API та SDK вже містять підключену логіку повторних спроб і пагінації, а короткий посібник зі SDK показує типізовану версію цього самого виклику.

Де має розміщуватися код

Майже всі випадки охоплюють чотири варіанти. Одноразовий скрипт, який скорочує CSV, чудово працює як звичайний Node із --env-file. Для форми на вашому сайті потрібен наведений вище обробник маршруту, де ключ зберігається на сервері, а браузер спілкується лише з вами. Для достатньо великої пакетної операції підійде воркер черги, щоб жоден HTTP-запит не чекав її завершення. А крок збирання, який генерує посилання кампаній, має належати CI, а ключ бути секретом репозиторію.

Є одне застереження щодо edge-середовищ виконання: node:crypto там недоступний, але crypto.randomUUID() є глобальним об'єктом Web Crypto у Vercel Edge, Cloudflare Workers і Deno, тож замініть імпорт, а решта коду залишиться без змін.

Середовище змінюється, але контрольний список незмінний: ключ у змінній середовища, тайм-аут для кожного запиту, читання res.ok перед тілом відповіді та ключ ідемпотентності для всього, що може виконатися двічі. Команди, які роблять це у великих масштабах, зазвичай переходять до вивчення інших можливостей API - кліків, тегів, терміну дії - і налаштовують вебхуки для подій посилань, а не виконують опитування.

Прочитайте основну серію

Ця стаття належить до інженерної категорії. Почніть із посібника з безкоштовного API скорочувача URL, щоб ознайомитися з формою ендпоінта та автентифікацією, а потім прочитайте матеріал про ліміти частоти та ідемпотентність, щоб правильно працювати під навантаженням. Актуальна довідка доступна в документації API, а стаття Скорочувачі URL для розробників розповідає, на що звертати увагу в API перед вибором.

Пов'язані матеріали в блозі

Поширені запитання

Як скоротити URL у JavaScript?

Надішліть HTTP POST до API скорочувача URL за допомогою fetch, передавши довгий URL у тілі JSON, а ключ API - як Bearer-токен, після чого прочитайте коротке посилання з розібраної відповіді. У Node 18 або новішої версії fetch вбудований, тому встановлювати пакет не потрібно: сформуйте заголовки, надішліть URL призначення до ендпоінта посилань і використайте поле short_url з отриманого JSON.

Чи можна скоротити URL у браузері за допомогою JavaScript?

Не маючи ключа API на сторінці. Усе в клієнтському JavaScript можна прочитати в DevTools, тому ключ, переданий у браузер, стає публічним, і будь-хто може витратити вашу квоту. Натомість викликайте скорочувач із серверного маршруту або безсерверної функції, а з браузера звертайтеся до цього маршруту.

Чому мій запит fetch до скорочувача URL повертає помилку CORS?

Тому що API скорочувача не надсилає заголовок Access-Control-Allow-Origin для вашого сайту, і це навмисно для ендпоінта з автентифікацією за ключем. Браузер блокує відповідь, навіть якщо запит міг успішно виконатися. Перенесення виклику на серверну частину прибирає браузер із ланцюжка, а разом із ним і цю помилку.

Чи потрібен пакет npm, щоб скорочувати URL у Node.js?

Ні. Починаючи з версії 18, Node постачається з глобальним fetch, а виклик API скорочувача - це один POST із двома заголовками, тож залежність майже нічого вам не дає. Використовуйте SDK, якщо вам потрібні типізовані відповіді, вбудовані повторні спроби та допоміжні засоби пагінації для багатьох ендпоінтів, а не одного виклику.

Як скоротити багато URL одночасно в Node.js?

Запустіть невеликий пул воркерів замість Promise.all для всього списку, щоб у кожен момент виконувалося лише кілька запитів і ви не перевищували ліміт частоти. Надсилайте окремий Idempotency-Key для кожного URL, щоб повторний запит повертав початкове посилання, а не створював дублікат.

Чи генерує fetch помилку для відповіді 401 або 500?

Ні, і це часто застає зненацька тих, хто переходить з axios. fetch відхиляє проміс лише через мережеву помилку або перерваний запит, тому відповідь 401 або 500 надходить як успішно отримана відповідь із параметром ok, встановленим у false. Перевіряйте response.ok самостійно перед читанням тіла, інакше невдалий виклик виглядатиме як успішний.

Спробуйте Elido

Вставте URL - отримайте коротке посилання

Без реєстрації. Посилання живе 30 днів. Зареєструйтесь, щоб зберегти назавжди.

Безкоштовно, без реєстрації · 2 на день

Спробуйте Elido

URL-скорочувач із хостингом у ЄС: власні домени, глибока аналітика, відкритий API. Безкоштовний тариф - без кредитної картки.

Теги
how to shorten a url in javascript
url shortener node js
shorten url javascript fetch
javascript url shortener api
bulk shorten urls node
node fetch post json

Читати далі