Using Webhooks to Integrate Google Calendar and React

...

In this tutorial, you’ll learn how to build a React chat app that integrates the Google Calendar API and Stream’s React Chat SDK. By the end, you’ll configure a custom webhook that retrieves your calendar events and lists them in a chat channel.

Use Webhooks to Integrate Google Calendar and React

Many chat apps today implement /slash commands for their end-users. When done right, these commands can be both practical and engaging, serving a variety of use cases.

For this tutorial, you’ll create a custom /gcal command that will populate your app's chat channel with your upcoming Google Calendar events and call it with a Webhook you'll configure in Stream's dashboard.

As an added bonus, you’ll implement Google’s OAuth 2.0 flow to access Google APIs in your app.

Developer Setup and Prerequisites

This project uses the following stack:

  • React.js
  • Express
  • Node.js
  • SQLite

Note: If you don’t know any SQL, that’s ok! By the end of this tutorial, you will be able to write simple SQL queries to execute basic CRUD operations in your server.

In addition to these technologies, you will also need:

  • Gmail
  • Google Calendar
  • Google Cloud Platform
  • Ngrok (or another tunneling service)

Before you start: Make sure you've installed Homebrew and the most recent version of Node.js.

To install Homebrew, run the following command:

Loading code sample...

You’ll also need npm (version 5.2+) so you can create a project using Create React App (CRA).

With CRA installed, create a new React project:

Loading code sample...

Then, create a folder for your server:

Loading code sample...

Stream Setup

Next, create a free Stream account. You’ll get a free 30-day trial so you can see what all Stream Chat has to offer.

After creating an account, go to your Stream Dashboard and create a project:

  1. Select Create App.
  2. Enter an App Name (like React Google Cal Integration).
  3. Select your Feeds Server and Data Storage locations.
  4. Set your Environment to Development.

Go into your project and store your API Key, Secret, and App ID somewhere safe so you can reference them later.

If you want to learn more about integrating Stream chat with your app, check out the React chat SDK tutorial.

Google Cloud Platform Setup

To use Google’s services in your app, you need to create a project on Google Cloud Platform. This requires you to create an account if you haven’t already.

Once you have an account, create a project:

  1. In your Cloud Console dashboard, go to the Manage Resources page.
  2. Select Create Project.
Create project in Google Cloud Console
  1. In the New Project window, enter a Project name (like React Google Integration).
  2. In the Organization dropdown, select No organization.
  3. Enter the parent organization or folder in the Location box.
  4. Select Create.

Create a Client ID

This project uses Google Sign-In, which implements the OAuth 2.0 flow to integrate and manage the use of Google APIs in your app.

If you’re unfamiliar with OAuth 2.0, read up on Access Tokens, Refresh Tokens, and Authorization Codes.

To use Google Sign-In, you need a Client ID:

  1. In your Google Cloud dashboard, click the Navigation menu.
  2. Go to APIs & Services and select Credentials.
Creating project credentials
  1. In the Credentials window, select CREATE CREDENTIALS, then OAuth client ID.
Creating client ID step 1
  1. In the Application type dropdown, select Web application.
  2. Enter a Name for your Web client ID.
  3. Under Authorized JavaScript origins, enter the two localhost ports you plan on using for your client and server.
Entering Javascript URIs

This project uses localhost:3000 for the client and localhost:3001 for the server.

  1. Select CREATE.
OAuth client created

You’re all set! Now, you should see your client ID listed on the Credentials page.

Ngrok Setup

You can use an alternative tunneling service, but the free ngrok plan will suffice for this project.

To get started:

  1. Go to the ngrok Signup page and enter your Name, E-mail, and Password.
Ngrok Signup
  1. Click Sign Up.
  2. In your ngrok dashboard under Getting Started > Setup & Installation, download ngrok for the appropriate OS.
Ngrok installation steps
  1. Then, follow the ngrok installation instructions.

Test your ngrok connection with the following command:

Loading code sample...

If installed correctly, you’ll see a new terminal window labeled ngrok http 80.

Ngrok connection test

Build Your React Client

With all of your accounts set up, you can organize your project. When bootstrapping a project with CRA, you’ll end up with some unnecessary files, so start by clearing those out.

Under src, delete the following files:
App.test.js
logo.svg
reportWebVitals.js
setupTests.js

