VidFlow is a full-stack video-sharing platform built with React, Vite, Node.js, Express, MongoDB, JWT authentication, Cloudinary uploads, and Multer.
User -> Vercel React frontend -> Render Docker/Express API -> MongoDB Atlas / Cloudinary
The repository is split into:
Frontend/: Vite 5 and React 18. The production build isnpm run build, and Vite writes todist/.Backend/: Express 5 with the entry pointsrc/index.js. It starts withnpm startand listens onprocess.env.PORT || 8000.
Node.js 22 is used by both package manifests, .nvmrc, Docker, and GitHub Actions.
Copy the example files before running locally:
cp Backend/.env.example Backend/.env
cp Frontend/.env.example Frontend/.envOn Windows Command Prompt, use copy instead of cp.
| Variable | Purpose |
|---|---|
PORT |
HTTP port. Render supplies this automatically. |
NODE_ENV |
Use development locally and production on Render. |
MONGODB_URI |
Complete MongoDB/Atlas connection URI. The application selects the youtube database. |
CORS_ORIGIN |
Comma-separated allowed frontend origins, without path components. |
COOKIE_SAME_SITE |
Usually lax locally and none for the cross-site Vercel/Render deployment. |
ACCESS_TOKEN_SECRET |
Secret used to sign access tokens. |
ACCESS_TOKEN_EXPIRY |
Access-token lifetime, such as 1d. |
REFRESH_TOKEN_SECRET |
Separate secret used to sign refresh tokens. |
REFRESH_TOKEN_EXPIRY |
Refresh-token lifetime, such as 10d. |
CLOUDINARY_CLOUD_NAME |
Cloudinary cloud name. |
CLOUDINARY_API_KEY |
Cloudinary API key. |
CLOUDINARY_API_SECRET |
Cloudinary API secret. |
Generate independent, long random values for both JWT secrets. Never commit a populated .env file.
VITE_API_BASE_URL must contain the complete API prefix:
# Local direct connection
VITE_API_BASE_URL=http://localhost:8000/api/v1
# Vercel production
VITE_API_BASE_URL=https://YOUR-RENDER-SERVICE.onrender.com/api/v1If the variable is omitted locally, the frontend uses /api/v1, which the Vite development proxy forwards to http://localhost:8000. The localhost proxy is development-only and is not included in the production bundle.
Backend:
cd Backend
npm ci
npm run devFrontend, in a second terminal:
cd Frontend
npm ci
npm run devUseful validation commands:
cd Backend
npm test
cd ../Frontend
npm run build
npm run previewnpm test performs a syntax check of every backend JavaScript file. There is no automated application test suite yet.
Build the backend image from the repository root:
docker build -t vidflow-backend ./BackendRun it with secrets supplied at runtime:
docker run --name vidflow-api --env-file Backend/.env -p 8000:8000 vidflow-backendTest the running container:
curl http://localhost:8000/api/v1/health
curl http://localhost:8000/api/v1/videosThe second request also exercises MongoDB-backed API behavior. It can fail if the Atlas network allowlist or database credentials do not permit the container's connection.
Stop and remove the local container when finished:
docker stop vidflow-api
docker rm vidflow-apiThe image contains the application and production dependencies. A container is a running instance of that image. The Dockerfile describes how the image is built; .dockerignore keeps local dependencies, temporary uploads, archives, and secrets out of the build context. EXPOSE 8000 documents the intended container port but does not publish it—the -p 8000:8000 option does that. Runtime environment variables keep secrets out of immutable image layers and allow the same image to run with different configurations.
The flow is:
docker build -> execute Dockerfile layers -> create vidflow-backend image
docker run -> create a container -> inject environment -> npm start -> Express listens
Docker Compose is intentionally omitted because the application uses hosted MongoDB Atlas and Cloudinary services.
- Push this repository to GitHub and import it in the Vercel dashboard.
- Set Root Directory to
Frontend. - Select Vite as the framework preset.
- Use Build Command
npm run build. - Use Output Directory
dist. - Leave the install command at
npm installor set it tonpm ci. - Add
VITE_API_BASE_URL=https://YOUR-RENDER-SERVICE.onrender.com/api/v1for Production (and Preview if desired). - Deploy, then copy the assigned
https://YOUR-PROJECT.vercel.appURL from the deployment overview.
Frontend/vercel.json sends non-file routes to index.html, so refreshing React Router URLs such as /trending or /watch/:videoId does not return a Vercel 404. Changing a Vercel environment variable requires a new deployment because Vite embeds it during the build.
Deploy Render first so its URL is available for the Vercel build:
- In Render, choose New > Web Service and connect the GitHub repository.
- Choose the
mainbranch and set Root Directory toBackend. - Select Docker as the language/runtime.
- Set Dockerfile Path to
./Dockerfileand Docker Build Context Directory to.. - Do not enter a separate build command. Render builds the Dockerfile.
- Leave Docker Command empty so Render uses the Dockerfile's
CMD ["npm", "start"]. - Set Health Check Path to
/api/v1/health. - Add the environment variables below. Do not manually set
PORT; Render injects it. - Deploy and copy the assigned
https://YOUR-SERVICE.onrender.comURL from the service page.
Render environment variables:
NODE_ENV=production
MONGODB_URI=<complete Atlas connection URI>
CORS_ORIGIN=http://localhost:5173
COOKIE_SAME_SITE=none
ACCESS_TOKEN_SECRET=<long random secret>
ACCESS_TOKEN_EXPIRY=1d
REFRESH_TOKEN_SECRET=<different long random secret>
REFRESH_TOKEN_EXPIRY=10d
CLOUDINARY_CLOUD_NAME=<cloud name>
CLOUDINARY_API_KEY=<API key>
CLOUDINARY_API_SECRET=<API secret>Ensure MongoDB Atlas permits connections from Render and that the database user has only the permissions VidFlow needs.
After Render provides its URL:
- Set Vercel's
VITE_API_BASE_URLto the Render URL plus/api/v1, then deploy Vercel. - Copy the final Vercel origin, for example
https://vidflow.vercel.app. - Replace Render's temporary
CORS_ORIGINwith that exact origin and redeploy Render. - If local frontend access is also needed against the hosted API, use a comma-separated value such as
https://vidflow.vercel.app,http://localhost:5173. - Verify the health endpoint, sign-up/login, an authenticated request, and an upload from the deployed frontend.
The API allows credentials only for configured origins. Production cookies use Secure and default to SameSite=None. The frontend also sends the access token as a Bearer token, preserving the existing authentication design. For stronger cross-browser cookie reliability in a larger production deployment, place the frontend and API on related custom domains.
.github/workflows/ci.yml runs on every pull request and every push to main. It creates two independent jobs:
- Backend: restore the npm download cache, run
npm ci, syntax-check all backend JavaScript, and build the Docker image. - Frontend: restore the npm download cache, run
npm ci, and create the Vite production build.
CI means automatically integrating and validating changes in a shared repository. GitHub Actions was chosen because it is hosted alongside the GitHub code and makes the checks visible on commits and pull requests. When a pull request is opened or updated, both jobs run on clean Linux runners. If the frontend build or any other step fails, the workflow fails and should be required before merging.
npm ci installs the exact dependency versions in package-lock.json, removes any pre-existing install tree, and fails if the manifest and lockfile disagree. That makes it more deterministic than npm install for CI.
This workflow is CI, not a GitHub Actions CD pipeline. It does not deploy or receive production secrets. Vercel and Render independently watch the GitHub repository and deploy after their account-level integrations are configured. A larger system could add unit/integration tests, dependency and image scanning, immutable image publishing, protected environments, staged promotion, smoke tests, observability, and rollback automation.
https://YOUR-RENDER-SERVICE.onrender.com/api/v1/healthreturns HTTP 200.- The Vercel home page loads and direct navigation to a nested route works.
- Browser network requests target Render, not localhost.
- Render's
CORS_ORIGINexactly contains the Vercel origin without a trailing path. - Login and an authenticated API request work.
- A media upload reaches Cloudinary and leaves no permanent local upload file.
- GitHub Actions passes on the committed revision.
- No populated
.envfile or credential is tracked.
Created:
.nvmrc.github/workflows/ci.ymlBackend/.dockerignoreBackend/.env.exampleBackend/DockerfileBackend/scripts/check-syntax.jsFrontend/.env.exampleFrontend/vercel.json
Modified:
Backend/src/index.js: hosted port and interface binding.Backend/src/app.js: public health endpoint.Backend/src/db/index.js: safe Atlas URI handling and non-secret logging.- Both
package.jsonand lockfiles: Node 22 metadata and backend validation command. - Both application
.gitignorefiles: environment and archive protection. README.md: local, Docker, CI, deployment, and interview documentation.
Before the external services are created, the accurate statement is:
Containerized the backend with Docker, configured GitHub Actions CI, and prepared the React and Express applications for Vercel and Render deployment.
After Vercel and Render have both deployed the GitHub revision and the checklist above passes, it is truthful to write:
Containerized with Docker, configured GitHub Actions CI, and deployed on Vercel and Render.