Tutorial API filme JavaScript

Scrie un script Node.js care primește o listă de ID-uri de filme și afișează un card pentru fiecare: titlu, an, regizor, distribuția principală și fiecare evaluare cunoscută. Folosește fetch încorporat, deci nu are pachete npm. Creditele și evaluările sosesc în aceeași cerere prin append_to_response.

.js JavaScript Actualizat Vezi ca Markdown

Vei scrie cards.mjs, un script Node.js. Dă-i ID-uri de filme și afișează câte un card pe film, cu titlu, an, regizor, distribuția principală și fiecare evaluare cunoscută. Folosește fetch încorporat, cere Node 20 sau mai nou și durează cam 15 minute.

Pasul 1: obține o cheie API

Planurile sunt plătite în avans și diferă doar prin viteză: 20, 40 sau 120 de cereri pe minut, cu toate datele la fiecare plan. Alege unul la prețuri, creează o cheie în panoul contului tău, apoi pune cheia în mediu:

Scriptul citește process.env.AMODB_KEY, deci cheia nu ajunge niciodată în sursă sau în controlul versiunilor.

Shell
export AMODB_KEY="YOUR_API_KEY"

Pasul 2: creează proiectul

Nu e nimic de instalat. Fă un folder și un cards.mjs gol:

Versiunea trebuie să afișeze v20 sau mai mult. Extensia .mjs activează modulele ES fără un package.json.

Shell
mkdir movie-cards && cd movie-cards
node --version

Pasul 3: trimite prima cerere

Apelează GET /3/movie/{movie_id} cu cheia într-un antet Authorization și cu un timeout, apoi verifică starea înainte să parsezi corpul.

JavaScript
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);

Pasul 4: adaugă credite și evaluări

Adăugarea credits,ratings la același apel întoarce lista de distribuție, lista de echipă și cele cinci surse de evaluări sub propriile chei. Asta înseamnă o cerere pe film în loc de trei, iar întregul apel se numără o singură dată din cele 20, 40 sau 120 de cereri pe minut. Regizorul este membrul echipei al cărui job este Director. Câmpurile sunt descrise la credite și evaluări.

În ratings, fiecare sursă este { value, votes } sau null. Formatorul de carduri de mai jos omite valorile null și le unește pe celelalte:

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

Pasul 5: tratează erorile și limitele de cereri

Fiecare eșec duce la o decizie:

StareDecizie
401Oprește toată rularea. O cheie greșită sau suspendată face să eșueze fiecare ID.
404Raportează ID-ul și continuă cu următorul.
429Așteaptă Retry-After secunde (plafon 30) și reîncearcă, până la două reîncercări.
504Reîncearcă după același program, apoi raportează.
Timeout sau fără rețeaAșteaptă o secundă înmulțită cu numărul încercării, apoi raportează la a treia eșuare.

Antetul X-RateLimit-Remaining arată câte apeluri au rămas în fereastra curentă de 60 de secunde. Loghează-l dacă vrei să încetinești înainte să apară un 429. Vezi limite de cereri pentru regulile ferestrei și erori pentru lista completă.

Pasul 6: adaugă un cache mic

Detaliile sunt ținute în cache la margine 6 ore, deci o nouă cerere în acest interval întoarce aceleași date. Pune în cache și la tine ca să sari complet peste apel. Scriptul ține un Map cu intrări { expires, data }, îl încarcă din .amodb-cache.json la pornire și îl scrie înapoi la final. A doua rulare peste aceleași ID-uri nu face nicio cerere.

Pasul 7: programul complet

Salvează asta ca cards.mjs și rulează node cards.mjs 550 603 27205.

Rulează-l de două ori. Prima rulare trimite o cerere pe ID, plus o reîncercare dacă vine un 429, și scrie .amodb-cache.json. A doua rulare afișează aceleași carduri și nu trimite nimic. Șterge fișierul ca să forțezi o reîmprospătare. Un ID care nu există afișează 404: The resource you requested could not be found. și setează codul de ieșire la 1, ceea ce ajută când scriptul rulează într-un job programat.

Bucla este secvențială intenționat. Rularea tuturor ID-urilor deodată ar consuma bugetul pe minut dintr-o rafală și ar declanșa calea 429 pentru majoritatea. Dacă ai nevoie de viteză, procesează ID-urile în grupuri de cinci și păstrează logica de reîncercare în request().

JavaScript
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;

Pașii următori

  • Transformă bucla într-o mică funcție care întoarce carduri, apoi apelează-o dintr-un handler HTTP de pe propriul server. Ține cheia pe server.
  • Înlocuiește lista de ID-uri cu rezultatele din tendințe și afișează un card pentru fiecare.
  • Adaugă moods sau soundtracks la append_to_response. Atmosferele sosesc ca moods.results[] cu name, avg și values pe o scară de la 0 la 10, iar moods.source este measured sau estimated. Coloanele sonore sosesc ca soundtracks.results[], goale când nu se cunoaște niciun album. Vezi atmosfere și coloane sonore.
  • Compară aceeași sarcină în Python sau citește ghidul de pornire rapidă.