# 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.

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](https://amodb.dev/ro/pricing/), creează o cheie în panoul contului tău, apoi pune cheia în mediu:

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

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

## Pasul 2: creează proiectul

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

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

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

## 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.

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

## 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](https://amodb.dev/ro/credits/) și [evaluări](https://amodb.dev/ro/ratings/).

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

```js
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:

| Stare                  | Decizie                                                                                |
| ---------------------- | -------------------------------------------------------------------------------------- |
| 401                    | Oprește toată rularea. O cheie greșită sau suspendată face să eșueze fiecare ID.       |
| 404                    | Raportează ID-ul și continuă cu următorul.                                             |
| 429                    | Așteaptă `Retry-After` secunde (plafon 30) și reîncearcă, până la două reîncercări.    |
| 504                    | Reîncearcă după același program, apoi raportează.                                      |
| Timeout sau fără rețea | Aș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](https://amodb.dev/ro/rate-limits/) pentru regulile ferestrei și [erori](https://amodb.dev/ro/errors/) 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`.

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

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()`.

## 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](https://amodb.dev/ro/trending-lists/) ș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](https://amodb.dev/ro/moods/) și [coloane sonore](https://amodb.dev/ro/soundtracks/).
- Compară aceeași sarcină în [Python](https://amodb.dev/ro/python-movie-api/) sau citește [ghidul de pornire rapidă](https://amodb.dev/ro/quickstart/).

## Întrebări și răspunsuri

### Are acest script nevoie de pachete npm?

Nu. Node 20 sau mai nou vine cu fetch, URL și AbortSignal.timeout, deci scriptul nu are dependențe. Extensia .mjs îți permite să folosești instrucțiuni import și top level await. Rulează-l cu node cards.mjs urmat de ID-uri.

### Pot apela amodb direct din JavaScript în browser?

Evită. O cheie în codul de front end este vizibilă oricărui vizitator și poate fi copiată și epuizată. Apelează amodb din Node sau de pe propriul server și trimite paginii doar câmpurile de care are nevoie. Imaginile nu cer cheie și se încarcă direct.

### Câte ID-uri pot procesa într-un minut?

O cerere pe ID fără cache, pentru că creditele și evaluările sosesc prin append_to_response. Planul de 20 de cereri pe minut acoperă cam 20 de ID-uri noi pe minut, iar planurile de 40 și 120 cresc proporțional. Reîncercarea la 429 absoarbe rafalele scurte.

### De ce lipsesc unele evaluări de pe un card?

Fiecare dintre cele cinci surse de evaluări este un obiect cu value și votes sau null când sursa respectivă nu este cunoscută pentru titlu. Scriptul elimină valorile null, deci un titlu poate arăta patru scoruri, iar altul niciunul.

### Unde ar trebui să stea cache-ul într-un serviciu care rulează mult timp?

Ține Map-ul în memorie și renunță la fișier. Folosește un TTL de 6 ore pentru detalii, ca să se potrivească cu cache-ul la margine, și șterge intrările expirate la citire, ca Map-ul să nu crească fără limită.

## Pagini conexe

- [API detalii film](https://amodb.dev/ro/movies/): API detalii film cu aceleași căi și același JSON ca TMDB v3. Preia un film după ID, adaugă credite, evaluări și atmosferă cu append_to_response, în 14 limbi.
- [API credite](https://amodb.dev/ro/credits/): API credite cu aceleași căi și același JSON ca TMDB v3. Distribuția și echipa pentru filme, seriale, sezoane și episoade, cu order, department și job.
- [Evaluări pentru filme și TV](https://amodb.dev/ro/ratings/): API evaluări filme ce întoarce scorurile IMDb, Kinopoisk, Metacritic, Rotten Tomatoes și TMDB pentru un titlu într-un apel, cu null pentru sursele necunoscute.
- [Autentificare](https://amodb.dev/ro/authentication/): Cum trimiți cheia amodb: api_key în interogare, antetul Authorization Bearer sau apikey pentru apelurile OMDb. Format, stocare, rotire și erorile 401.
- [Limite de cereri](https://amodb.dev/ro/rate-limits/): amodb limitează fiecare cheie în cereri pe minut, în fereastră glisantă de 60 de secunde: 20, 40 sau 120. Citește antetele și tratează 429 cu Retry-After.
- [Tutorial API filme Python](https://amodb.dev/ro/python-movie-api/): Instrument Python în linia de comandă care caută un film și afișează anul, distribuția, evaluarea IMDb și posterul, cu reîncercare la 429 și cache în fișier.

Actualizat: 2026-10-18. Source: https://amodb.dev/ro/javascript-movie-api/
