Every piece of media in VideoHub — video files, auto-generated and custom thumbnails, user avatars, and channel cover images — is stored in Cloudinary. The backend never serves binary files directly; it stores Cloudinary URLs in MongoDB and returns those URLs to clients. Local disk is used only as a transient staging area during the upload pipeline.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/barcode8/VideoHub/llms.txt
Use this file to discover all available pages before exploring further.
Upload Pipeline
The journey from an HTTP request to a persisted Cloudinary URL follows the same pattern for every media type:Client sends multipart/form-data
The client encodes the file(s) in a
multipart/form-data request and POSTs it to the appropriate endpoint. For videos the fields are videoFile and optionally thumbnail; for profile images the field is avatar or coverImage.Multer saves the file to disk
The Route handlers apply the middleware using
upload middleware (configured in multer.middleware.js) intercepts the request before it reaches the controller. It uses diskStorage to write the file to Backend/public/temp/ with a collision-resistant filename composed of the original field name, the current timestamp, and a random integer.upload.fields() for multi-file uploads or upload.single() for single-file uploads.Controller reads the local path
After Multer runs, the controller reads the temporary file path from
req.files (multi-field) or req.file (single-field):uploadOnCloudinary streams the file
uploadOnCloudinary(localFilePath, isVideo) (defined in utils/cloudinary.js) opens the local file and streams it to Cloudinary using uploader.upload_large. For video uploads it sets resource_type: "video" and applies a 1920×1080 crop limit transformation with eager_async: true so that transcoding happens asynchronously on Cloudinary’s side.Video Upload Flow
Uploading a video is a deliberate two-step process that decouples ID reservation from the potentially long-running upload and Cloudinary processing job.Reserve a Video ID — POST /api/v1/videos/init
The client calls this endpoint first. No file is sent. The controller generates a valid MongoDB Having a stable
ObjectId in memory — nothing is written to the database — and returns it as videoId:videoId before the upload starts lets the client build a shareable URL, track upload state, or resume a failed upload without creating orphaned DB records.Upload the file — POST /api/v1/videos/:videoId
The client sends a The controller (Once
multipart/form-data request to the ID reserved in step 1, including the video file, an optional thumbnail, title, and description:publishVideoDraft) immediately creates a MongoDB record with isPublished: false and sets videoFile: "processing" and thumbnail: "processing" as placeholder values, then fires processVideoBackground asynchronously — the HTTP response is returned to the client before Cloudinary processing is complete:processVideoBackground finishes uploading to Cloudinary it calls Video.findByIdAndUpdate to set the real videoFile URL, thumbnail URL, duration, and flips isPublished to true, making the video visible in the feed.Auto-Thumbnail Generation
If the client does not supply a thumbnail in the upload request, VideoHub automatically derives one from the video itself using Cloudinary’sso_auto transformation. The background function constructs a thumbnail URL by inserting so_auto into the video’s Cloudinary URL and converting the file extension to .jpg:
so_auto instructs Cloudinary to select the most visually interesting frame as the poster image. No additional API call is made — the thumbnail URL is resolved on-the-fly by Cloudinary’s image delivery pipeline.
Video Constraints
The background upload function enforces a maximum video duration of 1 hour (3 600 seconds). If the uploaded video exceeds this limit, the pipeline:- Destroys the video asset from Cloudinary (
resource_type: "video") - Deletes the MongoDB document created for the draft
- Removes any local thumbnail temp file
Cloudinary Configuration
Three environment variables must be present before the server starts. They are read inutils/cloudinary.js via dotenv:
| Variable | Description |
|---|---|
CLOUDINARY_CLOUD_NAME | Your Cloudinary account’s cloud name |
CLOUDINARY_API_KEY | Public API key from the Cloudinary dashboard |
CLOUDINARY_SECRET_KEY | Secret API key — treat this like a password |
Image Uploads (Avatar and Cover Image)
Avatar and cover image updates follow a simpler, synchronous path. The route usesupload.single() to accept one file, and the controller:
- Uploads the new image to Cloudinary via
uploadOnCloudinary(localFilePath) - Extracts the
public_idfrom the old Cloudinary URL stored in the user document - Calls
cloudinary.uploader.destroy(publicId)to delete the old file - Saves the new URL to the
Userdocument in MongoDB
The
Backend/public/temp/ directory is gitignored and only a .gitkeep file is committed to preserve the directory in version control. In containerised or serverless deployments, ensure the process has write permissions to this path before the server starts. The index.js entry point handles this automatically — it checks for the directory on boot and creates it (with recursive: true) if it is missing, so a fresh container will never fail due to a missing temp directory.