SemScore Logo
New App

SemScore App is Live on Google Play!

How to Upload Files to Google Drive API from Node.js (Without Google Workspace or Enterprise)

O
OD2 Team
7 min read (1309 words)21 readsOffline Snapshot
How to Upload Files to Google Drive API from Node.js (Without Google Workspace or Enterprise)

Learn how to stream automated database backups and files directly to personal Google Drive storage using Node.js, OAuth 2.0 user delegation, and error webhooks.

Introduction

Automating daily file backups to cloud storage is a standard task for modern backend applications. When choosing a storage provider, Google Drive is often the top choice due to its generous 15 GB free tier. However, developers attempting to integrate Google Drive API v3 using a Google Cloud Service Account quickly hit a brick wall:

403 Forbidden: "Service Accounts do not have storage quota. Leverage shared drives or use OAuth delegation instead."

This error occurs because Google attributes file storage to the account that uploads the file. Because Google Cloud Service Accounts carry a 0 MB personal storage quota, Google Drive API rejects direct file uploads unless the service account writes to a Google Workspace Shared Drive, which requires a paid enterprise account.

⚡ OD2 Free Developer Tool

Testing Email & SMTP Integrations?

Launch a private SMTP sandbox to capture, inspect, and debug mock emails without sending to real users.

Launch SMTP Sandbox →

If you are building a project using a free personal @gmail.com account, the solution is OAuth 2.0 User Delegation. By acquiring a persistent refresh token, your Node.js server acts on behalf of your personal account, uploading files directly into your 15 GB quota without requiring manual browser sign-ins.

In this guide, we will step through configuring Google Cloud OAuth 2.0, acquiring a long-lived refresh token, writing a zero-dependency Node.js multipart upload service, and configuring automated alerts via Discord webhooks.

Technical Prerequisites

Before diving into code, ensure you have the following environment set up:

  • Node.js Environment: Node.js v18.0.0 or higher (supporting native fetch and crypto modules).
  • Google Account: A standard @gmail.com personal account.
  • Google Cloud Console Access: Permission to create and manage projects in GCP.

Step 1: Create a Google Cloud Project & Configure OAuth Credentials

First, set up a dedicated project in Google Cloud Console to handle API authentication.

  1. Navigate to the Google Cloud Console and create a new project named Node-GDrive-Backup.
  2. Enable the Google Drive API:
    • Go to APIs & Services | Library.
    • Search for Google Drive API and click Enable.
  3. Configure the OAuth Consent Screen:
    • Navigate to APIs & Services | OAuth consent screen.
    • Choose External user type and click Create.
    • Enter your Application Name and support email ([email protected]).
    • Add the required scope: https://www.googleapis.com/auth/drive.
    • Important Publishing Step: Click Publish App to move your consent screen from Testing mode to Production. If left in Testing status, Google will automatically invalidate your refresh tokens after 7 days!
  4. Generate OAuth 2.0 Client Credentials:
    • Go to APIs & Services | Credentials and select Create Credentials | OAuth client ID.
    • Set Application Type to Web application.
    • Scroll to Authorized redirect URIs, click + ADD URI, and enter:https://developers.google.com/oauthplayground
    • Click Create and safely store your Client ID and Client Secret.

Step 2: Generate a Persistent OAuth 2.0 Refresh Token

To allow your server to authenticate in the background without user intervention, generate a persistent Refresh Token using Google OAuth Playground.

  1. Open Google OAuth 2.0 Playground.
  2. Click the ⚙️ Settings icon in the upper-right corner.
  3. Check the option “Use your own OAuth credentials”.
  4. Paste your Client ID and Client Secret generated in Step 1.
  5. In the left scope menu under Drive API v3, select https://www.googleapis.com/auth/drive.
  6. Click Authorize APIs and complete the consent screen login.
  7. Click Exchange authorization code for tokens.
  8. Copy the returned refresh_token string.

Step 3: Configure Environment Variables

Create or update your .env or .env.local configuration file in your project root:

# Target Google Drive Folder ID (Extracted from your folder URL in Drive)
DRIVE_BACKUP_FOLDER_ID="1your_google_drive_folder_id_here"

# Personal Google Account OAuth Credentials
OAUTH_CLIENT_ID="123456789000-your_client_id.apps.googleusercontent.com"
OAUTH_CLIENT_SECRET="GOCSPX-your_client_secret_here"
OAUTH_REFRESH_TOKEN="1//0your_oauth_refresh_token_here"

Developer Note: To locate your DRIVE_BACKUP_FOLDER_ID, open your target folder in Google Drive. The folder ID is the alphanumeric string following /folders/ in the URL bar.

Step 4: Implement the Google Drive Upload Engine (driveUploader.js)

Below is the production-ready implementation in Node.js. It exchanges the refresh token for a short-lived 1-hour access token, constructs an RFC-compliant multipart payload, and posts the file to Google Drive API v3.

Create lib/services/driveUploader.js:

import crypto from 'crypto';
import fs from 'fs';
import path from 'path';

/**
 * Exchanges the OAuth 2.0 Refresh Token for a fresh Access Token
 */
