🍿 @lorenzopant/tmdb
Getting started

Features

An overview of all built-in features of the @lorenzopant/tmdb client.

@lorenzopant/tmdb is more than a thin TMDB HTTP wrapper. The client ships several built-in features designed to improve performance, developer ergonomics, and reliability out of the box — no extra configuration needed for the defaults.


Authentication

The client accepts both modern and legacy TMDB credentials and automatically selects the correct transport for each.

CredentialSent as
JWT (Read Access Token)Authorization: Bearer <token> header
v3 API key?api_key=<key> query parameter

Detection is purely local — no network call is made. See the Authentication guide for details.


Request Deduplication

When multiple identical requests are fired at the same time — for example, two components rendering simultaneously and both calling tmdb.movies.details(550) — the client issues only one HTTP request and resolves all callers from the same Promise.

// Both calls share a single fetch — no duplicate network request
const [movie1, movie2] = await Promise.all([
	tmdb.movies.details({ movie_id: 550, language: "en-US" }),
	tmdb.movies.details({ movie_id: 550, language: "en-US" }),
]);

How it works:

  • Each request is identified by a stable key derived from the endpoint URL + serialised parameters.
  • Parameter key order is normalised (sorted), so { language, page } and { page, language } are treated as the same request.
  • Once the in-flight promise settles (success or error), it is evicted from the map so the next call always triggers a fresh fetch.

Deduplication is on by default and applied to every method in the client. Only concurrent requests benefit — sequential calls always produce independent fetches.

Disabling deduplication

Set deduplication: false in TMDBOptions to make every call trigger its own fetch regardless of concurrent siblings.

const tmdb = new TMDB(apiKey, { deduplication: false });

Useful when you need guaranteed-fresh data:

ScenarioWhy deduplication can be a problem
Polling loopsTwo overlapping polls would collapse into one, hiding the newer response
Force-refresh after a mutationConcurrent callers would receive the pre-mutation cached promise
Independent SSR data loadersTwo route handlers that coincidentally request the same resource should not share state

Null Sanitisation

TMDB responses frequently contain null values for optional fields. The client recursively converts every null in the response body to undefined, so TypeScript optional types (field?: string) model the data accurately and you never need to guard against null at the call site.

const movie = await tmdb.movies.details({ movie_id: 550 });

// tagline is typed as `string | undefined` — never `null`
if (movie.tagline) {
	console.log(movie.tagline);
}

Sanitisation is applied deeply — including nested objects and array elements — before the response reaches your code.


Default Options

Pass a TMDBOptions object to the constructor to set global defaults that are merged into every request automatically. Individual call-site params always take precedence over defaults.

const tmdb = new TMDB(apiKey, {
	language: "it-IT",
	region: "IT",
	timezone: "Europe/Rome",
});

// language and region are merged automatically
const popular = await tmdb.movie_lists.popular();
OptionEffect
languageLocalises titles, overviews, and taglines
regionFilters release dates, certifications, and watch providers
timezoneScopes TV airing-time calculations

See the Options guide for the full reference.


Image URL Builder

tmdb.images is a pure URL builder — no HTTP request, no async. Pass a file path returned by any API response and get back a ready-to-use image URL.

The builder exposes five methods, one per TMDB image category:

MethodInput path fieldDefault sizeSize type
poster()poster_pathw500PosterSize
backdrop()backdrop_pathw780BackdropSize
logo()logo_pathw185LogoSize
profile()profile_pathw185ProfileSize
still()still_pathw300StillSize

Path fields such as poster_path, backdrop_path, profile_path, and still_path are optional on most TMDB response types. Always guard against undefined before building a URL.

const movie = await tmdb.movies.details({ movie_id: 550 });
const tv = await tmdb.tv_series.details({ series_id: 1396 });
const person = await tmdb.people.details({ person_id: 31 });
const episode = await tmdb.tv_episodes.details({
	series_id: 1396,
	season_number: 1,
	episode_number: 1,
});

// Poster — movies, TV series, collections
if (movie.poster_path) {
	const posterUrl = tmdb.images.poster(movie.poster_path, "w500");
	// → "https://image.tmdb.org/t/p/w500/pB8BM7pdSp6B6Ih7QZ4DrQ3PmJK.jpg"
}

// Backdrop — movies, TV series
if (movie.backdrop_path) {
	const backdropUrl = tmdb.images.backdrop(movie.backdrop_path, "w1280");
	// → "https://image.tmdb.org/t/p/w1280/rr7E0NoGKxvbkb89eR1GwfoYjpA.jpg"
}

// Logo — companies, networks (logo_path is always present on NetworkDetails)
const network = await tmdb.networks.details({ network_id: 213 });
const logoUrl = tmdb.images.logo(network.logo_path, "w185");
// → "https://image.tmdb.org/t/p/w185/alqLicR1ZMHMaZGP3xRQxn9sq7p.png"

