google-workspace-connector
v1.0.0
Published
Drop-in Gmail, Google Calendar, and Google Drive integration for your intranet platform. Plain JS, no framework required.
Maintainers
Readme
google-workspace-connector
A drop-in Google Workspace integration for your intranet platform. Connects Gmail, Google Calendar, and Google Drive to your app in plain JavaScript — no framework required.
What It Does
Lets your intranet platform read and send Gmail, manage Calendar events, and interact with Google Drive — all through your own backend. Every action reflects in the user's real Google account.
What You Get
| File | Purpose |
|---|---|
| google-workspace-client.js | Drop into your frontend (browser). Plain JS, no framework needed. |
| backends/node/ | Node.js + Express backend |
| backends/python/ | Python + Django backend |
How It Works
Your frontend never talks to Google directly. Everything goes through your own backend server, which holds the user's OAuth tokens and makes authenticated calls to Google's API on their behalf.
Your Browser
└── google-workspace-client.js
│ HTTP calls
▼
Your Backend Server
└── Node.js or Django
│ Google API calls (with OAuth token)
▼
Google's Servers
└── Gmail / Calendar / DrivePrerequisites
Before you start, you need:
A Google Cloud Project with the following APIs enabled:
- Gmail API
- Google Calendar API
- Google Drive API
An OAuth 2.0 Client ID (Web application type)
Your callback URL added to the authorized redirect URIs
Setting Up Google Cloud
- Go to console.cloud.google.com
- Create a new project (or use an existing one)
- Go to APIs & Services → Library and enable:
- Gmail API
- Google Calendar API
- Google Drive API
- Go to APIs & Services → Credentials
- Click Create Credentials → OAuth 2.0 Client ID
- Application type: Web application
- Authorized redirect URIs:
http://localhost:3000/workspace/auth/callback(development)https://yourapp.com/workspace/auth/callback(production)
- Copy your Client ID and Client Secret
Project Structure
google-workspace-connector/
│
├── google-workspace-client.js ← frontend file (works with all backends)
│
├── backends/
│ ├── node/ ← Node.js + Express
│ │ ├── google-workspace-backend.js
│ │ └── src/
│ │ ├── core/
│ │ │ ├── oauth.js ← Google OAuth2 flow
│ │ │ ├── google-client.js ← authenticated HTTP client
│ │ │ └── mime.js ← email MIME builder/parser
│ │ ├── gmail/
│ │ │ └── index.js ← Gmail logic
│ │ ├── calendar/
│ │ │ └── index.js ← Calendar logic
│ │ ├── drive/
│ │ │ └── index.js ← Drive logic
│ │ └── server/
│ │ └── router.js ← all Express routes
│ │
│ └── python/ ← Python + Django
│ ├── requirements.txt
│ ├── SETUP.md
│ └── workspace/
│ ├── core/
│ │ ├── oauth.py ← Google OAuth2 flow
│ │ ├── google_client.py ← authenticated HTTP client
│ │ └── mime.py ← email MIME builder/parser
│ ├── gmail/
│ │ └── service.py ← Gmail logic
│ ├── calendar/
│ │ └── service.py ← Calendar logic
│ ├── drive/
│ │ └── service.py ← Drive logic
│ ├── views.py ← all Django views
│ └── urls.py ← URL patternsBackend Setup
Node.js
1. Install the dependency
npm install expressRequires Node.js 18 or higher (for native fetch support).
2. Create your server file
// server.js
const express = require('express');
const workspace = require('./backends/node/google-workspace-backend');
const app = express();
app.use(express.json());
// Token store — swap this with your database in production
const tokenStore = {
_store: {},
get: async (userId) => tokenStore._store[userId] || null,
set: async (userId, tokens) => { tokenStore._store[userId] = tokens; },
delete: async (userId) => { delete tokenStore._store[userId]; },
};
workspace.init(app, {
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
redirectUri: 'http://localhost:3000/workspace/auth/callback',
services: ['gmail', 'calendar', 'drive'],
mountPath: '/workspace',
tokenStore,
getUserId: (req) => req.session?.userId,
});
app.listen(3000, () => console.log('Running on http://localhost:3000'));3. Add your credentials to a .env file
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secret4. Run it
node server.jsPython / Django
1. Install dependencies
pip install -r backends/python/requirements.txt2. Copy the workspace folder into your Django project
your_django_project/
├── manage.py
├── your_project/
│ ├── settings.py
│ └── urls.py
└── workspace/ ← copy this folder from backends/python/3. Add credentials to settings.py
GOOGLE_CLIENT_ID = 'your-client-id'
GOOGLE_CLIENT_SECRET = 'your-client-secret'
GOOGLE_REDIRECT_URI = 'http://localhost:8000/workspace/auth/callback'
GOOGLE_SERVICES = ['gmail', 'calendar', 'drive']4. Mount the routes in urls.py
from workspace.urls import workspace_urlpatterns
token_store = ... # your token store adapter
urlpatterns += [
path('workspace/', include(workspace_urlpatterns(token_store))),
]5. Run it
python manage.py runserverSee
backends/python/SETUP.mdfor a full database token store example.
Token Store
Both backends need a token store — an object that saves and retrieves OAuth tokens per user. You provide your own so it works with any database.
It only needs three methods:
// Node.js — MongoDB example
const tokenStore = {
get: async (userId) => db.collection('tokens').findOne({ userId }),
set: async (userId, tokens) => db.collection('tokens').updateOne(
{ userId }, { $set: { tokens } }, { upsert: true }
),
delete: async (userId) => db.collection('tokens').deleteOne({ userId }),
};# Django — database example
class DatabaseTokenStore:
def get(self, user_id):
record = GoogleToken.objects.filter(user_id=user_id).first()
return record.tokens if record else None
def set(self, user_id, tokens):
GoogleToken.objects.update_or_create(
user_id=user_id, defaults={'tokens': tokens}
)
def delete(self, user_id):
GoogleToken.objects.filter(user_id=user_id).delete()Frontend Setup
1. Serve the client file from your backend
Node.js:
app.use('/js', express.static(__dirname));Django:
# In settings.py, add the file to your STATICFILES_DIRS
# then reference it as a static file in your template2. Include it in your HTML
<script src="/js/google-workspace-client.js"></script>3. Create a client instance
const workspace = new WorkspaceClient({ apiBase: '/workspace' });Usage
Authentication
// Check if the current user is connected
const status = await workspace.auth.status();
// { connected: true, email: '[email protected]' }
// Connect — redirects to Google consent screen
workspace.auth.connect();
// Disconnect
await workspace.auth.disconnect();Gmail
// List inbox
const { messages } = await workspace.gmail.messages.list('INBOX');
// Other folders
await workspace.gmail.messages.list('SENT');
await workspace.gmail.messages.list('DRAFTS');
await workspace.gmail.messages.list('TRASH');
// Paginate
await workspace.gmail.messages.list('INBOX', { maxResults: 50, pageToken });
// Read an email
const email = await workspace.gmail.messages.get(messageId);
// { id, subject, from, to, date, body, htmlBody, attachments[] }
// Get a full conversation thread
const thread = await workspace.gmail.messages.getThread(threadId);
// Send an email
await workspace.gmail.send({
to: '[email protected]',
subject: 'Hello',
body: 'Plain text body',
htmlBody: '<p>Or an <strong>HTML</strong> body</p>',
cc: '[email protected]',
});
// Reply
await workspace.gmail.reply(messageId, { body: 'Thanks!' });
// Forward
await workspace.gmail.forward(messageId, { to: '[email protected]', note: 'FYI' });
// Search (full Gmail query syntax)
await workspace.gmail.search('from:[email protected] has:attachment');
await workspace.gmail.search('is:unread in:inbox');
await workspace.gmail.search('subject:invoice after:2024/01/01');
// Mark read / unread
await workspace.gmail.messages.markAsRead(messageId);
await workspace.gmail.messages.markAsUnread(messageId);
// Organize
await workspace.gmail.labels.archive(messageId);
await workspace.gmail.labels.trash(messageId);
await workspace.gmail.labels.untrash(messageId);
await workspace.gmail.labels.star(messageId);
await workspace.gmail.labels.moveTo(messageId, labelId);
// List labels / folders
await workspace.gmail.labels.list();
// Drafts
await workspace.gmail.drafts.save({ to, subject, body });
await workspace.gmail.drafts.update(draftId, { body: 'Updated' });
await workspace.gmail.drafts.send(draftId);
await workspace.gmail.drafts.delete(draftId);
// Attachments
const blob = await workspace.gmail.attachments.download(messageId, attachmentId);
const url = workspace.gmail.attachments.getUrl(messageId, attachmentId);Calendar
// List all calendars
const calendars = await workspace.calendar.calendars.list();
// Create a calendar
await workspace.calendar.calendars.create({ summary: 'Team Calendar' });
// List events
const { events } = await workspace.calendar.events.list('primary', {
timeMin: new Date().toISOString(),
timeMax: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
});
// Get upcoming events (next 7 days)
const { events } = await workspace.calendar.events.upcoming('primary', 7);
// Get a single event
const event = await workspace.calendar.events.get(eventId);
// { id, title, description, location, start, end, attendees[], organizer }
// Create an event
await workspace.calendar.events.create({
title: 'Team Standup',
start: '2025-03-10T09:00:00',
end: '2025-03-10T09:30:00',
attendees: ['[email protected]', '[email protected]'],
location: 'Conference Room A',
timeZone: 'Africa/Nairobi',
});
// All-day event
await workspace.calendar.events.create({
title: 'Company Holiday',
start: '2025-12-25',
end: '2025-12-25',
allDay: true,
});
// Update an event
await workspace.calendar.events.update(eventId, { title: 'Updated Title' });
// Delete an event (sends cancellation to all attendees)
await workspace.calendar.events.delete(eventId);
// Search events
await workspace.calendar.events.search('standup');
// Check availability across multiple users
const busy = await workspace.calendar.freebusy.check(
['[email protected]', '[email protected]'],
{ timeMin: '2025-03-10T08:00:00Z', timeMax: '2025-03-10T18:00:00Z' }
);
// { '[email protected]': [{ start, end }], '[email protected]': [...] }Drive
// List files
const { files } = await workspace.drive.files.list();
// List files in a specific folder
const { files } = await workspace.drive.files.list({ folderId: 'abc123' });
// Get file metadata
const file = await workspace.drive.files.get(fileId);
// { id, name, mimeType, size, createdAt, updatedAt, webViewLink, isFolder }
// Search files (full Drive query syntax)
const { files } = await workspace.drive.files.search("name contains 'report'");
await workspace.drive.files.search("mimeType = 'application/pdf'");
// Upload a file (pass a browser File object from an <input type="file">)
const input = document.querySelector('input[type="file"]');
await workspace.drive.files.upload(input.files[0], { folderId: 'optional' });
// Download a file as a Blob
const blob = await workspace.drive.files.download(fileId);
// Export a Google Doc / Sheet / Slide to a downloadable format
const pdf = await workspace.drive.files.export(fileId, 'pdf');
const docx = await workspace.drive.files.export(fileId, 'docx');
const xlsx = await workspace.drive.files.export(fileId, 'xlsx');
// Other formats: pptx, csv, txt, html
// Organize
await workspace.drive.files.rename(fileId, 'New Name');
await workspace.drive.files.move(fileId, newFolderId);
await workspace.drive.files.copy(fileId);
await workspace.drive.files.trash(fileId);
await workspace.drive.files.delete(fileId); // permanent
// Folders
await workspace.drive.folders.create('My Folder', parentFolderId);
const { files } = await workspace.drive.folders.list(folderId);
// Sharing
await workspace.drive.permissions.add(fileId, '[email protected]', 'writer');
await workspace.drive.permissions.add(fileId, '[email protected]', 'reader');
await workspace.drive.permissions.makePublic(fileId);
await workspace.drive.permissions.remove(fileId, permissionId);
// Storage quota
const quota = await workspace.drive.getStorageQuota();
// { total, used, usedInDrive, usedInTrash, remaining } — all in bytesAll API Routes
All routes are mounted under your chosen mountPath (default: /workspace).
Auth
| Method | Route | Description |
|---|---|---|
| GET | /workspace/auth/connect | Redirect to Google login |
| GET | /workspace/auth/callback | Handle Google redirect |
| GET | /workspace/auth/status | Check if user is connected |
| DELETE | /workspace/auth/disconnect | Revoke and delete tokens |
Gmail
| Method | Route | Description |
|---|---|---|
| GET | /workspace/gmail/messages | List messages (?folder=INBOX) |
| GET | /workspace/gmail/messages/:id | Get single message |
| GET | /workspace/gmail/threads | List threads |
| GET | /workspace/gmail/threads/:id | Get full thread |
| POST | /workspace/gmail/send | Send email |
| POST | /workspace/gmail/messages/:id/reply | Reply |
| POST | /workspace/gmail/messages/:id/forward | Forward |
| PATCH | /workspace/gmail/messages/:id/labels | Archive / trash / star / move |
| GET | /workspace/gmail/labels | List labels |
| POST | /workspace/gmail/labels | Create label |
| GET | /workspace/gmail/drafts | List drafts |
| GET | /workspace/gmail/drafts/:id | Get draft |
| POST | /workspace/gmail/drafts | Save draft |
| PUT | /workspace/gmail/drafts/:id | Update draft |
| POST | /workspace/gmail/drafts/:id/send | Send draft |
| DELETE | /workspace/gmail/drafts/:id | Delete draft |
| GET | /workspace/gmail/search | Search (?q=query) |
| GET | /workspace/gmail/messages/:id/attachments/:aid | Download attachment |
Calendar
| Method | Route | Description |
|---|---|---|
| GET | /workspace/calendar/calendars | List calendars |
| POST | /workspace/calendar/calendars | Create calendar |
| GET | /workspace/calendar/events | List events |
| GET | /workspace/calendar/events/upcoming | Upcoming events |
| GET | /workspace/calendar/events/:id | Get event |
| POST | /workspace/calendar/events | Create event |
| PUT | /workspace/calendar/events/:id | Update event |
| DELETE | /workspace/calendar/events/:id | Delete event |
| POST | /workspace/calendar/freebusy | Check availability |
Drive
| Method | Route | Description |
|---|---|---|
| GET | /workspace/drive/files | List files |
| GET | /workspace/drive/files/search | Search files |
| GET | /workspace/drive/files/:id | Get file metadata |
| GET | /workspace/drive/files/:id/download | Download file |
| GET | /workspace/drive/files/:id/export | Export Google Doc |
| POST | /workspace/drive/files/upload | Upload file |
| PATCH | /workspace/drive/files/:id | Rename / update |
| POST | /workspace/drive/files/:id/copy | Copy file |
| POST | /workspace/drive/files/:id/move | Move file |
| DELETE | /workspace/drive/files/:id | Delete file |
| GET | /workspace/drive/folders/:id | List folder contents |
| POST | /workspace/drive/folders | Create folder |
| GET | /workspace/drive/files/:id/permissions | List permissions |
| POST | /workspace/drive/files/:id/permissions | Share file |
| DELETE | /workspace/drive/files/:id/permissions/:pid | Remove access |
| GET | /workspace/drive/storage | Storage quota |
Security Notes
- Never expose your
GOOGLE_CLIENT_SECRETto the frontend - Store OAuth tokens encrypted in your database in production
- Always use HTTPS in production
- The
google-workspace-client.jsfile is safe to serve publicly — it contains no secrets
Requirements
| Backend | Requirement |
|---|---|
| Node.js | Node 18+, express package |
| Django | Python 3.9+, packages in requirements.txt |
License
MIT
