7 min di letturaIngegneria

Come accorciare un URL in JavaScript con fetch e Node

Accorcia un URL in JavaScript con una sola chiamata fetch, poi aggiungi timeout, retry e idempotenza, e scopri perché il browser è il posto sbagliato per custodire la chiave.

Marius Voß
DevRel · edge infra
Come accorciare un URL in JavaScript: una chiamata fetch di Node che invia un URL di destinazione a un'API di accorciamento e legge un link breve, mentre al browser viene impedito di custodire la chiave API

Accorciare un URL in JavaScript richiede una sola richiesta HTTP POST. Invia il link lungo all'API di un accorciatore con fetch, passa la tua chiave API come token Bearer e leggi il link breve dalla risposta JSON. Da Node 18 in poi non serve installare pacchetti: fetch è globale.

Il punto delicato è dove viene eseguito il codice. Una chiave API di un accorciatore è una credenziale che consuma quota, mentre il JavaScript lato client è pubblico, quindi il browser è escluso. Tutto ciò che segue viene eseguito lato server: uno script Node, un gestore di route, una funzione serverless.

Questa è la versione JavaScript della guida generale su come accorciare un URL, che tratta invece la dashboard e il flusso dal browser. Se non hai ancora scelto un servizio, la panoramica sull'API gratuita per accorciare URL descrive la struttura della richiesta, il modello di autenticazione e i limiti del piano gratuito presupposti da questo articolo. I nomi dei campi qui sotto sono quelli di Elido, ma la struttura, cioè inviare una destinazione con POST e ricevere un URL breve, è compatibile con la maggior parte degli accorciatori moderni.

Il modo più rapido: una chiamata fetch in Node

Metti la chiave in una variabile d'ambiente e invia la destinazione all'endpoint dei link:

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

Eseguilo con node --env-file=.env shorten.js e la chiave non entra mai nel codice sorgente.

La riga if (!res.ok) non è decorativa. fetch rifiuta la promessa solo in caso di errore di rete o interruzione, quindi una risposta 401 o 500 torna come promessa perfettamente risolta con ok: false. Salta il controllo e una chiave revocata sembrerà esattamente un successo finché qualcosa a valle non proverà a usare undefined come link. Chi arriva da axios ci sbatte contro almeno una volta.

AbortSignal.timeout è l'altra abitudine che vale la pena adottare. Senza, una connessione bloccata resta in attesa finché il socket non muore da solo, il che in un cron job significa che il job semplicemente sparisce.

Perché il browser è il posto sbagliato

Incolla quel frammento in un componente React e si rompono due cose. La prima è CORS: un'API autenticata con chiave non invia Access-Control-Allow-Origin per siti arbitrari, quindi il browser blocca la risposta. Leggere il riferimento CORS di MDN di solito porta a cercare un proxy che faccia sparire l'errore.

L'errore è il sintomo. Il vero problema è il secondo: una chiave nel codice front-end è leggibile da chiunque apra DevTools o cerchi nel bundle. I bundler non la nascondono, i prefissi NEXT_PUBLIC_ la segnalano e una chiave pubblica su un'API a pagamento diventa la quota gratuita di qualcun altro.

Una chiamata fetch dal browser a un'API di accorciamento URL bloccata da CORS con la chiave API esposta nel bundle, a confronto con il percorso corretto in cui il browser chiama una route server che custodisce la chiave e inoltra la richiesta

La struttura funzionante mette in mezzo una tua route. Il browser invia un URL semplice al tuo endpoint, il server aggiunge la chiave e la chiave resta in una variabile d'ambiente che il client non vede mai:

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

Convalida url prima di inoltrarlo. Un endpoint aperto che accorcia qualsiasi cosa venga inviata da chiunque è un reindirizzamento aperto con qualche passaggio in più, e l'articolo sulle vulnerabilità dei reindirizzamenti aperti spiega cosa fanno gli aggressori con uno di questi.

Aggiungi un timeout, retry e una chiave di idempotenza

Il percorso felice è di otto righe. Un job eseguito senza supervisione deve sopravvivere a un 429, a un 5xx temporaneo e al caso più insidioso, in cui la POST va a buon fine ma la risposta non arriva mai: il codice va in timeout, ritenta e ora esistono due link brevi per una destinazione.

Una chiave di idempotenza chiude questa falla. Generane una per ogni URL, riutilizzala per ogni retry di quell'URL e l'API restituirà il link originale invece di crearne un secondo.

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 o un 403 non si risolverà mai da solo, quindi genera subito un errore invece di consumare tre tentativi. Un 429 attende esattamente quanto richiesto dall'intestazione Retry-After. Solo i 5xx ricevono un backoff esponenziale. L'approfondimento su limiti di frequenza e idempotenza spiega perché un ciclo di retry ingenuo trasforma l'interruzione di un fornitore in due link.

Vuoi eseguire i frammenti così come sono? Crea una chiave sul piano gratuito, esportala come ELIDO_API_KEY e ogni esempio di questa pagina funzionerà senza modifiche.

Accorcia URL in blocco senza sforare il limite di frequenza

