Google Calendar API with NodeJS

Posted by

This post will explain how to set up Google Calendar API on NodeJS application. The official documentation for quick start is kind of a callback hell. So, I refactored them in ‘async/await’ so that it is easier to read (Thank me!). Furthermore, this article will delve into the implementation of oAuth2.0 in the application.

Dependencies

  • express
  • googleapis

To-Dos

  1. Enable the Google Calendar API
  2. Create credentials
  3. Authenticate users with oAuth2.0
  4. Request for creating an event on Google Calendar

Please complete the first two steps on Google Developer Console on your own (It should be easy!).

Authenticate users with oAuth2.0

In order to save an event on Google Calendar, the application needs to obtain an access token from Google Authentication Server. Users need to grant all requested scopes of the application. Scopes are the functionalities of Google API that the application depends on.

First, the authorization process begins with redirecting users to a Google URL where they are asked to consent to the application’s read/write access to their Google Calendar. After users grant them by signing in with their google account, it redirects them to a specified URL with an authorization code in its query. The application can exchange it for an access token and a refresh token.

The application will use the access token to access the functionalities of users’ Google Calendar. Since the access token has an expiration time, the refresh token can be used to exchange for new access token.

Here is the auth/auth.js in your NodeJS application that handles basic authentication methods for using Google Calendar API.

auth/auth.js

// auth/auth.js

const { google } = require("googleapis");
const { OAuth2 } = google.auth;

const SCOPES = ['https://www.googleapis.com/auth/calendar', 'https://www.googleapis.com/auth/calendar.events'];

const credentials = require("./credentials.json");
const oAuth2Client = new OAuth2(credentials.clientID, credentials.clientSecret, credentials.redirect_uris[0]);

exports.getAuthUrl = async (requestURL) => {
  const authUrl = oAuth2Client.generateAuthUrl({
    access_type: 'offline',
    state: requestURL,
    scope: SCOPES
  });
  return authUrl; // redirect user to the url
}

exports.getAuthByCode = async (code) => {
  const data = await oAuth2Client.getToken(code);
  oAuth2Client.setCredentials(data.tokens);
  return oAuth2Client;
}

exports.getAuthByRefreshToken = async (refreshToken) => {
  oAuth2Client.setCredentials({refresh_token: refreshToken});
  return oAuth2Client;
}

Request for creating an event on Google Calendar

In this application, we are going to make an Event model class that has two methods: check if users are not busy and create an event. Creating an event on Google Calendar requires start-time, end-time, summary and location, so the event class set them as arguments.

In controller, we instantiate the event class with requested data and check if users are busy. If not, create an event.

For authentication, we set up an middleware to return oAuth2Client by using functions defined in auth/auth.js. It runs the conditional statement whether to use auth code or refresh token to obtain an access token.

Finally, hook everything up with the application in app.js.

model/event.js

// models/event.js

const { google } = require("googleapis");

class Event {
  constructor(startTime, endTime, summary, location, description) {
    this.startTime = startTime;
    this.endTime = endTime;
    this.summary = summary;
    this.location = location;
    this.description = description;
    this.timezone = "Asia/Tokyo";
    this.colorId = 1;

    this.event = {
      summary: this.summary,
      location: this.location,
      description: this.description,
      colorId: this.colorId,
      start: {
        dateTime: this.startTime,
        timeZone: this.timezone,
      },
      end: {
        dateTime: this.endTime,
        timeZone: this.timezone,
      },
    };
  }


  async create(oAuth2Client) {
    const calendar = google.calendar({ version: "v3", auth: oAuth2Client });
    const {refresh_token} = oAuth2Client.credentials;
    await calendar.events
      .insert({ calendarId: "primary", resource: this.event })
      .then(() => {
        console.log("Event created on Calendar", this.event);
      })
      .catch((err) => {
        console.log("Error Creating Calender Event:", err);
      });
      return refresh_token;
  }

  async isBusy(oAuth2Client) {
    const calendar = google.calendar({ version: "v3", auth: oAuth2Client });
    const searchQuery = {
      resource: {
        timeMin: this.startTime,
        timeMax: this.endTime,
        timeZone: "Asia/Tokyo",
        items: [{ id: "primary" }],
      },
    };
    return calendar.freebusy
      .query(searchQuery)
      .then((res) => {
        const eventArr = res.data.calendars.primary.busy;
        if (eventArr.length > 0) {
          return true;
        }
        return false;
      })
      .catch((err) => {
        console.log(err);
      });
  }
}

module.exports = Event;

controller/event.js

// controller/event.js

const Event = require("../models/event");
const auth = require("../auth/auth");


exports.createEvent = async (req, res, next) => {
  const { oAuth2Client } = req.body;

  const event = new Event(
    req.body.startTime,
    req.body.endTime,
    req.body.summary,
    req.body.location,
    req.body.description
  );

  // google calendar api
  const isBusy = await event
    .isBusy(oAuth2Client)
    .then((result) => {
      return result;
    });

  if (!isBusy) {
    return event
      .create(oAuth2Client)
      .then(refreshToken => {
        return res.json({ refreshToken });
      })
      .catch((err) => {
        console.log(err);
      });
  }
  return res.status(200).send("You are busy");
};

middleware/middleware.js

// middleware/middleware.js

const auth = require("../auth/auth");

exports.setAuthToken = async (req, res, next) => {
  if (!req.body.token) {
    const authUrl = await auth.getAuthUrl(req.url);
    return res.send(authUrl);
  }

  const { type, token } = req.body;
  switch(type) {
    case 'CODE':
      req.body.oAuth2Client = await auth
      .getAuthByCode(token)
      .then((auth) => {
        return auth;
      })
      .catch((err) => {
        console.log(err);
      });
      break;
    case 'REFRESH_TOKEN':
      req.body.oAuth2Client = await auth.getAuthByRefreshToken(token)
      .then(auth => {
        return auth;
      }).catch((err) => {
        console.log(err);
      });
      break;
    default:
      // do something here if necessary
  }
  next();
}

app.js

// app.js

const express = require("express");

const eventController = require("./controllers/event");
const middleware = require("./middleware/middleware");

const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.post("/create", middleware.setAuthToken, eventController.createEvent);

const port = process.env.PORT || 8000;

Thanks for reading.

Hope you enjoyed the article. If you have any question or opinion to share, feel free to write some comments.

Facebook Comments