google-calendar-client
v1.0.1
Published
Simple, type-safe TypeScript client for Google Calendar API with automatic OAuth2 token refresh and batch operations
Maintainers
Readme
Google Calendar API Client
A simple, type-safe TypeScript wrapper for Google Calendar API with automatic token refresh.
Features
- ✅ Full CRUD operations (Create, Read, Update, Delete)
- ✅ List and search events with advanced filters
- ✅ Batch operations for multiple events
- ✅ Automatic OAuth2 token refresh
- ✅ Google Meet integration
- ✅ TypeScript support
- ✅ Zero dependencies
Installation
npm install google-calendar-clientQuick Start
1. Setup
Get your Google OAuth credentials from Google Cloud Console:
- Create a project
- Enable Google Calendar API
- Create OAuth 2.0 credentials
2. Initialize
import { GoogleCalendarAPI } from 'google-calendar-client';
const calendar = new GoogleCalendarAPI({
clientId: 'your-client-id.apps.googleusercontent.com',
clientSecret: 'your-client-secret',
});3. Authenticate User
// Redirect user to Google OAuth
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?${new URLSearchParams({
client_id: 'your-client-id',
redirect_uri: 'http://localhost:3000/callback',
response_type: 'code',
scope: 'https://www.googleapis.com/auth/calendar',
access_type: 'offline',
prompt: 'consent',
})}`;
// After redirect, exchange code for tokens
const tokens = await calendar.exchangeAuthCode({
code: 'code-from-redirect',
redirectUri: 'http://localhost:3000/callback',
});
// Save these tokens
const accessToken = tokens.access_token;
const refreshToken = tokens.refresh_token;4. Use the API
// Create an event
const event = await calendar.createEvent({
title: 'Team Meeting',
startEpoch: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
endEpoch: Math.floor(Date.now() / 1000) + 5400, // 1.5 hours from now
accessToken,
refreshToken,
});
console.log('Event created:', event.event.htmlLink);
console.log('Meet link:', event.event.hangoutLink);Basic Usage
Create Event
const result = await calendar.createEvent({
title: 'Product Demo',
description: 'Demo for new client',
startEpoch: 1698336000,
endEpoch: 1698339600,
timeZone: 'America/New_York',
location: 'Conference Room A',
attendees: [
{ email: '[email protected]' },
{ email: '[email protected]' },
],
accessToken,
refreshToken,
});List Events
const result = await calendar.listEvents({
accessToken,
refreshToken,
timeMin: Math.floor(Date.now() / 1000), // From now
timeMax: Math.floor(Date.now() / 1000) + 7 * 86400, // Next 7 days
maxResults: 50,
singleEvents: true, // Expand recurring events
orderBy: 'startTime',
});
for (const event of result.events) {
console.log(`${event.summary} - ${event.start.dateTime}`);
}Update Event
await calendar.updateEvent({
eventId: 'event-id',
title: 'Updated Title',
startEpoch: 1698340000,
endEpoch: 1698343600,
accessToken,
refreshToken,
});Delete Event
await calendar.deleteEvent({
eventId: 'event-id',
accessToken,
refreshToken,
});Get Single Event
const result = await calendar.getEvent({
eventId: 'event-id',
accessToken,
refreshToken,
});
console.log(result.event);Advanced Features
Batch Operations
Create, update, or delete multiple events in one call:
const result = await calendar.batchOperations({
accessToken,
refreshToken,
operations: [
// Create events
{
type: 'create',
data: {
title: 'Event 1',
startEpoch: 1698336000,
endEpoch: 1698339600,
},
},
// Update events
{
type: 'update',
data: {
eventId: 'existing-id',
title: 'Updated Event',
},
},
// Delete events
{
type: 'delete',
data: {
eventId: 'old-event-id',
},
},
],
});
console.log(`Success: ${result.successCount}, Failed: ${result.failureCount}`);Recurring Events
await calendar.createEvent({
title: 'Weekly Standup',
startEpoch: 1698336000,
endEpoch: 1698337800,
recurrence: ['RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR;COUNT=10'],
accessToken,
refreshToken,
});Search Events
const result = await calendar.listEvents({
accessToken,
refreshToken,
q: 'team meeting', // Search text
timeMin: Math.floor(Date.now() / 1000),
});Custom Event Properties
await calendar.createEvent({
title: 'Client Meeting',
startEpoch: 1698336000,
endEpoch: 1698339600,
extendedProperties: {
private: {
clientId: '12345',
salesRep: 'john',
},
},
accessToken,
refreshToken,
});Important Notes
Token Refresh
The library automatically refreshes expired tokens. Always save the new token:
const result = await calendar.createEvent({...});
if (result.token) {
// Token was refreshed - save it!
accessToken = result.token.access_token;
refreshToken = result.token.refresh_token || refreshToken;
// Update your database
}Timestamps
Use seconds, not milliseconds:
// ✅ Correct
const startEpoch = Math.floor(Date.now() / 1000);
// ❌ Wrong
const startEpoch = Date.now();Error Handling
try {
await calendar.createEvent({...});
} catch (error) {
console.error('Failed:', error.message);
}API Methods
| Method | Description |
|--------|-------------|
| exchangeAuthCode() | Exchange OAuth code for tokens |
| createEvent() | Create a new calendar event |
| updateEvent() | Update an existing event |
| deleteEvent() | Delete an event |
| getEvent() | Get a single event |
| listEvents() | List/search events with filters |
| moveEvent() | Move event between calendars |
| batchOperations() | Bulk create/update/delete |
Type Definitions
Full TypeScript support with comprehensive types. See API_REFERENCE.md for details.
import type {
CalendarEvent,
CreateEventPayload,
ListEventsPayload,
BatchOperationsPayload,
} from 'google-calendar-client';Examples
Complete App Example
import { GoogleCalendarAPI } from 'google-calendar-client';
class CalendarService {
private calendar: GoogleCalendarAPI;
constructor() {
this.calendar = new GoogleCalendarAPI({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
});
}
async createMeeting(data: {
title: string;
startTime: Date;
duration: number;
attendees: string[];
}) {
const tokens = await this.getTokens(); // Your token storage
const startEpoch = Math.floor(data.startTime.getTime() / 1000);
const endEpoch = startEpoch + data.duration * 60;
const result = await this.calendar.createEvent({
title: data.title,
startEpoch,
endEpoch,
attendees: data.attendees.map(email => ({ email })),
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken,
});
if (result.token) {
await this.saveTokens(result.token); // Update stored tokens
}
return {
eventId: result.event.id,
meetLink: result.event.hangoutLink,
};
}
}Import from CSV
const events = parseCSV('events.csv');
const result = await calendar.batchOperations({
accessToken,
refreshToken,
operations: events.map(e => ({
type: 'create',
data: {
title: e.title,
startEpoch: parseDate(e.start),
endEpoch: parseDate(e.end),
},
})),
});
console.log(`Imported ${result.successCount} events`);Documentation
- API_REFERENCE.md - Complete parameter reference and advanced options
- GitHub - Source code and issues
License
MIT
Support
- 📖 Documentation: API_REFERENCE.md
- 🐛 Issues: GitHub Issues