La versione di massa più ovvia è Promise.all(urls.map(shorten)). Avvia ogni richiesta nello stesso tick, quindi un elenco di 500 URL diventa 500 connessioni simultanee e l'API risponde alla maggior parte con 429. Un piccolo pool risolve il problema: un numero fisso di worker che prelevano da una coda condivisa, così la concorrenza resta costante indipendentemente dalla lunghezza dell'elenco.

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 che avvia tutte le richieste di accorciamento insieme e raccoglie gli errori di limite, a confronto con un pool di worker con concorrenza limitata che mantiene un numero fisso di richieste in corso per accorciare URL in blocco in sicurezza

Indicizzare i risultati con l'URL originale rende un errore parziale visibile e rieseguibile invece di lasciare un vuoto silenzioso nell'output. Imposta concurrency su quanto documentato dal piano; otto è un punto di partenza sicuro e le intestazioni della risposta indicano quando ridurla.

Se preferisci non gestire nulla di tutto questo, le API e gli SDK forniscono già la logica di retry e paginazione, mentre la guida rapida agli SDK mostra la versione tipizzata della stessa chiamata.

Dove dovrebbe vivere il codice

Quattro collocazioni coprono quasi ogni caso. Uno script occasionale che accorcia un CSV funziona bene come semplice Node con --env-file. Un modulo sul tuo sito richiede il gestore di route qui sopra, dove la chiave resta sul server e il browser parla solo con te. Un worker di coda è adatto a un batch abbastanza grande da non dover attendere alcuna richiesta HTTP. E un passaggio di build che genera link di campagna appartiene alla CI, con la chiave come segreto del repository.

Una precisazione per i runtime edge: node:crypto non è disponibile lì, ma crypto.randomUUID() è una funzione globale della Web Crypto API su Vercel Edge, Cloudflare Workers e Deno, quindi sostituisci l'import e il resto del codice non cambia.

La collocazione cambia, la checklist no: chiave nell'ambiente, timeout su ogni richiesta, lettura di res.ok prima del corpo e chiave di idempotenza su tutto ciò che può essere eseguito due volte. I team che lo fanno su scala finiscono di solito per leggere cos'altro espone l'API, come clic, tag e scadenza, e collegare webhook per gli eventi dei link invece di fare polling.

Leggi la serie cardine

Questo articolo fa parte del cluster engineering. Inizia con la guida all'API gratuita per accorciare URL per la struttura dell'endpoint e l'autenticazione, poi leggi l'articolo su limiti di frequenza e idempotenza per comportarti correttamente sotto carico. Il riferimento aggiornato è la documentazione dell'API, mentre accorciatori di URL per sviluppatori spiega cosa cercare in un'API prima di sceglierne una.

Correlati sul blog

Domande frequenti

Come accorcio un URL in JavaScript?

Invia una richiesta HTTP POST all'API di un accorciatore di URL con fetch, passando l'URL lungo nel corpo JSON e la tua chiave API come token Bearer, poi leggi il link breve dalla risposta analizzata. In Node 18 o versioni successive fetch è integrato, quindi non serve installare pacchetti: prepara le intestazioni, invia l'URL di destinazione all'endpoint dei link e usa il campo short_url del JSON ricevuto.

Posso accorciare un URL nel browser con JavaScript?

Non con la tua chiave API nella pagina. Qualsiasi elemento nel JavaScript lato client è leggibile in DevTools, quindi una chiave distribuita nel browser è una chiave pubblica che chiunque può usare consumando la tua quota. Chiama l'accorciatore da una route server o da una funzione serverless e fai chiamare quella route al browser.

Perché la mia richiesta fetch a un accorciatore di URL restituisce un errore CORS?

Perché l'API dell'accorciatore non invia un'intestazione Access-Control-Allow-Origin per il tuo sito, ed è una scelta deliberata su un endpoint autenticato con chiave. Il browser blocca la risposta anche se la richiesta potrebbe essere andata a buon fine. Spostare la chiamata lato server rimuove il browser dal percorso e con esso anche l'errore.

Mi serve un pacchetto npm per accorciare URL in Node.js?

No. Node include fetch come globale dalla versione 18 in poi, e una chiamata API a un accorciatore è una POST con due intestazioni, quindi una dipendenza offre ben poco. Scegli un SDK quando vuoi risposte tipizzate, retry integrati e strumenti di paginazione su molti endpoint invece di una singola chiamata.

Come accorcio molti URL contemporaneamente in Node.js?

Esegui un piccolo pool di worker invece di usare Promise.all sull'intero elenco, così solo una manciata di richieste è in corso in ogni momento e resti sotto il limite di frequenza. Invia una Idempotency-Key per ogni URL, così una richiesta ritentata restituisce il link originale invece di crearne un duplicato.

fetch genera un errore per una risposta 401 o 500?

No, e questo trae in inganno chi arriva da axios. fetch rifiuta la promessa solo in caso di errore di rete o richiesta interrotta, quindi una risposta 401 o 500 arriva come risposta risolta con ok impostato su false. Controlla tu response.ok prima di leggere il corpo, altrimenti una chiamata fallita sembrerà riuscita.

Prova Elido

Incolla un URL, ottieni un link breve

Senza registrazione. Il link vive 30 giorni. Iscriviti per conservarlo.

Gratis, nessuna registrazione richiesta · 2 al giorno

Prova Elido

Accorciatore di URL ospitato nell'UE: domini personalizzati, analisi approfondite e API aperta. Piano gratuito - senza carta di credito.

Tag
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

Continua a leggere