Сокращение URL в JavaScript - это один HTTP-запрос POST. Вы отправляете длинную ссылку в API сервиса сокращения с помощью fetch, передаёте ключ API как Bearer-токен и извлекаете короткую ссылку из ответа JSON. Начиная с Node 18 ничего устанавливать не нужно - fetch является глобальным объектом.
Важно, где выполняется этот код. Ключ API сервиса сокращения даёт возможность расходовать квоту, а клиентский JavaScript общедоступен, поэтому браузер не подходит. Всё ниже выполняется на серверной стороне: в скрипте Node, обработчике маршрута или бессерверной функции.
Это версия на JavaScript для общего руководства по сокращению URL, где вместо этого рассматриваются панель управления и сценарий в браузере. Если вы ещё не выбрали сервис, в обзоре бесплатного API сервиса сокращения URL описаны форма запроса, модель аутентификации и ограничения бесплатного тарифа, которые предполагаются в этой статье. Имена полей принадлежат Elido, но сама схема - отправить назначение и получить короткий URL - подходит большинству современных сервисов сокращения.
Самый быстрый способ: один вызов fetch в Node
Поместите ключ в переменную окружения и отправьте назначение в конечную точку links:
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 означает, что кто-то другой бесплатно расходует вашу квоту.
Рабочая схема помещает в середину ваш маршрут. Браузер отправляет обычный 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;
}
Если использовать исходный 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.
Читайте опорную серию статей
Эта статья входит в кластер материалов для разработчиков. Начните с руководства по бесплатному API сервиса сокращения URL, чтобы разобраться с формой конечной точки и аутентификацией, а затем перейдите к статье об ограничениях частоты и идемпотентности, где объясняется корректная работа под нагрузкой. Актуальная справка доступна в документации API, а в статье Сервисы сокращения URL для разработчиков рассказывается, на что обратить внимание в API перед выбором сервиса.
Ещё по теме в блоге
- Как сократить URL в Python с библиотекой requests
- Как сократить URL в PHP с curl, Guzzle и WordPress
- Как сократить URL в Go с net/http
- Бесплатный API сервиса сокращения URL: форма запроса, аутентификация и ограничения
- Как сократить URL в Java со встроенным HttpClient
- Как сократить URL в C# с HttpClient и .NET
Частые вопросы
Как сократить URL в JavaScript?
Отправьте HTTP-запрос POST к API сервиса сокращения URL с помощью fetch, передав длинный URL в теле JSON, а ключ API - как Bearer-токен, затем извлеките короткую ссылку из разобранного ответа. В Node 18 и более новых версиях fetch встроен, поэтому устанавливать пакет не нужно: сформируйте заголовки, отправьте URL назначения в конечную точку links и используйте поле 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 в день