Video Streaming App
Important Links
Introduction
In this article, I’ll show you exactly how I designed and built a scalable video streaming platform using microservices!
This isn’t just about the code—it’s about the thought process behind designing a scalable, and efficient system.
Key Points Covered: How I designed the system architecture? Why I chose microservices over monolith? How authentication, video upload, comment, search, and streaming work together? And how i used docker to bring everything together?
Why Microservices?
For a video streaming platform, I needed a system that was:
Scalable – Handles a growing number of users Independent – Each service runs separately Fault-Tolerant – If one service fails, the others still work
So Instead of building a single large app, I broke it down into five powerful microservices:
- User Authentication Service – Manages user login, JWT tokens
- Video Upload Service – Handles video storage
- Video Streaming Service – Streams videos to users
- Search Service – Enables real-time search
- Comment Service – Manages user comments
And now let me tell you how I found a way for them to communicate with each other
Basic app
I had a basic app up and running that allowed users to upload and stream videos.
How It Worked?
Uploading:
Users uploaded videos via the React frontend using a simple upload form. The selected video was sent to the backend and upload-service saves them in /app/videos.
Link: Upload Service
Streaming:
A user clicks on a video in the frontend. The frontend sends a request to the video-service for the file. The video-service reads the file and streams it back to the frontend. Everything was functional, but there was one major issue—I didn’t want random users uploading inappropriate or unauthorized content to my platform.
That’s when I decided to upgrade the app by implementing JWT authentication to ensure only authenticated users could upload videos.
Link: Stream Service
Service Communication & Discovery
With multiple services running, I needed a way for them to communicate efficiently without hardcoding their locations. That’s where I used Service Registry.
- Each microservice registers itself here
- Other services query it to find their dependencies
- This avoids hardcoded IPs and allows dynamic scaling
For example, when the frontend calls video-upload-service, it doesn’t need to know the exact IP—it just asks the Service Registry.
Think of it like this: Instead of memorizing your friend’s address, you just check Google Maps. That’s exactly what the Service Registry does!
This makes the system flexible!
Link: Service Registry
Authentication & Authorization
A video uploading platform needs users to log in before uploading or commenting.
When a user logs in via the frontend, the Auth Service performs the following steps:
Validates User Credentials:
Checks if the user exists in the database. Verifies the password using bcrypt.
Generates Secure Tokens:
Access Token (short-lived, stored in an HTTP-only cookie) → Used for authenticating API requests. Refresh Token (long-lived, stored in MongoDB) → Used to generate a new access token when the current one expires.
Persistent Authentication:
Instead of storing the JWT in local storage (which is insecure and does not persist authentication properly), I decided to store the Access Token in a secure HTTP-only cookie. This ensures the token is automatically sent with every request, allowing seamless authentication.
Handling Expired Tokens:
When the Access Token expires, the frontend does not ask the user to log in again. Instead, the Refresh Token (stored securely in MongoDB) is used to generate a new Access Token, ensuring continuous authentication.
User Logout:
The Refresh Token is deleted from MongoDB to prevent further use. Cookies are cleared, fully logging out the user and ensuring no session remains.
Link: Authentication Service
Uploading Mechanism
As I kept refining the app, a new problem emerged:
- How do I track which user uploaded which video?
- How do I ensure that only authenticated users can upload?
- What happens if someone uploads the same video twice with the same name? (Spoiler: It replaced the old one!)
The solution? Unique filenames + metadata storage in MongoDB!
How I Solved It Multer – Handles file uploads. Custom Storage Logic – Generates unique filenames. MongoDB – Stores essential video details:
Generating Unique Filenames To prevent overwrites, I used Multer for file uploads and added a custom storage function. This function generates unique filenames based on a timestamp + random string.
const storage = multer.diskStorage({
destination: (req, file, cb) => {
if (!fs.existsSync(VIDEO_STORAGE_PATH)) fs.mkdirSync(VIDEO_STORAGE_PATH, { recursive: true });
cb(null, VIDEO_STORAGE_PATH);
},
filename: (req, file, cb) => {
const ext = path.extname(file.originalname);
const timestamp = Date.now();
const randomString = Math.floor(Math.random() * 10000);
const newFilename = `${timestamp}_${randomString}${ext}`;
console.log("📂 Saving file as:", newFilename);
cb(null, newFilename);
},
});
Now, every uploaded video gets a unique filename—no more accidental overwrites!
Storing Video Metadata in MongoDB:
Now that filenames are unique, I needed a way to link videos to users. MongoDB stores video details along with the user ID & username:
How the /upload API Works Here’s the step-by-step flow when a user uploads a video:
- The frontend sends a video file + authentication token.
- The authenticateUser middleware checks if the user is logged in.
- Multer saves the video with a unique filename.
- MongoDB stores the video metadata (title, filename, uploader, etc.).
- The backend responds with a success message + video details.
This ensures every video is securely stored, properly tracked, and linked to a user!
Video Streaming Mechanism
Here’s how the streaming works:
User clicks a video on the frontend Frontend requests video-service for the file video-service reads the file & streams it
Fetching Videos The /videos API Endpoint:
Fetches all video metadata stored in MongoDB. Supports sorting by newest or oldest uploads. Returns a list of videos for the frontend to display. This is how users see a list of all available videos on the platform!
Serving the Videos The frontend sends a request to /videos/:filename. The video-service reads the requested file from storage. The /videos/:filename Route:
Serves uploaded video files as static content. Users can directly access a video URL to stream it. This makes it easy to deliver video content to users seamlessly!
Once a video is uploaded, users should be able to watch it instantly.
Implementing Search
We don’t want to scroll endlessly to find a video. So how about we build a search service that filters videos instantly/ [show search on website]
That’s exactly why I built video-search-service. It will help users to search for videos, fetch videos by username, and retrieve video details, all in real time.
How the Video Search Service Works?
The service connects to the MongoDB database, ensuring all video metadata is accessible for searching.
Search API - /search Users can search for videos based on a query. And query would be any word or a sentence they are typing
How it works: The frontend sends a search request (/search?query=keyword). The backend scans MongoDB for videos with matching titles or descriptions. The service responds with a list of matching videos.
Now users can find videos instantly instead of scrolling forever!
Get Videos by Username - /user-videos So i also needed a functionality which showed videos of the users beside the currently playing video. That is when I created another endpoint /user-videos [show code] This API retrieves all videos uploaded by a specific user.
How it works: The frontend sends a request (/user-videos?username=JohnDoe). The service fetches all videos uploaded by that user. The response includes video titles, upload dates, and other details.
Link: Search Service
Comment Service
Videos are more engaging when users can share their thoughts! So, I created the comment-service to allow users to comment on videos and view comments for each video in real time. [Show screen recording of commenting on a video]
How Users Post Comments To add a comment, the frontend sends a POST request to the comment service’s API.
Endpoint: POST /comments
Request Body:
{
"videoName": "example_video.mp4",
"username": "JohnDoe",
"comment": "Great video!"
}
How it Works: The user submits a comment via the frontend. The comment-service receives the request and validates it. The comment is saved in MongoDB with:
Fetching Comments for a Video To display comments under a video, the frontend requests all comments for that video.
Endpoint: GET /comments/:videoName
How it Works: The frontend sends a request with the video name. The comment-service fetches all comments from MongoDB sorted by timestamp. The response returns a list of comments.
Example Response:
{
"username": "JohnDoe",
"comment": "Great video!",
"timestamp": "2025-03-01T12:34:56Z"
},
Now, users can see all the discussions happening around a video!
Link: Comment Service
Deploying Everything with Docker
Finally, I needed a way to run everything together. Instead of setting up each service manually which was legit painfull, I used Docker & Docker Compose. [screen recording of manually starting each services]
What I did is? In root of every service i created a dockerfile so that Each service runs in its own container. [show conatiner in docker desktop]
And then this Containers talk through a shared network.
MongoDB & video storage persist across restarts.
And now instead of manually starting every service, I just had to enter this one command and my application would run as one system. And the command was: docker-compose up --build
With this one command, the entire platform started running!
Services were now isolated but yet they started communicating efficiently.
So that’s pretty much all for this article. I hope it helped you. And with this I’ll see you in the next one. Till then bye bye.