Building a serverless chat backend with Google Cloud Platform

Yonah Forst
Abi.ai
Published in
4 min readFeb 16, 2018

(skip to part 2 for tldr and the tech stuff)

Part 1: Why

Here at Abi Global Health, we integrate with almost all popular messaging services (FB Messenger, Telegram, Skype, etc…). It follows our goal of making medical advice conveniently available to everyone.

But what if the most convenient place is inside your favourite health app? We wanted to open up the possibilities for chatting with Abi directly from inside other apps and services. So we need a chat backend.

Our current architecture looks like this:

I don’t draw well

All messaging services connect to our msg-gateway aggregator. It uses Babelbot to parse the different JSON payloads, then broadcasts the normalized result to the rest of our system for handling. Also pictured is our chatLogger which keeps a record of all incoming/outgoing messages.

My first question was: should the chatLogger become the chat backend? That’s our central repo for all conversations. If we built another system, they would eventually make their way to the chatLogger anyway.

I tapped my buddy Javier Marquez on the shoulder and asked him what he thought. “No pasa nada, quillo” he replied and explained the service concerns are sufficiently different and that having the data in both places isn’t so terrible.

So then the question was: off-the-shelf or do we build it ourselves? There are a lot of really nice products out there, but none of them fit our needs exactly. Our use case isn’t the classic chat app. Users are chatting with our bot, not with each other. We don’t need typing indicators, rooms, invitations, admins, all that stuff. Plus all those features come at a cost.

Firebase was the obvious choice for the database. If you’re new to Firebase, it’s particularly useful for syncing a set of data across multiple devices. In our case, the data we want to sync is a list of messages in a chatroom. Both Abi and the user subscribes to that chatroom. When the user writes a message Abi is notified, and visa-versa.

Firebase also lets you write fine-grained access rules. We can restrict users to only their own chatroom while Abi can access any chatroom.

Here’s how it would integrate into our system:

‘our chat service’ essentially forwards messages back-and-forth between Firebase and our msg-gateway

Part 2: How

So here’s the deal: You have an amazing health app but sometimes your users want advice from a real doctor. Easy enough. Download the Firebase SDK and connect to our database instance. Have your frontend watch messages/{userId} If you push a message to that endpoint, you see Abi’s reply there a second or two later. Whoa!

Authentication and Authorization

Database access needs to be restricted so that users can only read their own messages, and not another user’s. Firebase to the rescue again. We’ll have an authorization endpoint which will return Firebase credentials scoped to individual users. Database rules will restrict access to the user’s own chatroom.

Infrastructure

We’ll need two things: 1) an endpoint that can authenticate requests and issue Firebase credentials and 2) something to listen for new messages and forward them to Abi. Turns out, Firebase Cloud Functions can do both those things.

We use Serverless for almost all our AWS-based services and I was excited to try and build a service on Google Cloud Platform. My excitement didn’t last long as I realized that Serverless doesn’t support realtime database triggers.

Then I learned that Google Cloud Functions and Firebase Cloud Functions are actually two different things. Or are they? Still not sure about that one. Anyway, Firebase has their own CLI for deploying cloud functions which works a bit differently from from what I was used to with Serverless+AWS.

In Serverless, there’s a .yml config file that defines how functions should be triggered by events. For example, from our msg-gateway:

functions:
webhookFunc:
handler: functions.webhook
events:
- http:
method: POST
path: webhook

In Firebase Cloud Functions, the event triggers are defined directly in the code, like this:

const functions = require('firebase-functions')
module.exports.webhook = functions.https.onRequest((req, res) => {
const { method, body } = req
//...
})

Close enough.

Data Structure

The data structure is taken straight from the official documentation

awesome-project-name
- messages
- room1
- message1
- userId: user1
- body: 'hello'
- at: 1234567
- message2
- userId: user2
- body: 'hi there!'
- at: 7654321
- room2
- ...
- ...
- chats
- room1
- userId: user2
- body: 'hi there!'
- at: 7654321
- room2
...

Every message belongs to a room. It contains the userId who sent it, the body, and an at timestamp. Individual messages are stored under /messages/{roomId}/{messageId} The most recent message in each room is stored under chats/{roomId}

So far so good!

Code

The two functions are pretty straight forward:

And database rules:

Then I got to thinking: This is a pretty generic implementation. What if I just exposed handlers for validating JWTs and notifying the backend. Everything else could go into a module that accepts a configuration file like this

{
authenticate: payload => {
return makeRequest('/authenticate', payload))
},
onNewMessage: payload => {
return makeRequest('/newMessage', payload)
},
}

And so, I’m proud to present…. drumroll…. chatfireServerless

Check it out! It’s pretty basic now, but I hope to expand it soon to add support for images, typing indicators, etc… (all that good stuff I said we didn’t need)

Now it’s time to deploy and see how it all works and, hopefully, send to production.

Stay tuned!!

--

--