Auto-Upload Files to Google Drive from Node.js
Prerequisites
- Completed Lesson 1 — Google Cloud project with Drive API enabled
- Completed Lesson 2 — OAuth authorization with saved token
- Google Drive folder IDs for each target directory
Getting Google Drive Folder IDs
Each Google Drive folder has a unique ID in its URL:
https://drive.google.com/drive/folders/1XpCzHK5SiKLFboKGYGJOBIfEb0PHWTvI
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This is the folder ID
Open each target folder in your browser and copy the ID from the URL.
The Upload Script
Create src/upload-to-gdrive.js:
const fs = require("fs");
const path = require("path");
const { getDriveClient } = require("./gdrive-auth");
const CLOUD_DIR = path.join(__dirname, "..", "output", "cloud");
// Map local directory names to Google Drive folder IDs
const FOLDER_MAP = {
"property-data": "YOUR_FOLDER_ID_1",
"property-images": "YOUR_FOLDER_ID_2",
"property-map": "YOUR_FOLDER_ID_3",
};
const CHECK_INTERVAL_MS = 60_000; // 1 minute
async function uploadFile(drive, localPath, folderId, fileName) {
const fileStream = fs.createReadStream(localPath);
const res = await drive.files.create({
requestBody: {
name: fileName,
parents: [folderId],
},
media: {
mimeType: "application/json",
body: fileStream,
},
fields: "id, name",
});
return res.data;
}
async function processDirectory(drive, dirName, folderId) {
const dirPath = path.join(CLOUD_DIR, dirName);
if (!fs.existsSync(dirPath)) {
return 0;
}
const files = fs
.readdirSync(dirPath)
.filter((f) => f.endsWith(".json") && !f.startsWith("."));
if (files.length === 0) {
return 0;
}
let uploaded = 0;
for (const fileName of files) {
const filePath = path.join(dirPath, fileName);
try {
const result = await uploadFile(drive, filePath, folderId, fileName);
fs.unlinkSync(filePath); // Delete local file after successful upload
console.log(` ✅ ${dirName}/${fileName} → Drive (id: ${result.id})`);
uploaded++;
} catch (err) {
console.error(` ❌ ${dirName}/${fileName}: ${err.message}`);
}
}
return uploaded;
}
async function runOnce(drive) {
let totalUploaded = 0;
for (const [dirName, folderId] of Object.entries(FOLDER_MAP)) {
const count = await processDirectory(drive, dirName, folderId);
totalUploaded += count;
}
return totalUploaded;
}
async function main() {
console.log("\n☁️ Google Drive uploader started");
console.log(" Watching: output/cloud/{property-data,property-images,property-map}");
console.log(` Interval: every ${CHECK_INTERVAL_MS / 1000}s\n`);
const drive = getDriveClient();
// Verify auth works
try {
await drive.about.get({ fields: "user" });
console.log(" ✅ Google Drive authentication successful\n");
} catch (err) {
console.error(`\n❌ Google Drive authentication failed: ${err.message}`);
console.error(" Run: npm run gdrive-auth\n");
process.exit(1);
}
// Initial run
const initial = await runOnce(drive);
if (initial > 0) {
console.log(`\n📤 Uploaded ${initial} file(s) on startup\n`);
} else {
console.log(" No files to upload right now\n");
}
// Continuous loop
setInterval(async () => {
try {
const timestamp = new Date().toLocaleTimeString();
const count = await runOnce(drive);
if (count > 0) {
console.log(`\n📤 [${timestamp}] Uploaded ${count} file(s)\n`);
}
} catch (err) {
console.error(`\n❌ [${new Date().toLocaleTimeString()}] Error: ${err.message}\n`);
}
}, CHECK_INTERVAL_MS);
}
main();
Replace YOUR_FOLDER_ID_1, YOUR_FOLDER_ID_2, YOUR_FOLDER_ID_3 with your actual Google Drive folder IDs.
Add npm Script
{
"scripts": {
"gdrive-auth": "node src/gdrive-authorize.js",
"upload-gdrive": "node src/upload-to-gdrive.js"
}
}
How It Works
- Startup: Authenticates with Google Drive using the saved OAuth token
- Initial scan: Checks all configured directories for
.jsonfiles - Upload: For each file found, uploads it to the corresponding Google Drive folder
- Cleanup: Deletes the local file after successful upload
- Loop: Repeats every 60 seconds (configurable via
CHECK_INTERVAL_MS)
Flow diagram
Local directories Google Drive
───────────────── ────────────
output/cloud/
├── property-data/
│ └── file_123.json ──────→ property-data/ (folder ID 1)
├── property-images/
│ └── img_456.json ──────→ property-images/ (folder ID 2)
└── property-map/
└── map_789.json ──────→ property-map/ (folder ID 3)
After upload, local files are deleted. The directories remain empty until new files appear.
Testing
Create a test file and run the uploader:
# Create a test file
echo '{"test": true}' > output/cloud/property-data/test_upload.json
# Run the uploader
npm run upload-gdrive
Expected output:
☁️ Google Drive uploader started
Watching: output/cloud/{property-data,property-images,property-map}
Interval: every 60s
✅ Google Drive authentication successful
✅ property-data/test_upload.json → Drive (id: 1O3p6Fb3M3JspkU5b_iJQgNB7TBICoTqL)
📤 Uploaded 1 file(s) on startup
Check your Google Drive folder — the file should be there.
Running in Production
To keep the uploader running in the background while you work:
# Option 1: Run in a separate terminal tab
npm run upload-gdrive
# Option 2: Run with nohup (persists after closing terminal)
nohup npm run upload-gdrive > upload.log 2>&1 &
# Check the log
tail -f upload.log
# Stop it
kill $(pgrep -f upload-to-gdrive)
Customization
Change the check interval
Edit CHECK_INTERVAL_MS in the script:
const CHECK_INTERVAL_MS = 30_000; // 30 seconds
const CHECK_INTERVAL_MS = 300_000; // 5 minutes
Add more directories
Add entries to FOLDER_MAP:
const FOLDER_MAP = {
"property-data": "FOLDER_ID_1",
"property-images": "FOLDER_ID_2",
"property-map": "FOLDER_ID_3",
"new-directory": "FOLDER_ID_4", // Add new mappings here
};
Upload non-JSON files
Change the filter in processDirectory:
// Upload all files (not just .json)
const files = fs
.readdirSync(dirPath)
.filter((f) => !f.startsWith("."));
And adjust the MIME type in uploadFile:
// Auto-detect MIME type
const mimeTypes = {
".json": "application/json",
".csv": "text/csv",
".png": "image/png",
".jpg": "image/jpeg",
};
const ext = path.extname(fileName);
const mimeType = mimeTypes[ext] || "application/octet-stream";
Token Expiry
The OAuth refresh token is long-lived but can expire if:
- You revoke access in Google Account Settings
- The token is unused for 6 months
- You change your Google password
If uploads start failing with authentication errors, re-run:
npm run gdrive-auth
Complete File Summary
| File | Purpose | Run |
|---|---|---|
credentials/gdrive-oauth.json | OAuth client credentials | Downloaded from GCP Console |
credentials/gdrive-token.json | Saved access + refresh tokens | Auto-created by gdrive-auth |
src/gdrive-auth.js | Shared authentication helper | Imported by other scripts |
src/gdrive-authorize.js | One-time browser authorization | npm run gdrive-auth (once) |
src/upload-to-gdrive.js | Continuous file uploader | npm run upload-gdrive |