You should also delete any code in App.js and App.css.

In index.js, replace the code with the following:

Loading code sample...

Cd into src and create your components folder:

Loading code sample...

Then, create your Auth.jsx and ChannelContainer.jsx components:

Loading code sample...

Lastly, create a .env file in your root project folder:

Loading code sample...

Your project folder structure should look similar to the image below:

React project folder structure

With your client set up, now you need to install the following dependencies:

Loading code sample...

Set Up Your Client’s Environment Variables

After installing your dependencies, you should create your environment variables.

You’ll need your:

  • Stream API Key
  • Google Client ID

Place these values into your .env file:

Loading code sample...

Initialize Stream Chat

Now, you need to initialize your client with Stream, which requires your API Key.

In App.jsx, add the following code:

Loading code sample...

In this snippet, you:

  • Pass in your API Key to StreamChat.getInstance() to create your client instance.
  • Wrap your app in the Chat context wrapper.
  • Return your app’s “Chat dashboard”.

All of your components will sit inside Stream’s Chat context wrapper, providing your app with all the out-of-the-box chat functionality it needs to create a messaging channel.

Build a Simplified Auth Flow

Now, you’ll build a simplified auth flow that uses cookies to set and remove a mock temp_token to log the user in and out of the chat dashboard (later, you’ll generate a unique Stream token in the backend).

In Auth.jsx, add the following code:

Loading code sample...

In Auth.jsx, you:

  • Instantiate your cookies and create a tempToken variable.
  • Declare your signIn() function, which listens for an on-click event to set the temp_token and trigger a page reload.

Now, add the following code in App.jsx:

Loading code sample...

In the snippet above, you declare a logout() function that removes your temp_token when the user clicks the logout button. It also triggers a page reload.

Now, when a user logs in, the authToken will trigger a page reload and redirect them to the chat dashboard. Clicking logout removes the token and triggers a page reload, directing the user to the sign-in page.

Lastly, add the following code to your App.css file:

Loading code sample...

You should be able to sign in and out of your app:

Building Your Backend

With your basic front-end structure out of the way, you can build your backend:

Open your backend project. In your project folder, create the following folders and files with the commands below:

Loading code sample...

Your project structure should look similar to the one below:

Server project folder structure

To get your project running, install the following dependencies:

Loading code sample...

With all your dependencies installed, configure your script in package.json so you can easily run nodemon to automatically restart your server after you save changes:

Loading code sample...

Now, go to index.js and add the following code:

Loading code sample...

Now, run npm run dev in your terminal. Your server should greet you with a “hello world” on your localhost port in your browser 😎

Define Your Environment Variables

In your .env file, you’ll need your Stream and Google secrets so you can use them throughout your server.

You can find all the required values in your Stream dashboard and under your OAuth 2.0 Client IDs in the Credentials section of your Google Cloud Platform dashboard.

Once you’ve entered those values, your .env file should look similar to the file below:

Loading code sample...

Define Your Routes and Controllers

In your ./routes folder, create a new file called auth.js:

Loading code sample...

In routes/auth.js, enter the following code:

Loading code sample...

In the snippet above, you direct your server’s routes to the specified endpoints. But if you fire up your server now, it should throw an error. That’s because you haven’t defined these endpoints in ../controllers/auth yet.

Back in your controllers folder, create another auth.js file:

Loading code sample...

Now, go to controllers/auth.js and define the handlers you’ll need for your backend with the following code:

Loading code sample...

Note: Later, you’ll define setupCommands() and handlewebhook() functions to handle your webhook. For now, you just need login() and googleauth().

Before continuing, you’ll need some way to persist your user’s Stream data, which calls for a database.

Implement Your SQLite Database

SQLite is a lightweight SQL database engine that’s perfect for the scope of this project.

To get started, you’ll need to install SQLite. If you use a Mac, it should be installed already. You can check by entering sqlite3 into your terminal. If it’s installed, you’ll see the following response in your terminal:

SQLite installation check

If it’s not installed on your machine, go to the SQLite download page and follow the instructions.

Once installed, enter your database folder and create the following files:

  • db.js: This is where you’ll configure your database connection
  • db.sql: This is where you’ll create your users table
  • proj.db: This is a simple text file that will contain your project’s in-memory database

In database, run the following command:

