A self-hosted movie streaming app that downloads torrents on-demand and plays them in the browser. Built with React, Hono, WebTorrent, and Sequelize.
Movies are fetched from the YTS catalog, ranked by TMDb popularity, and searchable through an infinite-scroll UI. When you click a movie, the server downloads the torrent in real-time via Server-Sent Events, caches it for 24 hours, and serves the video with subtitle support and HTTP range seeking.
- Node.js 20+
- A NordVPN subscription (for the SOCKS5 proxy)
- A TMDb API read access token (for popularity ranking and poster images)
cp .env.example .env.local
npm installEdit .env.local with your values:
PORT=3900
APP_URL=http://localhost:8900
DATABASE_DIALECT=sqlite3
DATABASE_URL=sqlite3:database.sqlite
TMDB_API_KEY=your-tmdb-read-access-token
BASE_PATH=/
PROXY_HOST=amsterdam.nl.socks.nordhold.net
PROXY_PORT=1080
PROXY_USERNAME=your-nordvpn-username
PROXY_PASSWORD=your-nordvpn-passwordAll outbound requests to YTS are routed through a SOCKS5 proxy to bypass regional restrictions. NordVPN provides SOCKS5 servers for this purpose.
- Log in to my.nordaccount.com and go to Services > NordVPN.
- Under Manual setup, copy your service credentials (username and password). These are different from your NordVPN login credentials.
- Choose a server hostname from NordVPN's server list. The hostname format is
hostname.nordhold.netand the port is1080. - Set
PROXY_HOST,PROXY_PORT,PROXY_USERNAME, andPROXY_PASSWORDin.env.local.
The app uses the TMDb API to fetch poster images and downloads a daily export to rank movies by popularity. A read access token is required.
- Create an account at themoviedb.org.
- Go to Settings > API and copy your API Read Access Token (the long JWT, not the shorter API key).
- Set
TMDB_API_KEYin.env.local.
npm startThis starts the Vite dev server (client) and tsx watch (server) concurrently. The client runs at localhost:8900 and the server at localhost:3900.
# Manually refresh the movie catalog from YTS
npm run cli -- update-streams
# Download a specific torrent by hash
npm run cli -- download-torrent <hash>
# Remove expired cached downloads
npm run cli -- cleanup-cacheThese run automatically when the server is running:
- Every 6 hours — Refreshes the movie catalog from YTS and updates torrent availability.
- Every hour — Deletes cached downloads older than 24 hours and removes their files from disk.
npm run build
pm2 start --name app build/server/index.jsExample NGINX config:
server {
server_name _;
listen 80;
listen [::]:80;
client_max_body_size 500m;
index index.html;
root /var/www/torrents/build/client;
location / {
try_files $uri $uri/ /index.html =404;
}
location /api {
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_pass http://localhost:3900;
}
}Dotenvx is used for storing encrypted environment variables in version control. Config files are loaded in this order (later values override earlier ones):
.env.devor.env.prod(encrypted, committed).env.local(local overrides, gitignored)- Process environment variables
Encrypt/decrypt commands:
npm run env:encrypt:dev
npm run env:decrypt:dev
npm run env:encrypt:prod
npm run env:decrypt:prodKeep .env.keys out of version control.
npm run test # Run all tests
npm run check # TypeScript type checkSQLite is the default. To use MySQL instead, update your .env.local:
DATABASE_DIALECT=mysql
DATABASE_URL=mysql://root:secret@127.0.0.1:3306/torrents