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, crea una chiave nella dashboard del tuo account, poi metti la chiave nell'ambiente:
Lo script legge process.env.AMODB_KEY, quindi la chiave non finisce mai nel sorgente né nel controllo di versione.
export AMODB_KEY="YOUR_API_KEY"Passaggio 2: crea il progetto
Non c'è nulla da installare. Crea una cartella e un file cards.mjs vuoto:
La versione deve stampare v20 o superiore. L'estensione .mjs attiva i moduli ES senza un package.json.
mkdir movie-cards && cd movie-cards
node --versionPassaggio 3: invia la prima richiesta
Chiama GET / con la chiave in un header Authorization e un timeout, poi controlla lo stato prima di analizzare il corpo.
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 e valutazioni.
In ratings ogni fonte è { value, votes } oppure null. Il formattatore di schede qui sotto salta i null e unisce il resto:
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 per le regole della finestra ed errori 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.
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().
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;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 e stampa una scheda per ciascuno.
- Aggiungi
moodsosoundtracksaappend_to_response. I mood arrivano comemoods.results[]conname,avgevaluessu una scala da 0 a 10, emoods.sourceèmeasuredoestimated. Le colonne sonore arrivano comesoundtracks.results[], vuoto quando non è noto alcun album. Vedi mood e colonne sonore. - Confronta lo stesso compito in Python o leggi l'avvio rapido.