A Next.js 16 service that caches TMDB movie and TV show data in PostgreSQL, MySQL, or MariaDB, providing a fast local API with lazy sync and admin dashboard.
- Next.js 16 with App Router
- PostgreSQL, MySQL, or MariaDB for data storage
- Prisma 7 as ORM
- next-auth v4 for admin authentication
- shadcn/ui + Tailwind CSS 4 for admin UI
- Node.js 20+
- PostgreSQL 14+, MySQL 8+, or MariaDB 10.6+
- TMDB API key from themoviedb.org
Choose the database type before setup. The selected type is fixed for that installation; changing it later means setting up a new database or migrating data yourself.
Create an empty database named tmdb_service in one of:
# PostgreSQL
createdb tmdb_service
# MySQL
mysql -u root -p -e "CREATE DATABASE tmdb_service CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
# MariaDB
mariadb -u root -p -e "CREATE DATABASE tmdb_service CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"cd tmdb-service
npm installCopy .env.example to .env and fill in your values:
DATABASE_PROVIDER=mysql
DATABASE_URL=mysql://user:password@host:3306/tmdb_service
TMDB_API_KEY=your_tmdb_api_key_here
NEXTAUTH_SECRET=your_random_secret_here
NEXTAUTH_URL=http://localhost:3000
ADMIN_USERNAME=admin@example.com
ADMIN_PASSWORD=your_secure_password
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your_smtp_username
SMTP_PASSWORD=your_smtp_password
SMTP_FROM="TMDB Service <no-reply@example.com>"Database URL examples:
# PostgreSQL
DATABASE_PROVIDER=postgresql
DATABASE_URL=postgresql://user:password@host:5432/tmdb_service
# MySQL
DATABASE_PROVIDER=mysql
DATABASE_URL=mysql://user:password@host:3306/tmdb_service
# MariaDB
DATABASE_PROVIDER=mariadb
DATABASE_URL=mysql://user:password@host:3306/tmdb_serviceDATABASE_PROVIDER must match DATABASE_URL before running Prisma commands or building the app. For MySQL, use DATABASE_PROVIDER=mysql and a mysql://... URL.
For a new local/dev database, db push is the simplest setup path for any provider:
# Generate Prisma Client for your selected database
npm run db:generate
# Create/update tables
npm run db:push
# Seed with admin user and default API key
npm run db:seedPrisma migration SQL is provider-specific. Do not run PostgreSQL migrations against MySQL/MariaDB, or MySQL migrations against PostgreSQL.
This repository currently has PostgreSQL migration files in prisma/migrations/:
cat prisma/migrations/migration_lock.toml
# provider = "postgresql"Use the checked-in migrations:
DATABASE_PROVIDER=postgresql npx prisma migrate deploy
npm run build
pm2 restart tmdbCreate a new PostgreSQL migration after schema changes:
DATABASE_PROVIDER=postgresql npx prisma migrate dev --name describe_changeCommit the generated prisma/migrations/* files.
The checked-in migrations are not MySQL-compatible. For the current MySQL/MariaDB install, use:
DATABASE_PROVIDER=mysql npx prisma db push
npm run build
pm2 restart tmdbUse DATABASE_PROVIDER=mariadb for MariaDB if your .env uses that provider.
To use real MySQL/MariaDB migrations later, create a separate MySQL/MariaDB migration history from prisma/schema.mysql.prisma on a clean database, then commit that provider-specific migration set. Do not mix it with the PostgreSQL migration folder.
npm run devThe app will be available at http://localhost:3000.
Go to http://localhost:3000/login and use the admin credentials you set in .env.
After login, the admin dashboard provides:
- Movies - Browse, search, and manage cached movies
- TV Shows - Browse, search, and manage cached TV shows
- Sync - Trigger bulk syncs (trending movies/TV, top rated)
- API Keys - Create, manage, and revoke API keys
All API endpoints require the x-api-key header. Rate limit: 60 requests per minute per key.
For TMDB-compatible content mirroring, use /api/v1/tmdb/{tmdb_path}. It forwards public TMDB content GET endpoints, caches successful JSON responses, and keeps TMDB's response shape. See docs/api.md.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/movies |
List all movies with pagination |
GET |
/api/v1/movies/:id |
Get movie details by TMDB ID (lazy syncs if missing) |
Query parameters for /api/v1/movies:
| Param | Type | Default | Description |
|---|---|---|---|
page |
int | 1 | Page number |
limit |
int | 20 | Items per page |
q |
string | - | Search by title |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/tv |
List all TV shows with pagination |
GET |
/api/v1/tv/:id |
Get TV show details by TMDB ID (lazy syncs if missing) |
Query parameters for /api/v1/tv:
| Param | Type | Default | Description |
|---|---|---|---|
page |
int | 1 | Page number |
limit |
int | 20 | Items per page |
q |
string | - | Search by name |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/search?q=term |
Search both movies and TV shows |
# Get movies list
curl -H "x-api-key: your_api_key" http://localhost:3000/api/v1/movies
# Get movie by TMDB ID (lazy syncs if not cached)
curl -H "x-api-key: your_api_key" http://localhost:3000/api/v1/movies/278
# Search movies and TV shows
curl -H "x-api-key: your_api_key" "http://localhost:3000/api/v1/search?q=matrix"
# Paginate with search
curl -H "x-api-key: your_api_key" "http://localhost:3000/api/v1/movies?page=2&limit=10&q=action"{
"data": [...],
"pagination": {
"page": 1,
"limit": 20,
"total": 150,
"totalPages": 8
}
}{
"id": 278,
"title": "The Shawshank Redemption",
"overview": "...",
"poster_path": "/...",
"backdrop_path": "/...",
"credits": { "cast": [], "crew": [] },
"videos": { "results": [] }
}{
"data": {
"movies": [...],
"tvShows": [...]
}
}npm run db:generate
npm run build
PORT=3000 npm run startThe app uses the normal Next.js production server via next start. Set NEXTAUTH_URL to the public HTTPS domain in production.
When a client requests a movie or TV show by TMDB ID that isn't in the local database, the service automatically fetches it from TMDB, stores it, and returns it. This eliminates the need for pre-populating the database.