Your personal, self-hosted media streaming server. Stream your movies and TV series to any device — no subscriptions, no cloud, no limits.
- About
- Features
- Tech Stack
- Project Structure
- Getting Started
- Remote Access
- API Reference
- Screenshots
- Roadmap
- Contributing
- License
Zentro is a self-hosted media streaming web application inspired by Netflix and Plex. It runs entirely on your own machine — your laptop, PC, or home server — and streams your local movie and TV series files to any device through a browser.
No paid hosting. No subscriptions. No data sent to third parties. Just your content, on your terms.
Built as a full-stack portfolio project showcasing video streaming, JWT authentication, REST API design, and a responsive React UI.
- 🎥 Stream videos directly in the browser using HTTP range requests for efficient, buffer-friendly playback
- 📂 Auto-scan library — point Zentro at a folder and it indexes your movies and series automatically
- 🔐 JWT Authentication — secure login so only you can access your content
- 📺 Polished video player — full controls, fullscreen, keyboard shortcuts, and subtitle support via Plyr
- 🌙 Dark-themed UI — clean, responsive interface built with React and Tailwind CSS
- 📊 Watch progress tracking — resume movies and episodes where you left off
- 📱 Works on any device — phones, tablets, smart TVs, laptops — anything with a browser
- 🌐 Remote access — watch from outside your home network via Cloudflare Tunnel (free)
- 🗄️ No external database — uses SQLite, a single file stored locally on disk
- ⚡ Fast metadata detection — uses FFmpeg to read video duration, resolution, and format
| Tool | Purpose |
|---|---|
| Node.js | JavaScript runtime — runs the server on your machine |
| Express.js | REST API framework — handles routes, streaming, and auth |
| better-sqlite3 | SQLite bindings — stores library metadata, users, watch progress |
| fluent-ffmpeg | Reads video metadata (duration, resolution, codec) |
| ffmpeg | System-level video processing (required by fluent-ffmpeg) |
| jsonwebtoken | Issues and verifies JWT tokens for authentication |
| bcrypt | Hashes passwords securely before storing |
| dotenv | Loads environment variables from .env |
| cors | Allows the React frontend to communicate with the Express API |
| multer | Handles file uploads through the web UI |
| nodemon | Auto-restarts the server during development on file changes |
| Tool | Purpose |
|---|---|
| React | UI component framework |
| React Router | Client-side routing (Home, Library, Player, Login pages) |
| Axios | HTTP client for API calls from the frontend |
| Tailwind CSS | Utility-first CSS for rapid, responsive styling |
| Plyr | Polished HTML5 video player with subtitle and keyboard support |
| Vite | Fast build tool and dev server for the React app |
| Tool | Purpose |
|---|---|
| SQLite | Lightweight, file-based database — no server setup needed |
| Cloudflare Tunnel | Exposes your local server to the internet for free |
| Git + GitHub | Version control and project hosting |
zentro/
├── server/ # Express backend
│ ├── routes/
│ │ ├── auth.js # Login, register, JWT
│ │ ├── library.js # Scan & list media files
│ │ ├── stream.js # Video streaming with range requests
│ │ └── progress.js # Watch progress tracking
│ ├── middleware/
│ │ └── auth.js # JWT verification middleware
│ ├── db/
│ │ ├── database.js # SQLite connection & setup
│ │ └── zentro.db # SQLite database file (auto-generated)
│ ├── utils/
│ │ ├── scanner.js # Library folder scanner using FFmpeg
│ │ └── metadata.js # Video metadata extraction
│ ├── .env # Environment variables (not committed)
│ └── index.js # Entry point
│
├── client/ # React frontend
│ ├── src/
│ │ ├── pages/
│ │ │ ├── Home.jsx # Dashboard / hero page
│ │ │ ├── Library.jsx # Browse movies & series
│ │ │ ├── Player.jsx # Video player page
│ │ │ └── Login.jsx # Login page
│ │ ├── components/
│ │ │ ├── Navbar.jsx
│ │ │ ├── MediaCard.jsx # Movie/episode card
│ │ │ └── ProgressBar.jsx # Watch progress indicator
│ │ ├── context/
│ │ │ └── AuthContext.jsx # Global auth state
│ │ ├── api/
│ │ │ └── axios.js # Axios instance with base URL & token
│ │ ├── App.jsx
│ │ └── main.jsx
│ ├── index.html
│ └── vite.config.js
│
├── .gitignore
├── LICENSE
└── README.md
Make sure you have the following installed:
Verify your installations:
node -v
npm -v
ffmpeg -version- Clone the repository
git clone https://github.com/your-username/zentro.git
cd zentro- Install backend dependencies
cd server
npm install- Install frontend dependencies
cd ../client
npm installCreate a .env file inside the server/ directory:
cd server
cp .env.example .envEdit .env with your values:
# Server
PORT=5000
# Path to your media folder (movies, series)
MEDIA_PATH=/path/to/your/media
# JWT
JWT_SECRET=your_super_secret_key_here
JWT_EXPIRES_IN=7d
# Admin credentials (used on first run to seed the database)
ADMIN_USERNAME=admin
ADMIN_PASSWORD=your_password_here
⚠️ Never commit your.envfile. It is already listed in.gitignore.
Start the backend server:
cd server
npm run devThe API will be running at http://localhost:5000
Start the React frontend (in a separate terminal):
cd client
npm run devThe app will be available at http://localhost:5173
Open your browser and navigate to http://localhost:5173 to use Zentro.
To access Zentro from your phone, tablet, or any device outside your home network, use Cloudflare Tunnel (free):
-
Install cloudflared
-
Run the tunnel pointing to your backend:
cloudflared tunnel --url http://localhost:5000-
Cloudflare will give you a public URL like
https://random-name.trycloudflare.com -
Update the
VITE_API_URLin your client.env:
VITE_API_URL=https://random-name.trycloudflare.com- Rebuild the client:
cd client
npm run buildNow you can stream your media from anywhere in the world.
All protected routes require a Bearer token in the Authorization header.
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
POST |
/api/auth/login |
Login and receive JWT token | ❌ |
GET |
/api/auth/me |
Get current user info | ✅ |
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
GET |
/api/library/movies |
List all movies | ✅ |
GET |
/api/library/series |
List all TV series | ✅ |
GET |
/api/library/scan |
Re-scan the media folder | ✅ |
GET |
/api/library/:id |
Get details for a single item | ✅ |
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
GET |
/api/stream/:id |
Stream a video file (supports range requests) | ✅ |
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
GET |
/api/progress/:id |
Get watch progress for a media item | ✅ |
POST |
/api/progress/:id |
Save watch progress | ✅ |
- Local video streaming with HTTP range requests
- JWT authentication
- Library auto-scanner
- Watch progress tracking
- Responsive dark UI
- Remote access via Cloudflare Tunnel
- TMDB API integration for automatic movie posters and descriptions
- Subtitle (.srt / .vtt) support
- Multi-user support with individual watch histories
- Season/episode browser for TV series
- Search and filter in the library
- Mobile PWA support (install as an app on your phone)
- Hardware transcoding for unsupported video formats
Contributions are welcome! Here's how to get started:
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature-name - Commit your changes:
git commit -m 'Add some feature' - Push to the branch:
git push origin feature/your-feature-name - Open a Pull Request
Please make sure your code is clean and follows the existing style before submitting.
This project is licensed under the MIT License — see the LICENSE file for details.
Built with ❤️ by your-username