TMDB - v0.9.2
Go client library for The Movie Database (TMDB) API with automatic rate limiting, retry logic, and convenient helper functions.
Overview
TMDB provides a clean interface for interacting with TMDB's REST API, including:
- Movie search functionality
- Detailed movie information retrieval
- Cast and crew data
- Automatic rate limiting with exponential backoff
- Retry logic for rate limit errors
- Image URL helpers
- Environment variable configuration
- EZConf integration
Installation
go get git.haelnorr.com/h/golib/tmdb
Configuration
Environment Variables
// TMDB_TOKEN: TMDB API access token (required)
TMDB_TOKEN=your_api_token_here
Get your API token from: https://www.themoviedb.org/settings/api
Using ConfigFromEnv
import "git.haelnorr.com/h/golib/tmdb"
// Create API connection using environment variable
api, err := tmdb.NewAPIConnection()
if err != nil {
log.Fatal(err)
}
Using EZConf Integration
import (
"git.haelnorr.com/h/golib/ezconf"
"git.haelnorr.com/h/golib/tmdb"
)
// Register TMDB with EZConf
loader := ezconf.New()
err := loader.RegisterIntegration(tmdb.NewEZConfIntegration())
if err != nil {
log.Fatal(err)
}
// Load all configurations
if err := loader.Load(); err != nil {
log.Fatal(err)
}
// Get the API connection
api, ok := loader.GetConfig("tmdb").(*tmdb.APIConnection)
if !ok {
log.Fatal("tmdb config not found")
}
API Reference
Creating an API Connection
// NewAPIConnection creates a new TMDB API connection
api, err := tmdb.NewAPIConnection()
if err != nil {
log.Fatal(err)
}
Searching for Movies
// SearchMovies searches for movies by title
// Parameters:
// - query: search term
// - includeAdult: whether to include adult content
// - page: page number (1-indexed)
results, err := api.SearchMovies("Fight Club", false, 1)
if err != nil {
log.Fatal(err)
}
for _, movie := range results.Results {
fmt.Printf("%s (%s)\n", movie.Title, movie.ReleaseYear())
fmt.Printf("Overview: %s\n", movie.Overview)
fmt.Printf("Rating: %.1f/10\n", movie.VoteAverage)
}
Getting Movie Details
// GetMovie retrieves detailed information about a specific movie
// Parameter: movieID (TMDB movie ID)
movie, err := api.GetMovie(550) // Fight Club
if err != nil {
log.Fatal(err)
}
fmt.Printf("Title: %s\n", movie.Title)
fmt.Printf("Original Title: %s\n", movie.OriginalTitle)
fmt.Printf("Overview: %s\n", movie.Overview)
fmt.Printf("Release Date: %s\n", movie.ReleaseDate)
fmt.Printf("Runtime: %d minutes\n", movie.Runtime)
fmt.Printf("Budget: $%d\n", movie.Budget)
fmt.Printf("Revenue: $%d\n", movie.Revenue)
fmt.Printf("Rating: %.1f/10 (%d votes)\n", movie.VoteAverage, movie.VoteCount)
fmt.Printf("IMDb ID: %s\n", movie.IMDbID)
fmt.Printf("Homepage: %s\n", movie.Homepage)
fmt.Printf("Status: %s\n", movie.Status)
fmt.Printf("Tagline: %s\n", movie.Tagline)
// Genres
for _, genre := range movie.Genres {
fmt.Printf("Genre: %s\n", genre.Name)
}
// Production companies
for _, company := range movie.ProductionCompanies {
fmt.Printf("Company: %s\n", company.Name)
}
Getting Cast and Crew
// GetCredits retrieves cast and crew information for a movie
credits, err := api.GetCredits(550)
if err != nil {
log.Fatal(err)
}
// Display cast
fmt.Println("Cast:")
for i, actor := range credits.Cast {
if i >= 10 { // Limit to top 10
break
}
fmt.Printf(" %s as %s\n", actor.Name, actor.Character)
// Get profile image
profileURL := actor.GetProfile(&api.Image, "w185")
fmt.Printf(" Profile: %s\n", profileURL)
}
// Display crew (directors, writers, etc.)
fmt.Println("\nDirector:")
for _, member := range credits.Crew {
if member.Job == "Director" {
fmt.Printf(" %s\n", member.Name)
}
}
fmt.Println("\nWriters:")
for _, member := range credits.Crew {
if member.Department == "Writing" {
fmt.Printf(" %s (%s)\n", member.Name, member.Job)
}
}
fmt.Println("\nProducers:")
for _, member := range credits.Crew {
if member.Department == "Production" {
fmt.Printf(" %s (%s)\n", member.Name, member.Job)
}
}
Working with Images
TMDB provides various image URLs for posters, backdrops, and profile pictures.
Image Sizes
Poster Sizes:
w92- Small thumbnailw154- Thumbnailw185- Small posterw342- Medium posterw500- Large poster (recommended for most uses)w780- Extra large posteroriginal- Original resolution (very large)
Backdrop Sizes:
w300- Small backdropw780- Medium backdropw1280- Large backdrop (recommended)original- Original resolution
Profile Sizes:
w45- Tiny profile picw185- Standard profile pic (recommended)h632- Large profile picoriginal- Original resolution
Getting Image URLs
// Movie poster
posterURL := movie.GetPoster(&api.Image, "w500")
fmt.Printf("Poster: %s\n", posterURL)
// Movie backdrop
backdropURL := movie.GetBackdrop(&api.Image, "w1280")
fmt.Printf("Backdrop: %s\n", backdropURL)
// Actor profile
for _, actor := range credits.Cast {
profileURL := actor.GetProfile(&api.Image, "w185")
fmt.Printf("%s's profile: %s\n", actor.Name, profileURL)
}
Rate Limiting
TMDB has rate limits around 40 requests per second. This package implements automatic retry logic with exponential backoff:
- Initial backoff: 1 second
- Exponential growth: 1s → 2s → 4s → 8s → 16s → 32s (max)
- Maximum retries: 3 attempts
- Respects Retry-After header when provided by the API
All API calls automatically handle rate limiting, so you don't need to worry about implementing your own retry logic.
How It Works
When a rate limit is hit (HTTP 429), the library:
- Checks if a
Retry-Afterheader is present and uses that value - Otherwise, uses exponential backoff starting at 1 second
- Retries up to 3 times
- Returns an error if all retries are exhausted
// This is handled automatically - no special code needed
results, err := api.SearchMovies("Inception", false, 1)
if err != nil {
// Will only error after all retries are exhausted
log.Printf("Search failed: %v", err)
}
Error Handling
The package returns wrapped errors with context:
results, err := api.SearchMovies("Inception", false, 1)
if err != nil {
if strings.Contains(err.Error(), "rate limit exceeded") {
// All retry attempts exhausted
log.Println("Rate limit reached, try again later")
} else if strings.Contains(err.Error(), "unexpected status code: 401") {
// Invalid API token
log.Println("Invalid TMDB_TOKEN - check your API token")
} else if strings.Contains(err.Error(), "unexpected status code: 404") {
// Resource not found
log.Println("Movie not found")
} else {
// Network or other errors
log.Printf("Request failed: %v", err)
}
}
Data Structures
Movie
type Movie struct {
Adult bool `json:"adult"`
BackdropPath string `json:"backdrop_path"`
Budget int `json:"budget"`
Genres []Genre `json:"genres"`
Homepage string `json:"homepage"`
ID int `json:"id"`
IMDbID string `json:"imdb_id"`
OriginalLanguage string `json:"original_language"`
OriginalTitle string `json:"original_title"`
Overview string `json:"overview"`
Popularity float64 `json:"popularity"`
PosterPath string `json:"poster_path"`
ProductionCompanies []ProductionCompany `json:"production_companies"`
ProductionCountries []ProductionCountry `json:"production_countries"`
ReleaseDate string `json:"release_date"`
Revenue int64 `json:"revenue"`
Runtime int `json:"runtime"`
SpokenLanguages []SpokenLanguage `json:"spoken_languages"`
Status string `json:"status"`
Tagline string `json:"tagline"`
Title string `json:"title"`
Video bool `json:"video"`
VoteAverage float64 `json:"vote_average"`
VoteCount int `json:"vote_count"`
}
SearchResponse
type SearchResponse struct {
Page int `json:"page"`
Results []Movie `json:"results"`
TotalPages int `json:"total_pages"`
TotalResults int `json:"total_results"`
}
Credits
type Credits struct {
ID int `json:"id"`
Cast []Cast `json:"cast"`
Crew []Crew `json:"crew"`
}
type Cast struct {
Adult bool `json:"adult"`
Gender int `json:"gender"`
ID int `json:"id"`
KnownForDepartment string `json:"known_for_department"`
Name string `json:"name"`
OriginalName string `json:"original_name"`
Popularity float64 `json:"popularity"`
ProfilePath string `json:"profile_path"`
CastID int `json:"cast_id"`
Character string `json:"character"`
CreditID string `json:"credit_id"`
Order int `json:"order"`
}
type Crew struct {
Adult bool `json:"adult"`
Gender int `json:"gender"`
ID int `json:"id"`
KnownForDepartment string `json:"known_for_department"`
Name string `json:"name"`
OriginalName string `json:"original_name"`
Popularity float64 `json:"popularity"`
ProfilePath string `json:"profile_path"`
CreditID string `json:"credit_id"`
Department string `json:"department"`
Job string `json:"job"`
}
Complete Example
package main
import (
"fmt"
"log"
"git.haelnorr.com/h/golib/tmdb"
)
func main() {
// Create API connection
api, err := tmdb.NewAPIConnection()
if err != nil {
log.Fatal(err)
}
// Search for a movie
fmt.Println("Searching for movies...")
results, err := api.SearchMovies("The Matrix", false, 1)
if err != nil {
log.Fatal(err)
}
if len(results.Results) == 0 {
log.Fatal("No results found")
}
// Get the first result
movieID := results.Results[0].ID
fmt.Printf("\nFound: %s (%s)\n", results.Results[0].Title, results.Results[0].ReleaseYear())
// Get detailed movie information
fmt.Println("\nFetching movie details...")
movie, err := api.GetMovie(movieID)
if err != nil {
log.Fatal(err)
}
fmt.Printf("\nMovie Details:\n")
fmt.Printf("Title: %s\n", movie.Title)
fmt.Printf("Tagline: %s\n", movie.Tagline)
fmt.Printf("Overview: %s\n", movie.Overview)
fmt.Printf("Release Date: %s\n", movie.ReleaseDate)
fmt.Printf("Runtime: %d minutes\n", movie.Runtime)
fmt.Printf("Rating: %.1f/10 (%d votes)\n", movie.VoteAverage, movie.VoteCount)
fmt.Printf("Budget: $%d\n", movie.Budget)
fmt.Printf("Revenue: $%d\n", movie.Revenue)
fmt.Printf("IMDb: https://www.imdb.com/title/%s/\n", movie.IMDbID)
// Get poster URL
posterURL := movie.GetPoster(&api.Image, "w500")
fmt.Printf("Poster: %s\n", posterURL)
// Get cast and crew
fmt.Println("\nFetching cast and crew...")
credits, err := api.GetCredits(movieID)
if err != nil {
log.Fatal(err)
}
fmt.Println("\nMain Cast:")
for i, actor := range credits.Cast {
if i >= 5 { // Show top 5
break
}
fmt.Printf(" %s as %s\n", actor.Name, actor.Character)
}
fmt.Println("\nDirector:")
for _, member := range credits.Crew {
if member.Job == "Director" {
fmt.Printf(" %s\n", member.Name)
}
}
fmt.Printf("\nGenres: ")
for i, genre := range movie.Genres {
if i > 0 {
fmt.Print(", ")
}
fmt.Print(genre.Name)
}
fmt.Println()
}
Best Practices
-
Reuse API Connections
- Create one
APIConnectionand reuse it for all requests - Don't create a new connection for each API call
- Create one
-
Cache Responses
- Cache movie details and search results to reduce API calls
- Consider using an in-memory cache or database
-
Use Appropriate Image Sizes
- Use
w500for posters in most cases - Use
w1280for backdrops - Avoid
originalunless you specifically need high resolution
- Use
-
Handle Rate Limits Gracefully
- The library handles this automatically with retries
- Be aware that retries may introduce delays (up to ~60 seconds total)
-
Use Context with Timeout
- For production applications, consider adding timeout handling
- The library's automatic retry can take time
-
Paginate Search Results
- Search results are paginated (20 results per page)
- Use the
pageparameter to get additional results
Testing
The package includes comprehensive tests with 94.1% coverage. To run tests:
# Set your TMDB token
export TMDB_TOKEN=your_api_token_here
# Run all tests
go test -v ./...
# Run specific test
go test -v -run TestSearchMovies
# Run with coverage
go test -cover ./...
Related Packages
External Resources
License
MIT License - see LICENSE file for details