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.
| Credential | Sent 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:
| Scenario | Why deduplication can be a problem |
|---|---|
| Polling loops | Two overlapping polls would collapse into one, hiding the newer response |
| Force-refresh after a mutation | Concurrent callers would receive the pre-mutation cached promise |
| Independent SSR data loaders | Two 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();| Option | Effect |
|---|---|
language | Localises titles, overviews, and taglines |
region | Filters release dates, certifications, and watch providers |
timezone | Scopes 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:
| Method | Input path field | Default size | Size type |
|---|---|---|---|
poster() | poster_path | w500 | PosterSize |
backdrop() | backdrop_path | w780 | BackdropSize |
logo() | logo_path | w185 | LogoSize |
profile() | profile_path | w185 | ProfileSize |
still() | still_path | w300 | StillSize |
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)
}
}| Property | Type | Description |
|---|---|---|
message | string | Human-readable message from the TMDB error payload |
http_status_code | number | HTTP status code of the response |
tmdb_status_code | number | TMDB-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.