# Návod na filmové API v JavaScriptu

> Napište skript v Node.js, který převezme seznam ID filmů a pro každý vypíše kartu: název, rok, režiséra, hlavní herce a každé známé hodnocení. Používá vestavěný fetch, takže nejsou žádné balíčky npm. Obsazení a hodnocení přicházejí ve stejném požadavku přes append_to_response.

Napíšete `cards.mjs`, skript v Node.js. Předejte mu ID filmů a vypíše jednu kartu na film s názvem, rokem, režisérem, hlavními herci a každým známým hodnocením. Používá vestavěný `fetch`, vyžaduje Node 20 nebo novější a zabere asi 15 minut.

## Krok 1: Získejte API klíč

Tarify jsou předplacené a liší se jen rychlostí: 20, 40 nebo 120 požadavků za minutu, přičemž všechna data jsou v každém tarifu. Zvolte tarif na stránce [ceník](https://amodb.dev/cs/pricing/), vytvořte klíč v panelu účtu a pak klíč vložte do prostředí:

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

Skript čte `process.env.AMODB_KEY`, takže klíč nikdy nepřistane ve zdrojovém kódu ani ve správě verzí.

## Krok 2: Vytvořte projekt

Není co instalovat. Vytvořte složku a prázdný `cards.mjs`:

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

Verze musí vypsat `v20` nebo vyšší. Přípona `.mjs` zapíná moduly ES bez `package.json`.

## Krok 3: Odešlete první požadavek

Zavolejte `GET /3/movie/{movie_id}` s klíčem v hlavičce `Authorization` a s timeoutem a před parsováním těla zkontrolujte stav.

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

## Krok 4: Přidejte obsazení a hodnocení

Připojení `credits,ratings` ke stejnému volání vrátí seznam herců, seznam štábu a pět zdrojů hodnocení pod jejich vlastními klíči. To je jeden požadavek na film místo tří a celé volání se počítá jednou do vašich 20, 40 nebo 120 požadavků za minutu. Režisér je člen štábu, jehož `job` je `Director`. Pole popisují stránky [obsazení a štáb](https://amodb.dev/cs/credits/) a [hodnocení](https://amodb.dev/cs/ratings/).

V `ratings` je každý zdroj `{ value, votes }` nebo `null`. Formátovač karty níže vynechá null a zbytek spojí:

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

## Krok 5: Ošetřete chyby a limity požadavků

Každé selhání odpovídá jednomu rozhodnutí:

| Stav                   | Rozhodnutí                                                               |
| ---------------------- | ------------------------------------------------------------------------ |
| 401                    | Zastavte celý běh. Nesprávný nebo pozastavený klíč selže u každého ID.   |
| 404                    | Nahlaste ID a pokračujte dalším.                                         |
| 429                    | Počkejte `Retry-After` sekund (maximum 30) a zopakujte, nejvýše dvakrát. |
| 504                    | Opakujte podle stejného plánu, pak nahlaste.                             |
| Timeout nebo žádná síť | Počkejte jednu sekundu krát číslo pokusu, při třetím selhání nahlaste.   |

Hlavička `X-RateLimit-Remaining` ukazuje, kolik volání zbývá v aktuálním 60sekundovém okně. Zapisujte ji do logu, pokud chcete zpomalit dřív, než nastane 429. Pravidla okna najdete na stránce [limity požadavků](https://amodb.dev/cs/rate-limits/) a úplný seznam na stránce [chyby](https://amodb.dev/cs/errors/).

## Krok 6: Přidejte malou cache

Detaily se na edge cachují 6 hodin, takže opakovaný dotaz v tomto čase vrátí stejná data. Cachujte na své straně, abyste volání vynechali úplně. Skript drží `Map` položek `{ expires, data }`, při startu ji načte ze `.amodb-cache.json` a na konci ji zapíše zpět. Druhé spuštění nad stejnými ID neposílá žádné požadavky.

## Krok 7: Kompletní program

Uložte to jako `cards.mjs` a spusťte `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;
```

Spusťte ho dvakrát. První spuštění pošle jeden požadavek na ID, plus opakování, pokud se vrátí 429, a zapíše `.amodb-cache.json`. Druhé spuštění vypíše stejné karty a nic neposílá. Smažte soubor, abyste vynutili obnovu. ID, které neexistuje, vypíše `404: The resource you requested could not be found.` a nastaví návratový kód na 1, což se hodí, když skript běží v naplánované úloze.

Smyčka je záměrně sekvenční. Spuštění všech ID najednou by vyčerpalo minutový rozpočet jedním nárazem a u většiny by spustilo cestu 429. Pokud potřebujete rychlost, zpracovávejte ID po pěti a logiku opakování ponechte v `request()`.

## Další kroky

- Změňte smyčku na malou funkci, která vrací karty, a zavolejte ji z HTTP handleru na vlastním serveru. Klíč držte na serveru.
- Nahraďte seznam ID výsledky [trendů](https://amodb.dev/cs/trending-lists/) a vypište kartu pro každý.
- Přidejte `moods` nebo `soundtracks` do `append_to_response`. Nálady přicházejí jako `moods.results[]` s `name`, `avg` a `values` na škále 0 až 10 a `moods.source` je `measured` nebo `estimated`. Soundtracky přicházejí jako `soundtracks.results[]`, prázdné, když není znám žádné album. Viz [nálady](https://amodb.dev/cs/moods/) a [soundtracky](https://amodb.dev/cs/soundtracks/).
- Porovnejte stejnou úlohu v [Pythonu](https://amodb.dev/cs/python-movie-api/) nebo si přečtěte [rychlý start](https://amodb.dev/cs/quickstart/).

## Otázky a odpovědi

### Potřebuje tento skript nějaké balíčky npm?

Ne. Node 20 nebo novější obsahuje fetch, URL a AbortSignal.timeout, takže skript nemá žádné závislosti. Přípona .mjs umožňuje používat příkazy import a top level await. Spusťte ho pomocí node cards.mjs a ID.

### Mohu volat amodb přímo z JavaScriptu v prohlížeči?

Nedělejte to. Klíč ve front endovém kódu vidí každý návštěvník a může ho zkopírovat a vyčerpat. Volejte amodb z Node nebo z vlastního serveru a stránce pošlete jen pole, která potřebuje. Obrázky klíč nepotřebují a načítají se přímo.

### Kolik ID zpracuji za minutu?

Jeden požadavek na každé ID mimo cache, protože obsazení a hodnocení přicházejí přes append_to_response. Tarif s 20 požadavky za minutu pokryje asi 20 čerstvých ID za minutu a tarify 40 a 120 to zvyšují. Opakování po 429 pohltí krátké nárazy.

### Proč na kartě některá hodnocení chybí?

Každý z pěti zdrojů hodnocení je objekt s value a votes, nebo null, když ten zdroj pro titul není znám. Skript null vynechá, takže jeden titul může ukázat čtyři skóre a jiný žádné.

### Kde má cache žít v dlouho běžící službě?

Držte Map v paměti a soubor vynechte. Pro detaily použijte TTL 6 hodin, aby odpovídalo cache na edge, a prošlé položky při čtení mažte, aby Map neomezeně nerostla.

## Související stránky

- [API detailu filmu](https://amodb.dev/cs/movies/): API detailu filmu se stejnými cestami a JSON jako TMDB v3. Načtěte film podle ID, přidejte obsazení, hodnocení a nálady přes append_to_response, 14 jazyků.
- [API obsazení a štábu](https://amodb.dev/cs/credits/): API obsazení a štábu se stejnými cestami a JSON jako TMDB v3. Získejte herce a štáb filmů, seriálů, sezón a epizod s poli order, department a job.
- [Hodnocení filmů a seriálů](https://amodb.dev/cs/ratings/): API hodnocení filmů, které vrací skóre z IMDb, Kinopoisk, Metacritic, Rotten Tomatoes a TMDB pro titul jedním voláním, s null u každého neznámého zdroje.
- [Autentizace](https://amodb.dev/cs/authentication/): Jak poslat klíč amodb: api_key v dotazu, hlavička Authorization Bearer nebo apikey pro volání OMDb. Formát klíče, uložení, rotace a chyby 401.
- [Limity požadavků](https://amodb.dev/cs/rate-limits/): amodb omezuje klíč počtem požadavků za minutu v klouzavém okně 60 sekund: 20, 40 nebo 120 podle tarifu. Čtěte hlavičky limitu a řešte 429 pomocí Retry-After.
- [Návod na filmové API v Pythonu](https://amodb.dev/cs/python-movie-api/): Vytvořte v Pythonu s knihovnou requests nástroj, který vyhledá film a vypíše rok, herce, hodnocení IMDb a plakát, s opakováním při 429 a souborovou cache.

Aktualizováno: 2026-10-18. Source: https://amodb.dev/cs/javascript-movie-api/
