7 min de lectureIngénierie

Comment raccourcir une URL en JavaScript avec fetch et Node

Raccourcissez une URL en JavaScript avec un seul appel fetch, puis ajoutez un délai d'expiration, des nouvelles tentatives et l'idempotence, et découvrez pourquoi le navigateur est le mauvais endroit pour conserver la clé.

Marius Voß
DevRel · edge infra
Comment raccourcir une URL en JavaScript : un appel fetch Node envoie une URL de destination à l'API d'un raccourcisseur et récupère un lien court, tandis que le navigateur ne peut pas conserver la clé API

Raccourcir une URL en JavaScript, c'est envoyer une requête HTTP POST. Vous envoyez le lien long à l'API d'un raccourcisseur avec fetch, passez votre clé API comme jeton Bearer et récupérez le lien court dans la réponse JSON. Depuis Node 18, aucun paquet n'est à installer : fetch est une fonction globale.

La vraie question est l'endroit où ce code s'exécute. Une clé API de raccourcisseur est un identifiant qui permet de dépenser du quota, et le JavaScript côté client est public : le navigateur est donc exclu. Tout ce qui suit s'exécute côté serveur : un script Node, un gestionnaire de route ou une fonction serverless.

C'est la version JavaScript du guide général pour raccourcir une URL, qui couvre plutôt le tableau de bord et le flux dans le navigateur. Si vous n'avez pas encore choisi de service, la présentation de l'API gratuite de raccourcissement d'URL décrit la forme des requêtes, le modèle d'authentification et les limites du forfait gratuit supposées dans cet article. Les noms de champs ci-dessous sont ceux d'Elido, mais la structure, envoyer une destination par POST et récupérer une URL courte, se transpose à la plupart des raccourcisseurs modernes.

La méthode la plus rapide : un appel fetch dans Node

Placez la clé dans une variable d'environnement et envoyez la destination par POST à l'endpoint des liens :

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

Exécutez-le avec node --env-file=.env shorten.js : la clé ne touche jamais au code source.

La ligne if (!res.ok) n'est pas décorative. fetch ne rejette la promesse qu'en cas d'échec réseau ou d'interruption, donc un 401 ou un 500 revient sous la forme d'une promesse parfaitement résolue avec ok: false. Omettez cette vérification et une clé révoquée semblera fonctionner jusqu'à ce qu'un traitement en aval essaie d'utiliser undefined comme lien. Toute personne venant d'axios se fait prendre une fois à ce piège.

AbortSignal.timeout est l'autre habitude à prendre. Sans lui, une connexion bloquée attend que le socket meure de lui-même, ce qui signifie, dans une tâche cron, que la tâche disparaît tout simplement.

Pourquoi le navigateur est le mauvais endroit pour cela

Collez cet extrait dans un composant React et deux choses se cassent. La première est CORS : une API authentifiée par clé n'envoie pas Access-Control-Allow-Origin pour des sites arbitraires, donc le navigateur bloque la réponse. La lecture de la référence CORS de MDN pousse généralement à chercher un proxy qui ferait disparaître l'erreur.

L'erreur n'est que le symptôme. Le vrai problème est le second : une clé présente dans le code front-end est lisible par toute personne qui ouvre les outils de développement ou recherche dans le bundle. Les bundlers ne la masquent pas, les préfixes NEXT_PUBLIC_ la signalent, et une clé publique sur une API payante devient le quota gratuit de quelqu'un d'autre.

Un appel fetch du navigateur vers l'API d'un raccourcisseur d'URL est bloqué par CORS et la clé API est exposée dans le bundle, comparé au chemin correct où le navigateur appelle une route serveur qui conserve la clé et transmet la requête

La structure qui fonctionne place une route à vous au milieu. Le navigateur envoie une URL simple à votre propre endpoint, votre serveur ajoute la clé et celle-ci reste dans une variable d'environnement que le client ne voit jamais :

// 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 });
}

Validez url avant de la transmettre. Un endpoint ouvert qui raccourcit tout ce que n'importe qui lui envoie est une redirection ouverte avec quelques étapes supplémentaires, et l'article sur les vulnérabilités de redirection ouverte explique ce que les attaquants peuvent en faire.

Ajouter un délai d'expiration, des nouvelles tentatives et une clé d'idempotence

Le chemin nominal tient en huit lignes. Une tâche qui s'exécute sans surveillance doit survivre à un 429, à un 5xx transitoire et au cas plus délicat où le POST aboutit mais où la réponse n'arrive jamais : votre code expire, réessaie, et il existe désormais deux liens courts pour une seule destination.