Loading code sample...

In db.js, enter the following code:

Loading code sample...

If you fire up your server, you should see “connection to db successful” in your console.

Now, you're ready to create your users table.

In db.sql, enter the following code:

Loading code sample...

This is a basic SQL command that tells your database to create a table if it doesn't exist. In this table, you specify what columns you need and what data types your DB should expect when you insert data.

This is a straitforward DB schema, where the data will be of the type TEXT, which means you can insert your data as a String.

Enter the following command to create your table:

Loading code sample...

To see if you created your users table successfully, enter .tables in your terminal. You should see your table listed:

Check users table in SQLite database

With your database up and running, you can define some basic CRUD operations for your app.

Go to your models folder and create a file called data-models.js:

Loading code sample...

In data-models.js, enter the following code:

Loading code sample...

In this code block, you define and export several functions that you’ll use to interact with your database:

  • findUser(email): Checks if a user exists in your database with their email.
  • insertUsers(name, email, user_id): Creates a new user by inserting their name, email, and user_id into your database’s users table.
  • updateRefreshToken(refresh_token, email): Gives the user a new refresh_token.
  • getRefreshToken(email): Retrieves the user’s refresh token with their email.
  • getRefreshTokenWithId(user_id): Retrieves the user’s refresh token with their user_id.

That’s it for your database! Now, back to your endpoints.

Backend Auth Flow With Stream and Google

Now, you can set up your Stream and Google auth flow.

Back in controllers/auth.js, replace your code with the following:

Loading code sample...

Note what’s happening in this code block:

  • You import googleapis and the OAuth2Client library.
  • You declare your scopes, which will prompt the user to give their consent for you to access their Google Calendar data.
  • You declare your Stream and Google .env variables.
  • You instantiate your Google OAuth 2.0 (oAuth2Client) client using the google.auth.OAuth2 constructor.

Note: The MARKDOWN_HASH957498a5940da078121d2fc7f984da47MARKDOWNHASH instance is important, as it keeps track of your refresh and access_tokens for you. You'll use it in multiple places throughout your server.

Here’s what’s happening in login:

  • You handle your Google token and profile object (you’ll set this up in your client soon), which generates an authorization url. This url is necessary to generate the consent form for your users, and specifies your permission scopes.
  • You initialize your server-side Stream client with your .env variables.
  • Your dataModel.findUser() function checks if there’s a user with the same email in the database. If no user exists, you generate a unique Stream user_id and call insertUsers to create the user in the database and generate their Stream token.
  • If the user does exist, you pass in the user_id to create a new Stream token.
  • Finally, you send your token, user_id, name, email, and url back to your front-end client.

With this data, you can authenticate a user on your front-end with a Stream token, replacing the mock token you used earlier. You can also prompt the user to give you access to their Google Calendar data.

Integrating Google Sign-In

Back in your client, you’ll add React Google Login to handle your Google sign-in.

In Auth.jsx, replace the file with the following code:

Loading code sample...

In this snippet:

  • You place the GoogleLogin component in your return statement.
  • You pass in your clientId as a prop and the responseGoogle function to handle Google’s response when you sign in.
    • In responseGoogle, you’re expecting a profile object, which includes your name, email, and other data related to your Google account.
  • Next, you specify the URL endpoint you want to hit in your server and create your fetch request, which must include the data and tokenId from the profile object.
  • Upon a successful request, you’ll receive your name, email, Stream token, Stream user_id, and Google’s unique authorization url. You’ll store the name, email, token, and user_id in cookies with cookies.set().
  • Lastly, you use window.location.assign(url) to navigate to Google's authorization url, which contains a code parameter you’ll pass to your server assuming the user grants access to the requested permission scopes.

To handle the code parameter and log your user into their chat channel, you need to update App.jsx with the following code:

Loading code sample...

In this snippet:

  • You retrieve your Stream token with cookies.get(‘token’) and declare the authToken variable.
  • Then, with connectUser(), you pass in the user_id, name, and authToken to create a websocket connection with the client.
  • You pass in the client object as a prop to your Chat context, which you’ll use in your ChannelContainer component to create a channel.
  • When the user clicks logout, you remove your cookies and close out the session.
  • Because you have an authToken, your app redirects the user to the chat dashboard inside ChannelContainer.