async function getAccessToken() {
  const clientId = process.env.OAUTH_CLIENT_ID;
  const clientSecret = process.env.OAUTH_CLIENT_SECRET;
  const refreshToken = process.env.OAUTH_REFRESH_TOKEN;

  if (!refreshToken || !clientId || !clientSecret) {
    throw new Error('OAuth configuration parameters missing from environment variables.');
  }

  try {
    const response = await fetch('https://oauth2.googleapis.com/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        client_id: clientId,
        client_secret: clientSecret,
        refresh_token: refreshToken,
        grant_type: 'refresh_token'
      })
    });

    if (response.ok) {
      const data = await response.json();
      return data.access_token;
    }

    const errorDetails = await response.text();
    throw new Error(`Failed to refresh OAuth token: ${errorDetails}`);
  } catch (err) {
    console.error('[OAuth Auth Error] Token exchange failure:', err.message);
    throw err;
  }
}

/**
 * Uploads file content directly to the target Google Drive folder
 * @param {string} fileName - Destination filename (e.g. "backup_2026-07-24.csv")
 * @param {string|Buffer} contentStringOrBuffer - Raw string or buffer content
 * @param {string} mimeType - File MIME type (e.g. "text/csv")
 * @returns {Promise<string>} The uploaded file ID in Google Drive
 */
export async function uploadToDrive(fileName, contentStringOrBuffer, mimeType) {
  const accessToken = await getAccessToken();
  const folderId = process.env.DRIVE_BACKUP_FOLDER_ID;

  const boundary = '-------multipart-boundary-987654';
  const metadata = {
    name: fileName,
    parents: folderId ? [folderId] : []
  };

  const bodyParts = [
    `--${boundary}\r\n`,
    `Content-Type: application/json; charset=UTF-8\r\n\r\n`,
    `${JSON.stringify(metadata)}\r\n`,
    `--${boundary}\r\n`,
    `Content-Type: ${mimeType}\r\n\r\n`,
    contentStringOrBuffer,
    `\r\n--${boundary}--`
  ];

  const multipartBody = Buffer.concat(
    bodyParts.map(part => (Buffer.isBuffer(part) ? part : Buffer.from(part)))
  );

  const uploadUrl = 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&supportsAllDrives=true';
  const response = await fetch(uploadUrl, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': `multipart/related; boundary=${boundary}`,
      'Content-Length': String(multipartBody.length)
    },
    body: multipartBody
  });

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(`Google Drive API Upload Failed: ${errorText}`);
  }

  const data = await response.json();
  return data.id;
}

Step 5: Wrap Execution with Automated Failure Monitoring

To ensure complete operational visibility, wrap the upload routine with instant Discord webhooks that trigger if authentication or upload failures occur.

Create lib/services/backupService.js:

import { uploadToDrive } from './driveUploader.js';

export async function executeScheduledBackup() {
  const timeStamp = new Date().toISOString().split('T')[0];
  const fileName = `db_audit_${timeStamp}.csv`;
  const dummyPayload = "id,action,user,timestamp\n101,LOGIN,user_admin,2026-07-24";

  console.log(`[Backup Service] Initiating file upload for ${fileName}...`);

  try {
    const fileId = await uploadToDrive(fileName, dummyPayload, 'text/csv');
    console.log(`✅ [Backup Service] Upload completed successfully. File ID: ${fileId}`);
    return { success: true, fileId };
  } catch (err) {
    console.error(`❌ [Backup Service] Backup failed: ${err.message}`);

    // Dispatch webhook alert to Discord
    await notifyDiscordFailure(fileName, err.message);

    throw err;
  }
}

async function notifyDiscordFailure(fileName, errorMsg) {
  const webhookUrl = process.env.DISCORD_WEBHOOK_URL;
  if (!webhookUrl) return;

  try {
    await fetch(webhookUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        embeds: [{
          title: '🚨 Google Drive Backup Alert',
          description: `**Target File:** \`${fileName}\`\n**Error:** ${errorMsg}\n**Timestamp:** ${new Date().toLocaleString()}`,
          color: 14177041
        }]
      })
    });
  } catch (e) {
    console.error('[Discord Webhook Failure]', e.message);
  }
}

FAQ & Common Troubleshooting

1. Error: Error 400: redirect_uri_mismatch

  • Root Causehttps://developers.google.com/oauthplayground is missing from Authorized Redirect URIs in GCP.
  • Resolution: Open Google Cloud Console | APIs & Services | Credentials, edit your OAuth Client ID, add https://developers.google.com/oauthplayground under Authorized redirect URIs, and click Save.

2. Error: 403 storageQuotaExceeded

  • Root Cause: The application is attempting to upload using a Service Account rather than User Delegation.
  • Resolution: Ensure your getAccessToken() function uses your personal OAuth Refresh Token rather than a Service Account JWT.

3. Error: invalid_grant (Token Expired or Revoked)

  • Root Cause: The refresh token was invalidated because the OAuth consent screen was in Testing status (which auto-expires tokens in 7 days), or the account password was changed.
  • Resolution: In Google Cloud Console, publish your consent screen to Production. Then re-run Step 2 in OAuth Playground to generate a new refresh token.

4. High Latency or Socket Timeouts During Large Uploads

  • Root Cause: Standard multipart uploads load the entire file payload into memory.
  • Resolution: For files exceeding 5 MB, switch from uploadType=multipart to uploadType=resumable to stream chunked buffers using headers X-Upload-Content-Type and X-Upload-Content-Length.

Summary

Using OAuth 2.0 User Delegation allows software engineers to stream automated data backups directly to personal Google Drive storage without requiring paid Google Workspace tiers. Combining native Node.js HTTP methods with automated error webhooks gives you a reliable, zero-dependency backup engine.

Developer Publishing Portal

Have an Engineering Tutorial or Tool to Share?

Publish your software engineering guides, API walkthroughs, and technical insights on the OD2 Blog. Review our publishing conditions, formatting standards, and guidelines.

Loading Sponsor...