Une clé d'idempotence comble cette faille. Générez-en une par URL, réutilisez-la pour chaque nouvelle tentative concernant cette URL et l'API renverra le lien d'origine au lieu d'en créer un second.

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`);
}

Un 401 ou un 403 ne se corrigera jamais tout seul : ils lèvent donc immédiatement une erreur au lieu de consommer trois tentatives. Un 429 attend exactement le temps indiqué par l'en-tête Retry-After. Seuls les 5xx utilisent un backoff exponentiel. L'analyse approfondie des limites de débit et de l'idempotence explique pourquoi une boucle de nouvelles tentatives naïve transforme une panne de fournisseur en deux liens.

Vous voulez exécuter les extraits tels quels ? Créez une clé avec le forfait gratuit, exportez-la sous le nom ELIDO_API_KEY et chaque exemple de cette page fonctionnera sans modification.

Raccourcir des URL en masse sans déclencher la limite de débit

La version en masse évidente est Promise.all(urls.map(shorten)). Elle lance toutes les requêtes dans le même tick : une liste de 500 URL devient donc 500 connexions simultanées et l'API répond à la plupart par un 429. Un petit pool règle le problème : un nombre fixe de workers prélève les éléments d'une file partagée, de sorte que la concurrence reste stable quelle que soit la longueur de la liste.

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 lance toutes les requêtes de raccourcissement à la fois et accumule les erreurs de limite de débit, comparé à un pool de workers borné qui maintient un nombre fixe de requêtes en cours pour raccourcir des URL en masse en toute sécurité

Associer les résultats à l'URL d'origine rend un échec partiel visible et relançable au lieu de créer un trou silencieux dans la sortie. Définissez concurrency selon ce que documente le forfait ; huit est un bon point de départ et les en-têtes de réponse vous indiquent quand réduire cette valeur.

Si vous préférez ne rien maintenir de tout cela, l'API et les SDK intègrent déjà la logique de nouvelles tentatives et de pagination, et le démarrage rapide des SDK présente la version typée du même appel.

Où le code doit-il vivre ?

Quatre emplacements couvrent presque tous les cas. Un script ponctuel qui raccourcit un CSV fonctionne très bien avec Node et --env-file. Un formulaire sur votre site a besoin du gestionnaire de route ci-dessus, où la clé reste sur le serveur et où le navigateur ne parle qu'à votre application. Un worker de file convient à un batch assez volumineux pour qu'aucune requête HTTP ne doive l'attendre. Enfin, une étape de build qui génère des liens de campagne doit appartenir à la CI, avec la clé comme secret du dépôt.

Une réserve pour les environnements edge : node:crypto n'y est pas disponible, mais crypto.randomUUID() est une fonction globale Web Crypto sur Vercel Edge, Cloudflare Workers et Deno. Remplacez donc l'import et le reste du code ne change pas.

L'emplacement change, mais pas la checklist : la clé dans l'environnement, un délai d'expiration sur chaque requête, res.ok lu avant le corps et une clé d'idempotence pour tout ce qui peut s'exécuter deux fois. Les équipes qui font cela à grande échelle finissent généralement par consulter ce que l'API expose d'autre, les clics, les tags et l'expiration, puis par configurer des webhooks pour les événements de liens plutôt que d'interroger l'API.

Lire la série de référence

Cet article appartient au cluster engineering. Commencez par le guide de l'API gratuite de raccourcissement d'URL pour la structure de l'endpoint et l'authentification, puis consultez l'article sur les limites de débit et l'idempotence pour vous comporter correctement sous charge. La référence à jour est la documentation de l'API, et les raccourcisseurs d'URL pour les développeurs explique ce qu'il faut rechercher dans une API avant de s'engager.

Articles connexes sur le blog

Questions fréquentes

Comment raccourcir une URL en JavaScript ?

Envoyez une requête HTTP POST à l'API d'un raccourcisseur d'URL avec fetch, en passant l'URL longue dans le corps JSON et votre clé API comme jeton Bearer, puis récupérez le lien court dans la réponse analysée. Avec Node 18 ou une version ultérieure, fetch est intégré : aucun paquet n'est nécessaire. Construisez les en-têtes, envoyez l'URL de destination à l'endpoint des liens et utilisez le champ short_url du JSON reçu.

Puis-je raccourcir une URL dans le navigateur avec JavaScript ?

Pas avec votre clé API dans la page. Tout ce qui se trouve dans du JavaScript côté client est lisible dans les outils de développement : une clé envoyée au navigateur devient donc une clé publique que n'importe qui peut utiliser aux dépens de votre quota. Appelez plutôt le raccourcisseur depuis une route serveur ou une fonction serverless, puis faites appeler cette route par le navigateur.

Pourquoi ma requête fetch vers un raccourcisseur d'URL renvoie-t-elle une erreur CORS ?

Parce que l'API du raccourcisseur n'envoie pas d'en-tête Access-Control-Allow-Origin pour votre site, ce qui est délibéré sur un endpoint authentifié par clé. Le navigateur bloque la réponse même si la requête a pu aboutir. Déplacer l'appel côté serveur retire le navigateur du chemin, ainsi que l'erreur.

Ai-je besoin d'un paquet npm pour raccourcir des URL dans Node.js ?

Non. Node fournit fetch comme fonction globale depuis la version 18, et un appel à l'API d'un raccourcisseur consiste en un seul POST avec deux en-têtes : une dépendance vous apporte donc très peu. Utilisez un SDK lorsque vous voulez des réponses typées, des nouvelles tentatives intégrées et des assistants de pagination sur de nombreux endpoints plutôt que pour un appel unique.

Comment raccourcir beaucoup d'URL à la fois dans Node.js ?

Lancez un petit pool de workers plutôt que Promise.all sur toute la liste, afin que seules quelques requêtes soient simultanément en cours et que vous restiez sous la limite de débit. Envoyez une clé Idempotency-Key par URL afin qu'une requête relancée renvoie le lien d'origine au lieu de créer un doublon.

fetch lève-t-il une erreur pour une réponse 401 ou 500 ?

Non, et cela surprend souvent les personnes qui viennent d'axios. fetch ne rejette la promesse qu'en cas d'échec réseau ou de requête interrompue : un 401 ou un 500 arrive donc comme une réponse résolue avec ok défini à false. Vérifiez vous-même response.ok avant de lire le corps, sinon un appel en échec semblera réussi.

Essayer Elido

Collez une URL, obtenez un lien court

Sans inscription. Lien actif 30 jours. Inscrivez-vous pour le garder pour toujours.

Gratuit, sans inscription · 2 par jour

Essayer Elido

Raccourcisseur d'URL hébergé en UE : domaines personnalisés, analyses approfondies et API ouverte. Forfait gratuit - sans carte bancaire.

Tags
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

Lire la suite