# Tutorial API film JavaScript

> Scrivi uno script Node.js che prende un elenco di ID di film e stampa una scheda per ciascuno: titolo, anno, regista, cast principale e tutte le valutazioni note. Usa il fetch integrato, quindi nessun pacchetto npm. Crediti e valutazioni arrivano nella stessa richiesta tramite append_to_response.

Scriverai `cards.mjs`, uno script Node.js. Dagli degli ID di film e stampa una scheda per film con titolo, anno, regista, cast principale e tutte le valutazioni note. Usa il `fetch` integrato, richiede Node 20 o successivo e dura circa 15 minuti.

## Passaggio 1: ottieni una chiave API

I piani sono prepagati e differiscono solo per la velocità: 20, 40 o 120 richieste al minuto, con tutti i dati su ogni piano. Scegline uno nella pagina dei [prezzi](https://amodb.dev/it/pricing/), crea una chiave nella dashboard del tuo account, poi metti la chiave nell'ambiente:

```bash
export AMODB_KEY="YOUR_API_KEY"
```

Lo script legge `process.env.AMODB_KEY`, quindi la chiave non finisce mai nel sorgente né nel controllo di versione.

## Passaggio 2: crea il progetto

Non c'è nulla da installare. Crea una cartella e un file `cards.mjs` vuoto:

```bash
mkdir movie-cards && cd movie-cards
node --version
```

La versione deve stampare `v20` o superiore. L'estensione `.mjs` attiva i moduli ES senza un `package.json`.

## Passaggio 3: invia la prima richiesta

Chiama `GET /3/movie/{movie_id}` con la chiave in un header `Authorization` e un timeout, poi controlla lo stato prima di analizzare il corpo.

```js
const res = await fetch('https://api.amodb.dev/3/movie/550?language=en-US', {
  headers: { Authorization: `Bearer ${process.env.AMODB_KEY}` },
  signal: AbortSignal.timeout(10_000),
});
console.log(res.status, (await res.json()).title);
```

## Passaggio 4: aggiungi crediti e valutazioni

Accodare `credits,ratings` alla stessa chiamata restituisce l'elenco del cast, l'elenco della troupe e le cinque fonti di valutazione sotto le loro chiavi. È una richiesta per film invece di tre, e l'intera chiamata conta una volta nelle tue 20, 40 o 120 richieste al minuto. Il regista è il membro della troupe il cui `job` è `Director`. I campi sono descritti nelle pagine [crediti](https://amodb.dev/it/credits/) e [valutazioni](https://amodb.dev/it/ratings/).

In `ratings` ogni fonte è `{ value, votes }` oppure `null`. Il formattatore di schede qui sotto salta i null e unisce il resto:

```js
const scores = Object.entries(movie.ratings ?? {})
  .filter(([, entry]) => entry !== null)
  .map(([source, entry]) => `${source} ${entry.value}`)
  .join(', ');
```

## Passaggio 5: gestisci errori e limiti di richieste

Ogni errore corrisponde a una decisione:

| Stato                  | Decisione                                                                                 |
| ---------------------- | ----------------------------------------------------------------------------------------- |
| 401                    | Ferma l'intera esecuzione. Una chiave sbagliata o sospesa fa fallire ogni ID.             |
| 404                    | Segnala l'ID e continua con il successivo.                                                |
| 429                    | Attendi i secondi di `Retry-After` (massimo 30) e riprova, fino a due nuovi tentativi.    |
| 504                    | Riprova con lo stesso schema, poi segnala.                                                |
| Timeout o nessuna rete | Attendi un secondo moltiplicato per il numero del tentativo, poi segnala al terzo errore. |

L'header `X-RateLimit-Remaining` mostra quante chiamate restano nella finestra di 60 secondi in corso. Registralo nel log se vuoi rallentare prima che arrivi un 429. Vedi [limiti di richieste](https://amodb.dev/it/rate-limits/) per le regole della finestra ed [errori](https://amodb.dev/it/errors/) per l'elenco completo.

## Passaggio 6: aggiungi una piccola cache

I dettagli restano in cache sull'edge per 6 ore, quindi chiederli di nuovo in quel tempo restituisce gli stessi dati. Usa una cache dalla tua parte per saltare del tutto la chiamata. Lo script tiene una `Map` di voci `{ expires, data }`, la carica da `.amodb-cache.json` all'avvio e la riscrive alla fine. Una seconda esecuzione sugli stessi ID non fa alcuna richiesta.

## Passaggio 7: il programma completo

Salvalo come `cards.mjs` ed esegui `node cards.mjs 550 603 27205`.

```js
import { readFile, writeFile } from 'node:fs/promises';

const BASE = 'https://api.amodb.dev/3';
const KEY = process.env.AMODB_KEY;
const CACHE_FILE = '.amodb-cache.json';
const DETAILS_TTL_MS = 6 * 60 * 60 * 1000;

if (!KEY) {
  console.error('Set AMODB_KEY first.');
  process.exit(1);
}

class ApiError extends Error {
  constructor(status, message) {
    super(`${status}: ${message}`);
    this.status = status;
  }
}

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function request(path, params = {}) {
  const url = new URL(BASE + path);
  for (const [name, value] of Object.entries(params)) url.searchParams.set(name, value);

  for (let attempt = 1; attempt <= 3; attempt++) {
    let res;
    try {
      res = await fetch(url, {
        headers: { Authorization: `Bearer ${KEY}` },
        signal: AbortSignal.timeout(10_000),
      });
    } catch (err) {
      if (attempt === 3) throw new Error(`network error: ${err.message}`);
      await sleep(1000 * attempt);
      continue;
    }
    if (res.status === 401) {
      throw new ApiError(401, 'the key is invalid or suspended. Check AMODB_KEY.');
    }
    if ((res.status === 429 || res.status === 504) && attempt < 3) {
      const seconds = Math.min(Number(res.headers.get('Retry-After')) || 1, 30);
      await sleep(seconds * 1000);
      continue;
    }
    if (!res.ok) {
      const body = await res.json().catch(() => ({}));
      throw new ApiError(res.status, body.status_message ?? res.statusText);
    }
    return res.json();
  }
}

async function loadCache() {
  try {
    return new Map(Object.entries(JSON.parse(await readFile(CACHE_FILE, 'utf8'))));
  } catch {
    return new Map();
  }
}

async function cachedGet(cache, path, params, ttlMs) {
  const key = `${path}?${new URLSearchParams(params)}`;
  const hit = cache.get(key);
  if (hit && hit.expires > Date.now()) return hit.data;
  const data = await request(path, params);
  cache.set(key, { expires: Date.now() + ttlMs, data });
  return data;
}

function formatCard(movie) {
  const year = (movie.release_date ?? '').slice(0, 4) || 'n/a';
  const director = movie.credits.crew.find((p) => p.job === 'Director')?.name ?? 'unknown';
  const cast = movie.credits.cast.slice(0, 3).map((p) => p.name).join(', ');
  const scores = Object.entries(movie.ratings ?? {})
    .filter(([, entry]) => entry !== null)
    .map(([source, entry]) => `${source} ${entry.value}`)
    .join(', ');
  return [
    `${movie.title} (${year}), ${movie.runtime ?? '?'} min, directed by ${director}`,
    `  Cast: ${cast || 'none listed'}`,
    `  Ratings: ${scores || 'none known'}`,
  ].join('\n');
}

const ids = process.argv.slice(2);
if (ids.length === 0) {
  console.error('Usage: node cards.mjs 550 603 27205');
  process.exit(1);
}

const cache = await loadCache();
let failed = false;
for (const id of ids) {
  try {
    const params = { language: 'en-US', append_to_response: 'credits,ratings' };
    console.log(formatCard(await cachedGet(cache, `/movie/${id}`, params, DETAILS_TTL_MS)));
  } catch (err) {
    console.error(`id ${id}: ${err.message}`);
    failed = true;
    if (err.status === 401) break;
  }
}
await writeFile(CACHE_FILE, JSON.stringify(Object.fromEntries(cache)));
process.exitCode = failed ? 1 : 0;
```

Eseguilo due volte. La prima esecuzione invia una richiesta per ID, più un nuovo tentativo se torna un 429, e scrive `.amodb-cache.json`. La seconda stampa le stesse schede e non invia nulla. Elimina il file per forzare un aggiornamento. Un ID che non esiste stampa `404: The resource you requested could not be found.` e imposta il codice di uscita a 1, utile quando lo script gira dentro un job pianificato.

Il ciclo è sequenziale di proposito. Eseguire tutti gli ID insieme brucerebbe un budget al minuto in un solo picco e farebbe scattare il percorso 429 per la maggior parte. Se ti serve velocità, elabora gli ID in gruppi di cinque e tieni la logica dei nuovi tentativi in `request()`.

## Prossimi passi

- Trasforma il ciclo in una piccola funzione che restituisce schede, poi chiamala da un gestore HTTP sul tuo server. Tieni la chiave sul server.
- Sostituisci l'elenco di ID con i risultati delle [tendenze](https://amodb.dev/it/trending-lists/) e stampa una scheda per ciascuno.
- Aggiungi `moods` o `soundtracks` a `append_to_response`. I mood arrivano come `moods.results[]` con `name`, `avg` e `values` su una scala da 0 a 10, e `moods.source` è `measured` o `estimated`. Le colonne sonore arrivano come `soundtracks.results[]`, vuoto quando non è noto alcun album. Vedi [mood](https://amodb.dev/it/moods/) e [colonne sonore](https://amodb.dev/it/soundtracks/).
- Confronta lo stesso compito in [Python](https://amodb.dev/it/python-movie-api/) o leggi l'[avvio rapido](https://amodb.dev/it/quickstart/).

## Domande e risposte

### Questo script richiede pacchetti npm?

No. Node 20 o successivo include fetch, URL e AbortSignal.timeout, quindi lo script non ha dipendenze. L'estensione .mjs permette di usare le istruzioni import e l'await di primo livello. Eseguilo con node cards.mjs seguito dagli ID.

### Posso chiamare amodb direttamente dal JavaScript del browser?

Evitalo. Una chiave nel codice front-end è visibile a ogni visitatore e può essere copiata ed esaurita. Chiama amodb da Node o dal tuo server e invia alla pagina solo i campi che servono. Le immagini non richiedono chiave e si caricano direttamente.

### Quanti ID posso elaborare in un minuto?

Una richiesta per ogni ID non in cache, perché crediti e valutazioni arrivano tramite append_to_response. Il piano da 20 richieste al minuto copre circa 20 ID nuovi al minuto, e i piani da 40 e 120 scalano di conseguenza. Il retry su 429 assorbe i picchi brevi.

### Perché alcune valutazioni mancano da una scheda?

Ognuna delle cinque fonti di valutazione è un oggetto con value e votes, oppure null quando quella fonte non è nota per il titolo. Lo script scarta i null, quindi un titolo può mostrare quattro punteggi e un altro nessuno.

### Dove deve stare la cache in un servizio sempre attivo?

Tieni la Map in memoria e salta il file. Usa un TTL di 6 ore per i dettagli, come la cache edge, ed elimina le voci scadute alla lettura così la Map non cresce senza limite.

## Pagine correlate

- [API dei dettagli del film](https://amodb.dev/it/movies/): API dettagli film con gli stessi percorsi e JSON di TMDB v3. Ottieni un film per ID, aggiungi crediti, valutazioni e mood con append_to_response, in 14 lingue.
- [API dei crediti](https://amodb.dev/it/credits/): API crediti con gli stessi percorsi e JSON di TMDB v3. Ottieni cast e troupe di film, serie, stagioni ed episodi, con i campi order, department e job.
- [Valutazioni di film e serie TV](https://amodb.dev/it/ratings/): API valutazioni film: punteggi IMDb, Kinopoisk, Metacritic, Rotten Tomatoes e TMDB di un titolo in una chiamata, con null per le fonti non note.
- [Autenticazione](https://amodb.dev/it/authentication/): Come inviare la chiave amodb: api_key nella query, un header Authorization Bearer o apikey per le chiamate OMDb. Formato, conservazione, rotazione, errori 401.
- [Limiti di richieste](https://amodb.dev/it/rate-limits/): amodb limita ogni chiave per richieste al minuto in una finestra scorrevole di 60 secondi: 20, 40 o 120 in base al piano. Leggi gli header, gestisci il 429.
- [Tutorial API film in Python](https://amodb.dev/it/python-movie-api/): Un tool Python da riga di comando che cerca un film e stampa anno, cast, valutazione IMDb e locandina, con retry su 429 e cache su file. Usa requests.

Aggiornato: 2026-10-18. Source: https://amodb.dev/it/javascript-movie-api/
