9Drive is a storage gateway web app for connecting multiple Google Drive accounts into one virtual storage dashboard. Users can connect Google Drive accounts, track quota, upload files, organize files with virtual folders, preview files, and let the backend route uploads to the Drive account with enough free space.
- React + Vite frontend.
- Express + TypeScript backend.
- MySQL database with Prisma migrations.
- Bearer token authentication.
- Google OAuth login for connected Drive accounts.
- Global Google OAuth config stored encrypted in DB.
- Direct upload stream to Google Drive. Files are not stored on the server.
- Multi-account storage quota summary.
- Quota tracker page.
- Virtual folders.
- File preview, download, rename, move, and delete actions.
- Bottom-right upload progress panel.
Live preview: https://9drive.zenhosta.com
backend/ Express API, Prisma schema, Google Drive integration
frontend/ Vite React app- Node.js 20+
- npm
- MySQL running locally
- Google Cloud project
- Google OAuth Client ID and Client Secret
Default database used by this project:
host: localhost
port: 3306
database: 9drive
user: root
password: emptygit clone git@github.com:zenhosta/9drive.git
cd 9driveInstall backend dependencies:
cd backend
npm installInstall frontend dependencies:
cd ../frontend
npm installCreate database:
CREATE DATABASE 9drive;If using MySQL CLI:
mysql -u root -e "CREATE DATABASE IF NOT EXISTS 9drive;"Create backend/.env:
DATABASE_URL="mysql://root@localhost:3306/9drive"
APP_PORT=4000
FRONTEND_URL="http://localhost:5173"
JWT_ACCESS_SECRET="change-this-jwt-secret-at-least-32-chars"
TOKEN_ENCRYPTION_KEY="change-this-encryption-key-32bytes!"
ACCESS_TOKEN_TTL_SECONDS=900
REFRESH_TOKEN_TTL_DAYS=30
MAX_UPLOAD_BYTES=5368709120
# Used only by `npm run seed:google-config`.
# These values are encrypted and stored in DB as global Google OAuth config.
GOOGLE_CLIENT_ID=""
GOOGLE_CLIENT_SECRET=""
GOOGLE_REDIRECT_URI="http://localhost:4000/connected-accounts/google/callback"Important:
JWT_ACCESS_SECRETshould be long and random.TOKEN_ENCRYPTION_KEYshould be long and random.- Do not commit
backend/.env. - Google OAuth credentials are used by the seed script, then stored encrypted in the database.
Create or confirm frontend/.env:
VITE_API_URL=http://localhost:4000cd backend
npm run prisma:migrateIf Prisma client generation is blocked on Windows by a running Node process, stop running backend/frontend dev servers and run:
npx prisma generateGoogle setup is done in Google Cloud Console, not Google Search Console. Google Search Console is for website indexing/search ownership. OAuth and Drive API are managed in Google Cloud Console.
Open Google Cloud Console:
https://console.cloud.google.com/- Open Google Cloud Console.
- Click project selector in top bar.
- Create a new project or select an existing project.
- Remember the project name because OAuth client and Drive API must be in the same project.
- Go to:
APIs & Services -> Library- Search:
Google Drive API- Open
Google Drive API. - Click
Enable. - Wait a few minutes if Google says the API was enabled recently.
Direct URL pattern:
https://console.developers.google.com/apis/api/drive.googleapis.com/overview?project=YOUR_PROJECT_IDIf Google Drive API is disabled, you will see an error like:
Google Drive API has not been used in project ... before or it is disabled.- Go to:
APIs & Services -> OAuth consent screen- Choose app type:
External- Fill required fields:
App name
User support email
Developer contact email- Add scopes:
https://www.googleapis.com/auth/drive.file
https://www.googleapis.com/auth/userinfo.email
https://www.googleapis.com/auth/userinfo.profile- If publishing status is
Testing, add test users.
Add every Google account that will test the app:
OAuth consent screen -> Test users -> Add usersIf you do not add test users, Google may show:
Access blocked: app has not completed the Google verification process
Error 403: access_denied- Go to:
APIs & Services -> Credentials- Click:
Create Credentials -> OAuth client ID- Application type:
Web application- Add authorized JavaScript origin:
http://localhost:5173- Add authorized redirect URI:
http://localhost:4000/connected-accounts/google/callback- Click Create.
- Copy:
Client ID
Client SecretPut values into backend/.env:
GOOGLE_CLIENT_ID="your-client-id"
GOOGLE_CLIENT_SECRET="your-client-secret"
GOOGLE_REDIRECT_URI="http://localhost:4000/connected-accounts/google/callback"Then run:
cd backend
npm run seed:google-configThis stores the Google OAuth config as a global encrypted provider config in MySQL. Users only need to click Connect Drive in the frontend.
Start backend:
cd backend
npm run devBackend runs at:
http://localhost:4000Start frontend:
cd frontend
npm run devFrontend runs at:
http://localhost:5173This repository includes Docker files for running MySQL, backend, and frontend together.
Files:
docker-compose.yml
.env.docker.example
backend/Dockerfile
frontend/Dockerfile
frontend/nginx.confCopy the example env file:
cp .env.docker.example .envOn Windows PowerShell:
Copy-Item .env.docker.example .envEdit .env:
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=9drive
FRONTEND_URL=http://localhost:5173
VITE_API_URL=http://localhost:4000
JWT_ACCESS_SECRET=replace-with-long-random-secret
TOKEN_ENCRYPTION_KEY=replace-with-long-random-secret
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GOOGLE_REDIRECT_URI=http://localhost:4000/connected-accounts/google/callbackdocker compose up -d --buildServices:
frontend: http://localhost:5173
backend: http://localhost:4000
mysql: localhost:3306The backend container runs Prisma migrations automatically on startup:
npx prisma migrate deployAfter containers are running, seed the global Google OAuth config:
docker compose exec backend npm run seed:google-configThis stores GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and GOOGLE_REDIRECT_URI from Docker env into MySQL as encrypted global config.
docker compose logs -f backend
docker compose logs -f frontend
docker compose logs -f mysqldocker compose downRemove database volume too:
docker compose down -v- Replace localhost URLs with production domain.
- Update Google OAuth authorized JavaScript origin.
- Update Google OAuth redirect URI.
- Use strong
JWT_ACCESS_SECRETandTOKEN_ENCRYPTION_KEY. - Do not expose MySQL port publicly in production.
- Put frontend/backend behind HTTPS reverse proxy.
- Rebuild frontend when
VITE_API_URLchanges because Vite embeds env at build time.
- Open frontend:
http://localhost:5173- Register a user.
- Open
Settings. - Click
Connect Drive. - Google OAuth popup opens.
- Approve access.
- Popup closes.
- Connected Google account appears.
- Open
Quota Tracker. - Confirm quota appears.
- Open
All Files. - Create a virtual folder.
- Upload a file.
- Watch bottom-right upload progress.
- Right-click file row for actions:
View
Download
Rename
Move to Folder
DeleteAuth:
POST /auth/register
POST /auth/login
POST /auth/refresh
POST /auth/logout
GET /auth/meGoogle accounts:
GET /connected-accounts/google/connect-url
GET /connected-accounts/google/callback
GET /connected-accounts
POST /connected-accounts/:id/sync-quota
DELETE /connected-accounts/:idStorage:
GET /storage/summaryFolders:
GET /folders
GET /folders/recent?limit=4
POST /folders
DELETE /folders/:idFiles:
GET /files
GET /files/:id
PATCH /files/:id
GET /files/:id/view-url
GET /files/:id/download
DELETE /files/:idUploads:
POST /uploadsUpload is multipart/form-data. Metadata fields should be appended before the file:
sizeBytes
fileName
mimeType
folderId optional
file- Backend never stores uploaded files on disk.
- Uploads are streamed through the backend to Google Drive.
- Google tokens are encrypted in MySQL.
- Refresh tokens for app sessions are hashed in MySQL.
backend/.envis ignored by git.- Do not expose
TOKEN_ENCRYPTION_KEYorJWT_ACCESS_SECRET.
- Replace localhost redirect URIs with production URLs.
- Add production domain to Google OAuth authorized origins.
- Set OAuth consent screen to production when ready.
- Google may require verification for public apps.
- Use strong secrets.
- Put the backend behind HTTPS.
- Consider secure cookies or stronger token storage for production.
Backend:
cd backend
npm run buildFrontend:
cd frontend
npm run build