// Profile — people (actors, directors, crew)
if (person.profile_path) {
	const profileUrl = tmdb.images.profile(person.profile_path, "w185");
	// → "https://image.tmdb.org/t/p/w185/r7WLn4Kbnqb6oJ8TmSI0e3GKEAO.jpg"
}

// Still — TV episode thumbnails
if (episode.still_path) {
	const stillUrl = tmdb.images.still(episode.still_path, "w300");
	// → "https://image.tmdb.org/t/p/w300/ydlY3iPfeOAvu8gVqrxPoMvzNCn.jpg"
}

Each method's second argument is optional. When omitted, the built-in default size for that category is used (w500 for posters, w780 for backdrops, w185 for logos and profiles, w300 for stills).

// Uses default size — equivalent to poster(path, "w500")
if (movie.poster_path) {
	const posterUrl = tmdb.images.poster(movie.poster_path);
}

Default sizes can be overridden globally once in TMDBOptions.images and are applied automatically across all calls. See the Images option for the full reference.

Autocomplete Image Paths

If you prefer API responses to already contain full image URLs, enable images.autocomplete_paths. This opt-in transformation rewrites supported relative image fields such as poster_path and backdrop_path using your configured image defaults.

const tmdb = new TMDB(apiKey, {
	images: {
		autocomplete_paths: true,
		default_image_sizes: {
			posters: "w500",
		},
	},
});

const movie = await tmdb.movies.details({ movie_id: 550 });

console.log(movie.poster_path);
// → "https://image.tmdb.org/t/p/w500/e1mjopzAS2KNsvpbpahQ1a6SkSn.jpg"

This is disabled by default so existing consumers still receive the original TMDB relative paths.


Error Handling

All HTTP and API errors are thrown as a TMDBError instance, which extends the built-in Error class with three additional properties:

import { TMDBError } from "@lorenzopant/tmdb";

try {
	const movie = await tmdb.movies.details({ movie_id: 0 });
} catch (err) {
	if (err instanceof TMDBError) {
		console.log(err.message); // TMDB status message
		console.log(err.http_status_code); // e.g. 404
		console.log(err.tmdb_status_code); // e.g. 34 (resource not found)
	}
}
PropertyTypeDescription
messagestringHuman-readable message from the TMDB error payload
http_status_codenumberHTTP status code of the response
tmdb_status_codenumberTMDB-specific status code; -1 for library errors

Network errors (connection refused, DNS failures, etc.) are re-thrown as-is and are not wrapped in TMDBError — catch Error for those.


Logging

The client includes a built-in structured logger. Enable it with logger: true or supply a custom function to integrate with any logging library.

// Built-in console logger
const tmdb = new TMDB(apiKey, { logger: true });

// Custom logger (e.g. pino, winston)
const tmdb = new TMDB(apiKey, {
	logger: (entry) => {
		if (entry.type === "error") logger.error(entry);
		else logger.info(entry);
	},
});

Log entries cover three lifecycle stages: request, response, and error. See the Logging guide for the full reference.


Rate Limiting

The client includes an opt-in sliding-window rate limiter that queues outgoing requests so they stay within TMDB's API budget automatically — no manual batching or sleep() calls needed.

// Enable with defaults (~40 requests per second)
const tmdb = new TMDB(apiKey, { rate_limit: true });

// Custom budget
const tmdb = new TMDB(apiKey, { rate_limit: { max_requests: 20, per_ms: 5_000 } });

When the budget is exhausted, requests are held in a FIFO queue and dispatched as slots free up. The limiter applies to both read and mutation requests.

Rate limiting is disabled by default. Enable it when running bulk data-fetching scripts or background jobs that fire many requests in a tight loop — scenarios where hitting 429 Too Many Requests would otherwise be likely.

See the Rate Limiting guide for full details.


Response Caching

The client includes an opt-in in-memory response cache. Repeated identical GET requests are served from memory without hitting the network — useful for SSR pages that re-render frequently or client-side apps with stable reference data.

// Enable with defaults (5-minute TTL, no size limit)
const tmdb = new TMDB(apiKey, { cache: true });

// Custom TTL and bounded memory
const tmdb = new TMDB(apiKey, { cache: { ttl: 60_000, max_size: 500 } });

Cache keys are derived from the endpoint path and parameters (sorted). Entries expire lazily on the next access after their TTL has elapsed — no background timers. Mutations are never cached.

Invalidation

Use tmdb.cache to clear or invalidate entries at runtime:

// Remove one entry after a mutation
tmdb.cache?.invalidate("/movie/now_playing", { language: "en-US" });

// Wipe everything — e.g. on sign-out
tmdb.cache?.clear();

Excluding endpoints

Certain endpoints can be permanently opted out even while the global cache is enabled:

const tmdb = new TMDB(apiKey, {
	cache: {
		excluded_endpoints: ["/trending", /\/discover\//],
	},
});

Response caching is disabled by default. The cache is in-process only — not shared across server instances or SSR requests. For persistent caching use the response.onSuccess interceptor to integrate with an external store.

See the Response Caching guide for full details.

On this page