At this point, Google will redirect the user to the authorization url that you received in Auth.jsx with the scopes you defined in your server:

Permission scopes

Here, when your user grants permission to the scopes, they'll be redirected to your chat dashboard. If they deny access, they'll be sent back to the sign-in page.

The last component you need to handle is in ChannelContainer.jsx.

Add in the following code:

Loading code sample...

In this snippet:

  • You import the stream-chat-react css file to give your channel a cleaner layout.
  • You use useChatContext() to access the client object, where you get your user’s id to create a channel.
  • Then, you pass channel as a prop into the Channel component.
  • In your useEffect hook, you parse the code parameter from the url your user lands on.
  • In your fetch request, you send your email and code to the /auth/googleauth endpoint.

To add a little bit more customization to your app’s look and feel (and to override some of Stream’s default css properties), add the following code to your App.css file:

Loading code sample...

Handling Google’s Refresh and Access Tokens

A core part of OAuth 2.0 is refresh and access tokens. When using Google’s OAuth 2.0 flow, access tokens are required to make requests to Google APIs. This includes Google Calendar.

But, access tokens expire quickly, which means you need to generate a new one with your refresh token. Google handles this process automatically for you, but it’s on you as the developer to store your refresh token.

This next section will show you how:

In your controllers folder in auth.js, enter the following code:

Loading code sample...

In this snippet, you receive the authorization code and email from your front-end, and then check if you’ve previously stored a refresh_token with dataModel.getRefreshToken(email).

If you have a refresh token, your server does nothing with the code. If you don’t have a refresh token, you pass in the code to oAuth2Client.getToken(), which will generate an access_token and refresh_token, but only on the first authorization.

On subsequent authorizations, Google will look for your stored refresh token to exchange for a new access token. To manage your tokens, use Google’s oAuth2Client.on() token event, which stores your refresh and access tokens for you (you still have to manually set your refresh token to get a new access token, but you’ll cover that in your webhook configuration).

After receiving your tokens, call setCredentials to authorize your user.

Configuring Your Webhook

Finally, now you can create a custom webhook.

First, fire up an ngrok process for your server:

Loading code sample...

In your Stream dashboard:

  1. Click on your app.
  2. In the nav, select Chat, then Overview.
Stream dashboard overview
  1. Under the Realtime window, specify your url.
    • Note: This must be a publicly accessible URL. In addition to generating your URL with ngrok, you also need to add the /auth/handlewebhook endpoint.
Webhook url in Stream chat dashboard
  1. Select Save.

Now, in components/auth.js, add the following line of code to login under your serverClient instance:

Loading code sample...

Then, create your setupCommands function to configure your custom commands webhook:

Loading code sample...

In this snippet, you’re using your ngrokUrl for your custom_action_handler_url, and setting up the command so that Stream knows to look for the /gcal custom command.

Lastly, create your handlewebhook handler:

Loading code sample...

In this snippet:

  1. You get your user_id and the message (/gcal) from your request and fetch your user’s refresh token with getRefreshTokenWithId().
  2. Using your global oAuthClient2 instance, you call setCredentials({refresh_token: rToken.refresh_token}) to get a new access token.
    • Note: This part is crucial. Google will automatically replace your expired access token with a new one, but you must call setCredentials with the {refresh_token} object here to do so. If you don’t, your access token will expire, which will force the user to log in again to get new access and refresh tokens.
  3. Next, you call google.calendar() and pass in your oAuth2Client to make a call to the Google Calendar API to retrieve your events.

You’ll get a list of the next five events from your Google Calendar. Using some JavaScript, you can then extract start and end times from your Date strings.

Lastly, using Stream’s Message Markup Language (MML), which is currently only supported in the React SDK, you can create a list of your events using MarkDown and send it back to your frontend client.

The end result will look like this:

Wrapping Up

Congratulations! In this tutorial, you successfully implemented Google’s OAuth 2.0 flow using refresh and access tokens to make API calls to retrieve events from Google Calendar.

You also configured your own webhooks in your server and in Stream’s dashboard, enabling you to implement your own custom commands.

Plus, you built the beginnings of a full-stack application that uses a SQL database and some basic SQL queries.

You can find all the code for the front-end project in this GitHub repo, and the code for the backend project in this GitHub repo.

As always, show us what you're working on @getstream_io and keep coding!