# Tutoriel API de films JavaScript

> Écrivez un script Node.js qui prend une liste d'identifiants de films et affiche une fiche pour chacun : titre, année, réalisateur, distribution principale et toutes les notes connues. Il utilise le fetch intégré, donc aucun paquet npm. Crédits et notes arrivent dans la même requête grâce à append_to_response.

Vous allez écrire `cards.mjs`, un script Node.js. Donnez-lui des identifiants de films et il affiche une fiche par film avec le titre, l'année, le réalisateur, la distribution principale et toutes les notes connues. Il utilise le `fetch` intégré, demande Node 20 ou plus récent et prend environ 15 minutes.

## Étape 1 : obtenir une clé d'API

Les offres sont prépayées et ne diffèrent que par la vitesse : 20, 40 ou 120 requêtes par minute, avec toutes les données dans chaque offre. Choisissez-en une sur les [tarifs](https://amodb.dev/fr/pricing/), créez une clé dans le tableau de bord de votre compte, puis placez la clé dans l'environnement :

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

Le script lit `process.env.AMODB_KEY` : la clé ne se retrouve donc jamais dans le code source ni dans le contrôle de version.

## Étape 2 : créer le projet

Il n'y a rien à installer. Créez un dossier et un fichier `cards.mjs` vide :

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

La version doit afficher `v20` ou plus. L'extension `.mjs` active les modules ES sans `package.json`.

## Étape 3 : envoyer la première requête

Appelez `GET /3/movie/{movie_id}` avec la clé dans un en-tête `Authorization` et un délai d'expiration, puis vérifiez le statut avant d'analyser le corps.

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

## Étape 4 : ajouter les crédits et les notes

Ajouter `credits,ratings` au même appel renvoie la liste de la distribution, la liste de l'équipe et les cinq sources de notes sous leurs propres clés. Cela fait une requête par film au lieu de trois, et l'appel entier compte une fois dans vos 20, 40 ou 120 requêtes par minute. Le réalisateur est le membre de l'équipe dont le `job` est `Director`. Les champs sont décrits sur [crédits](https://amodb.dev/fr/credits/) et [notes](https://amodb.dev/fr/ratings/).

Dans `ratings`, chaque source est `{ value, votes }` ou `null`. Le formateur de fiche ci-dessous ignore les null et joint le reste :

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

## Étape 5 : gérer les erreurs et les limites de débit

Chaque échec correspond à une décision :

| Statut                         | Décision                                                                                         |
| ------------------------------ | ------------------------------------------------------------------------------------------------ |
| 401                            | Arrêter toute l'exécution. Une clé fausse ou suspendue fait échouer chaque identifiant.          |
| 404                            | Signaler l'identifiant et continuer avec le suivant.                                             |
| 429                            | Attendre `Retry-After` secondes (plafond 30) et réessayer, jusqu'à deux nouvelles tentatives.    |
| 504                            | Réessayer selon le même calendrier, puis signaler.                                               |
| Délai dépassé ou pas de réseau | Attendre une seconde multipliée par le numéro de la tentative, puis signaler au troisième échec. |

L'en-tête `X-RateLimit-Remaining` indique combien d'appels restent dans la fenêtre de 60 secondes en cours. Consignez-le si vous voulez ralentir avant qu'un 429 ne se produise. Voir [limites de débit](https://amodb.dev/fr/rate-limits/) pour les règles de la fenêtre et [erreurs](https://amodb.dev/fr/errors/) pour la liste complète.

## Étape 6 : ajouter un petit cache

Les détails sont mis en cache 6 heures en périphérie : redemander dans ce délai renvoie les mêmes données. Mettez en cache de votre côté pour éviter complètement l'appel. Le script garde une `Map` d'entrées `{ expires, data }`, la charge depuis `.amodb-cache.json` au démarrage et la réécrit à la fin. Une seconde exécution sur les mêmes identifiants ne fait aucune requête.

## Étape 7 : le programme complet

Enregistrez ceci sous `cards.mjs` et lancez `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;
```

Lancez-le deux fois. La première exécution envoie une requête par identifiant, plus une nouvelle tentative si un 429 revient, et écrit `.amodb-cache.json`. La seconde affiche les mêmes fiches et n'envoie rien. Supprimez le fichier pour forcer une actualisation. Un identifiant qui n'existe pas affiche `404: The resource you requested could not be found.` et met le code de sortie à 1, ce qui aide quand le script tourne dans une tâche planifiée.

La boucle est séquentielle volontairement. Lancer tous les identifiants à la fois consommerait un budget par minute en une seule rafale et déclencherait le chemin 429 pour la plupart d'entre eux. Si vous avez besoin de vitesse, traitez les identifiants par groupes de cinq et gardez la logique de nouvelle tentative dans `request()`.

## Pour aller plus loin

- Transformez la boucle en petite fonction qui renvoie des fiches, puis appelez-la depuis un gestionnaire HTTP de votre propre serveur. Gardez la clé côté serveur.
- Remplacez la liste d'identifiants par les résultats de [tendances](https://amodb.dev/fr/trending-lists/) et affichez une fiche pour chacun.
- Ajoutez `moods` ou `soundtracks` à `append_to_response`. Les ambiances arrivent sous la forme `moods.results[]` avec `name`, `avg` et `values` sur une échelle de 0 à 10, et `moods.source` vaut `measured` ou `estimated`. Les bandes originales arrivent sous la forme `soundtracks.results[]`, vide quand aucun album n'est connu. Voir [ambiances](https://amodb.dev/fr/moods/) et [bandes originales](https://amodb.dev/fr/soundtracks/).
- Comparez la même tâche en [Python](https://amodb.dev/fr/python-movie-api/) ou lisez le [démarrage rapide](https://amodb.dev/fr/quickstart/).

## Questions et réponses

### Ce script a-t-il besoin de paquets npm ?

Non. Node 20 ou plus récent fournit fetch, URL et AbortSignal.timeout : le script n'a donc aucune dépendance. L'extension .mjs permet d'utiliser les instructions import et l'await de niveau supérieur. Lancez-le avec node cards.mjs suivi des identifiants.

### Puis-je appeler amodb directement depuis du JavaScript de navigateur ?

Évitez-le. Une clé dans du code front-end est visible de tous les visiteurs et peut être copiée et épuisée. Appelez amodb depuis Node ou votre propre serveur et n'envoyez à la page que les champs dont elle a besoin. Les images n'exigent aucune clé et se chargent directement.

### Combien d'identifiants puis-je traiter par minute ?

Une requête par identifiant non mis en cache, car les crédits et les notes arrivent via append_to_response. L'offre à 20 requêtes par minute couvre environ 20 identifiants neufs par minute, et les offres à 40 et 120 montent d'autant. La relance sur 429 absorbe les rafales courtes.

### Pourquoi certaines notes manquent-elles sur une fiche ?

Chacune des cinq sources de notes est un objet avec value et votes, ou null quand cette source n'est pas connue pour le titre. Le script écarte les null : un titre peut donc afficher quatre notes et un autre aucune.

### Où placer le cache dans un service qui tourne longtemps ?

Gardez la Map en mémoire et laissez de côté le fichier. Utilisez une durée de vie de 6 heures pour les détails, comme le cache de périphérie, et supprimez les entrées expirées à la lecture pour que la Map ne grandisse pas sans limite.

## Pages liées

- [API de détails de film](https://amodb.dev/fr/movies/): API de détails de film aux chemins et au JSON de TMDB v3. Obtenez un film par id, ajoutez crédits, notes et ambiances avec append_to_response, en 14 langues.
- [API des crédits](https://amodb.dev/fr/credits/): API crédits aux chemins et au JSON de TMDB v3. Distribution et équipe des films, séries, saisons et épisodes, avec les champs order, department et job.
- [Notes des films et séries](https://amodb.dev/fr/ratings/): API de notes de films : scores IMDb, Kinopoisk, Metacritic, Rotten Tomatoes et TMDB d'un titre en un appel, avec null pour toute source inconnue.
- [Authentification](https://amodb.dev/fr/authentication/): Comment envoyer votre clé amodb : api_key dans la requête, en-tête Authorization Bearer, ou apikey pour OMDb. Format, stockage, rotation et erreurs 401.
- [Limites de débit](https://amodb.dev/fr/rate-limits/): amodb limite chaque clé en requêtes par minute sur une fenêtre glissante de 60 secondes : 20, 40 ou 120 selon l'offre. Lisez les en-têtes et gérez le 429.
- [Tutoriel API de films en Python](https://amodb.dev/fr/python-movie-api/): Un outil Python en CLI qui cherche un film et affiche année, distribution, note IMDb et affiche, avec relance 429 et cache fichier. Utilise requests.

Mis à jour: 2026-10-18. Source: https://amodb.dev/fr/javascript-movie-api/
