# Going Serverless with Google Firebase Functions

RELE.AI (opens new window) lets you leverage WhatsApp as a gateway to your enterprise systems and business processes intuitively. However, today, many enterprises decided to go serverless (opens new window). Serverless is an approach that uses managed infrastructure by cloud vendors to increase developers' productivity. One example will be Google Firebase Functions (opens new window), a solution that is connected natively to Firebase products.

# What are we going to build

To demonstrate RELE.AI simply, we will connect an example ToDo application with WhatsApp using the RELE.AI CLI (opens new window) and Google Firebase Functions (opens new window).

The ToDo application will have the following workflows automated using RELE.AI (opens new window):

  1. /create-task - Create a new task
  2. /summary - Get a summary of all the open tasks
  3. /get-task - Get a specific task
  4. /delete-task - Delete an open task

We will use JSONPlaceholder (opens new window) /todo resource as our back-end service for this project.

todo-architecture

USE A TEMPLATE

This tutorial comes with a GitHub template (opens new window) that you can use!

Run the following command to init a project from a serverless template:

rb create serverless -t rele-ai/firebase-functions-integration-template#main

This tip assumes you're already authenicated with the Firebase CLI and Firestore is already initiated in your project.

# How does it work

RELE.AI's engine runs the given workflow one operation after the other. Each operation can send a request to an external server. The response from the request will be flattened and then added to the Redis state. Since the response is stored in the state, we recommend using a controller (configuration of type App) to shape the data as needed. In the case of serverless, those controllers will be cloud functions.region('{{ GOOGLE_FUNCTIONS_REGION }}').

Example:

  • The developer configured an operation to make a request to an external cloud function.
  • RELE.AI's core engine processed the operation and made the request to the handler.
  • The handler collected and formatted the data from a backend server before returning the response to RELE.AI's engine.
  • RELE.AI's engine added the response to the session state.

# Setting up Firebase Functions

Cloud Functions for Firebase is a serverless framework that lets you automatically run backend code in response to events triggered by Firebase features and HTTPS requests. Your JavaScript or TypeScript code is stored in Google's cloud and runs in a managed environment. So there's no need to manage and scale your servers.

# Creating a Firebase project

  1. In the Firebase console (opens new window), click on Add project, then select or enter a Project name. If you already have an existing Google Cloud project, you can select the project from the dropdown menu to add Firebase resources to that project.
  2. Click Continue
  3. Click Create project (or Add Firebase, if you're using an existing Google Cloud Project).

TIP

Make sure the Google Cloud Project is associated with a billing account

Firebase automatically provisions resources for your Firebase project. When the process completes, you'll be taken to the overview page for your Firebase project in the Firebase console.

# Setting up Node.JS and Firebase CLI

You'll need a Node.js (opens new window) environment to write functions, and you'll need the Firebase CLI to deploy functions to the Cloud Functions runtime.

Once you have Node.js and npm installed, install the Firebase CLI (opens new window) via your preferred method. To install the CLI via npm, use:

npm install -g firebase-tools

This installs the globally available firebase command.

# Initalize your project

When you initialize Firebase SDK for Cloud Functions, you create an empty project containing dependencies and some minimal sample code, and you choose either TypeScript or JavaScript for composing functions.region('{{ GOOGLE_FUNCTIONS_REGION }}'). For the purposes of this tutorial, you'll also need to initialize Cloud Firestore.

  1. Run firebase login in your terminal to login via the browser and authenticate the firebase tool.
  2. Go to your Firebase project directory.
  3. Run firebase init firestore. For this tutorial, you can accept the default values when prompted for Firestore rules and index files. If you haven't used Cloud Firestore in this project yet, go to https://console.firebase.google.com/project/$YOUR-PROJECT/firestore to create your Cloud Firestore database. You'll also need to select a starting mode and location for Firestore as described in Get started with Cloud Firestore (opens new window).
  4. Run firebase init functions. The tool gives you an option to install dependencies with npm. It is safe to decline if you want to manage dependencies in another way, though if you do decline you'll need to run npm install before emulating or deploying your functions.region('{{ GOOGLE_FUNCTIONS_REGION }}').

The tool gives you two options for language support:

  • JavaScript
  • TypeScript. See Write Functions with TypeScript for more information.

For this tutorial, select JavaScript. After these commands complete successfully, your project structure looks like this:

myproject
 +- .firebaserc    # Hidden file that helps you quickly switch between
 |                 # projects with `firebase use`
 |
 +- firebase.json  # Describes properties for your project
 |
 +- functions/     # Directory containing all your functions code
      |
      +- .eslintrc.json  # Optional file containing rules for JavaScript linting.
      |
      +- package.json  # npm package file describing your Cloud Functions code
      |
      +- index.js      # main source file for your Cloud Functions code
      |
      +- node_modules/ # directory where your dependencies (declared in
                       # package.json) are installed

# Installing axios (opens new window) for later use

We will use axios in this example to make requests to the backend service.

To install axios in the project:

# cd into the functions directory
cd ./functions/

# install axios via npm
npm install axios

# Setting up the RELE.AI CLI

You'll need the RELE.AI CLI to deploy workflows to the RELE.AI runtime and Node.js (opens new window) to run the CLI. Once you have Node.js and npm installed, install the RELE.AI CLI via your preferred method. To install the CLI via npm, use:

npm install -g @releai/cli

# Authenticate with RELE.AI CLI

Run rb auth:login to log in via the browser and authenticate the RELE.AI tool. If you are already a registered organization, you can log in to your user before authorizing the CLI. Otherwise, you can register as a new company and authorized the CLI once you're done with the registration.

# Add a configs directory to your firebase project layout

Under the functions directory, create a new folder called configs. This directory will contain all the RELE.AI workflow configurations.

myproject
 +- .firebaserc    # Hidden file that helps you quickly switch between
 |                 # projects with `firebase use`
 |
 +- firebase.json  # Describes properties for your project
 |
 +- functions/     # Directory containing all your functions code
      |
      +- .eslintrc.json  # Optional file containing rules for JavaScript linting.
      |
      +- package.json  # npm package file describing your Cloud Functions code
      |
      +- index.js      # main source file for your Cloud Functions code
      |
      +- node_modules/ # directory where your dependencies (declared in
      |                # package.json) are installed
      +- configs/      # directory where your workflow configs
          |            # will be listed
          +- app.yaml       # the serverless application definitions
          |
          +- create-task-workflow.yaml # the create task workflow definiton
          |
          +- summary-workflow.yaml # the summary workflow definiton
          |
          +- get-task-workflow.yaml # the get task workflow definiton
          |
          +- delete-task-workflow.yaml the delete task workflow definition

# Defining the application

The application definition is an indication for RELE.AI's runtime on the base request details for the integrated service. In a serverless deploying, the definition needs to point the base URL to the cloud endpoint.

You can find the the base URL in your firebase functions console under each function: firebase-functions-console-screenshot

After you have the base_url of your cloud functions, create the application definition and write it to the configs directory under app.yaml

# describe the config type
type: App

# point to the app's base url
# example: https://{us-central1}-{my-new-project}.cloudfunctions.net
base_url: https://{region}-{projectId}.cloudfunctions.net

# access protocol - gRPC or REST
protocol: REST

# app config identifier
key: serverless_app

# Writing the handlers and workflows

For each workflow we will create a handler that will format the data according to our needs and a set of operations that will define the user's experience within WhatsApp.

# Create task

To define the create-task workflow we will first define the user's experience within WhatsApp:

  1. The user will send /create-task to RELE.AI's number
  2. The bot will reply with a question about the content of the task
  3. The user will send the content of the task to RELE.AI's number
  4. RELE.AI runtime will make a request to the backend service asking to create a task
  5. The bot will reply with a success message

# Step 1: Creating the workflow trigger

We will start by creating the workflow trigger definition in the configs directory:

type: Workflow
match:
  callback: exact_match
  input: /create-task
key: create_task_workflow

TIP

The create task workflow, uses the exact_match method. This method detects a specific message text from the user and matches it to the provided configuraiton.

# Defining the user experience

To the define the user experience within WhatsApp we will define a set of operations that will correspond to each step of the above description.

# Step 2-3: The user will get a question from the bot asking for the task content

To send a message via the WhatsApp integration, use the whatsapp application and the send_message app_action. To read more about the WhatsApp integration, check the WhatsApp integration docs.

type: Operation

selector:
  workflow:
    - create_task_workflow
  app: whatsapp
  app_action: send_message

# the is_root attribute defines where the workflow should start
is_root: true

next:
  selector:
    - type: operation
      data:
        workflow: create_task_workflow
        next: create_task

payload:
  content:
    type: raw
    data: "What would you like the task to say?"

key: ask_the_user_for_task_content

# Step 4: RELE.AI runtime will make a request to the backend service to create the task

Once the user replied to the question, RELE.AI runtime will make a request to the serverless handler to create the task through the backend service with the provided content. We want to use the middleware handler to provide an authentication, data validation, and formatting layer between RELE.AI's runtime and the backend service.

type: Operation

selector:
  workflow:
    - create_task_workflow
  app: serverless_app
  app_action: create_task_handler

next:
  selector:
    - type: operation
      data:
        workflow: create_task_workflow
        next: send_task_id_to_user

# we will use the on_error attribute
# to handle server errors and let the user
# know what happend.
on_error:
  selector:
    - type: operation
      data:
        workflow: create_task_workflow
        next: send_error_message_to_user

payload:
  task:
    # we will grab the user's response
    # from the global redis state using the operation
    # key as a prefix to the state data
    type: redis
    rkey_type: hash_map
    data: ask_the_user_for_task_content:message_data:message:body

key: create_task

# Step 5: The bot will reply back with a success message

type: Operation

selector:
  workflow:
    - create_task_workflow
  app: whatsapp
  app_action: send_message

output:
  operation_type: drop_session

payload:
  # we will use the vars option to customize the response message with
  # the data we got from the handler
  content:
    type: raw
    data: "I've created that task for you. Your task ID is: $task_id"
    vars:
      # we will grab the task id from the global redis state
      task_id:
        type: redis
        rkey_type: hash_map
        data: create_task:id

key: send_task_id_to_user

# Handle server errors

type: Operation

selector:
  workflow:
    - create_task_workflow
    - summary_workflow
    - get_task_workflow
    - delete_task_workflow
  app: whatsapp
  app_action: send_message

output:
  operation_type: drop_session

payload:
  content:
    type: raw
    data: "Something went wrong :-("

key: send_error_message_to_user

# Writing the serverless handler

The handler will get the data from the RELE.AI's runtime, make a request to the backend service and reply back with the created task id.

const axios = require('axios')
const functions = require('firebase-functions')

exports.createTaskHandler = functions
	.region('{{ GOOGLE_FUNCTIONS_REGION }}')
	.https
	.onRequest(async (req, res) => {
		// make a request to the backend service
		const response = await axios.post('https://jsonplaceholder.typicode.com/todos', {
			title: req.body.task,
			userId: 1,
			completed: false,
		})

		// format the provided response to our needs
		res.json({
			id: response.data.id
		})
	})

In addition to the handler we will need to provide RELE.AI with an app_action configuration to make the handler accessible through RELE.AI.

type: AppAction
selector:
  app: serverless_app
request:
  method: POST
  uri: /createTaskHandler
key: create_task_handler

⚙️ Automation

Remember you can always automate the app_action generation using a build script.

🛡️ Security

It is recommended to pass some access token to add another validation layer to the request.

# Summary

To define the summary workflow we will first define the user's experience within WhatsApp:

  1. The user will send /summary to RELE.AI's number
  2. RELE.AI's runtime will collect all the information from the backend service
  3. The bot will reply with the content of the summary.

# Step 1: Creating the workflow trigger

We will start by creating the workflow trigger definition in the configs directory:

type: Workflow
match:
  callback: exact_match
  input: /summary
key: summary_workflow

TIP

The summary workflow, uses the exact_match method. This method detects a specific message text from the user and matches it to the provided configuraiton.

# Defining the user experience

To the define the user experience within WhatsApp we will define a set of operations that will correspond to each step of the above description.

# Step 2: RELE.AI runtime will make a request to the backend service to get a summary of the tasks

RELE.AI's runtime will request the serverless handler to collect all the summary information. Then, the handler will get the back-end service data, do some calculations, and send back a formated response.

type: Operation

is_root: true

selector:
  workflow:
    - summary_workflow
  app: serverless_app
  app_action: summary_handler

next:
  selector:
    - type: operation
      data:
        workflow: summary_workflow
        next: send_summary

# we will use the on_error attribute
# to handle server errors and let the user
# know what happend.
#
# NOTE: We will be using the same error message
# operation from the last endpoint.
on_error:
  selector:
    - type: operation
      data:
        workflow: summary_workflow
        next: send_error_message_to_user

key: collect_summary

# Step 3: The bot will reply back with the summary

type: Operation

selector:
  workflow:
    - summary_workflow
  app: whatsapp
  app_action: send_message

output:
  operation_type: drop_session

payload:
  # we will use the vars option to customize the response message with
  # the data we got from the handler
  content:
    type: redis
    data: collect_summary:text

key: send_summary

# Writing the serverless handler

The handler will get the data from the RELE.AI's runtime, make a request to the backend service and reply back with the tasks summary.

const axios = require('axios')
const functions = require('firebase-functions')

function getSummaryData(tasks) {
	let summaryData = {
		userIds: new Set(),
		taskIds: new Set(),
		completedTasks: 0,
	}

	// for each task collect the relevant information
	for (const task of tasks) {
		// append the user id
		summaryData.userIds.add(task.userId)

		// append the task id
		summaryData.taskIds.add(task.id)

		// increase count if task is completed
		if (task.completed) {
			summaryData.completedTasks++
		}
	}

	return {
		...summaryData,
		usersCount: summaryData.userIds.size,
		tasksCount: summaryData.taskIds.size,
	}
}

exports.summaryHandler = functions
	.region('{{ GOOGLE_FUNCTIONS_REGION }}')
	.https
	.onRequest(async (req, res) => {
		// make a request to the backend service
		const response = await axios.get('https://jsonplaceholder.typicode.com/todos')

		// calculate summary informaiton and format data
		const summaryData = getSummaryData(response.data)

		// format the provided response to our needs
		res.json({
			text: `
			Here's a summary of your tasks:\n
			• ${summaryData.usersCount} users created tasks in the system.\n
			• ${summaryData.tasksCount} tasks are stored in the system.\n
			• ${summaryData.completedTasks} are completed tasks.
			`
		})
	})

In addition to the handler we will need to provide RELE.AI with an app_action configuration to make the handler accessible through RELE.AI.

type: AppAction
selector:
  app: serverless_app
request:
  method: GET
  uri: /summaryHandler
key: summary_handler

# Get task

To define the get-task workflow we will first define the user's experience within WhatsApp:

  1. The user will send /get-task to RELE.AI's number
  2. The bot will request the task ID.
  3. The user will send the task ID to RELE.AI's number
  4. RELE.AI runtime will query the backend service for the task
  5. The bot will reply with the task

# Step 1: Creating the workflow trigger

We will start by creating the workflow trigger definition in the configs directory:

type: Workflow
match:
  callback: exact_match
  input: /get-task
key: get_task_workflow

# Defining the user experience

To the define the user experience within WhatsApp we will define a set of operations that will correspond to each step of the above description.

# Step 2-3: The bot will ask the user for the task ID

To send a message via the WhatsApp integration, use the whatsapp application and the send_message app_action. To read more about the WhatsApp integration, check the WhatsApp integration docs.

type: Operation

selector:
  workflow:
    - get_task_workflow
  app: whatsapp
  app_action: send_message

# the is_root attribute defines where the workflow should start
is_root: true

next:
  selector:
    - type: operation
      data:
        workflow: get_task_workflow
        next: query_task

payload:
  content:
    type: raw
    data: "What is the task ID you're looking for?"

key: ask_the_user_for_task_id

# Step 4: RELE.AI runtime will query the backend service for the task

Once the user replied to the question, RELE.AI runtime will make a request to the serverless handler to get the task through the backend service with the provided id.

type: Operation

selector:
  workflow:
    - get_task_workflow
  app: serverless_app
  app_action: get_task_handler

next:
  selector:
    - type: operation
      data:
        workflow: get_task_workflow
        next: send_task_to_user

# we will use the on_error attribute
# to handle server errors and let the user
# know what happend.
on_error:
  selector:
    - type: operation
      data:
        workflow: get_task_workflow
        next: send_error_message_to_user

payload:
  id:
    # we will grab the user's response
    # from the global redis state using the operation
    # key as a prefix to the state data
    type: redis
    rkey_type: hash_map
    data: ask_the_user_for_task_id:message_data:message:body

key: query_task

# Step 5: The bot will reply back with the task details

type: Operation

selector:
  workflow:
    - get_task_workflow
  app: whatsapp
  app_action: send_message

output:
  operation_type: drop_session

payload:
  # we will use the vars option to customize the response message with
  # the data we got from the handler
  content:
    type: redis
    data: query_task:text

key: send_task_to_user

# Writing the serverless handler

The handler will get the data from the RELE.AI's runtime, make a request to the backend service and reply back with the task details.

const axios = require('axios')
const functions = require('firebase-functions')

exports.getTaskHandler = functions
	.region('{{ GOOGLE_FUNCTIONS_REGION }}')
	.https
	.onRequest(async (req, res) => {
		// make a request to the backend service
		const response = await axios.get(`https://jsonplaceholder.typicode.com/todos/${req.query.id}`)

		// format the provided response to our needs
		res.json({
			text: `
			*title:* ${response.data.title}\n
			*completed:* ${response.data.completed ? 'Yes' : 'No'}
			`
		})
	})

In addition to the handler we will need to provide RELE.AI with an app_action configuration to make the handler accessible through RELE.AI.

type: AppAction
selector:
  app: serverless_app
request:
  method: GET
  uri: /getTaskHandler
key: get_task_handler

# Delete task

To define the delete-task workflow we will first define the user's experience within WhatsApp:

  1. The user will send /delete-task to RELE.AI's number
  2. The bot will request the task ID.
  3. The user will send the task ID to RELE.AI's number
  4. RELE.AI runtime will delete the task via the backend service
  5. The bot will reply with a success message

# Step 1: Creating the workflow trigger

We will start by creating the workflow trigger definition in the configs directory:

type: Workflow
match:
  callback: exact_match
  input: /delete-task
key: delete_task_workflow

# Defining the user experience

To the define the user experience within WhatsApp we will define a set of operations that will correspond to each step of the above description.

# Step 2-3: The bot will ask the user for the task ID

To send a message via the WhatsApp integration, use the whatsapp application and the send_message app_action. To read more about the WhatsApp integration, check the WhatsApp integration docs.

type: Operation

selector:
  workflow:
    - delete_task_workflow
  app: whatsapp
  app_action: send_message

# the is_root attribute defines where the workflow should start
is_root: true

next:
  selector:
    - type: operation
      data:
        workflow: delete_task_workflow
        next: delete_task

payload:
  content:
    type: raw
    data: "What is the task ID you want to delete?"

key: ask_the_user_for_task_id_delete

# Step 4: RELE.AI runtime will delete the task via the backend service

Once the user replied to the question, RELE.AI runtime will make a request to the serverless handler to delete the task through the backend service with the provided id.

type: Operation

selector:
  workflow:
    - delete_task_workflow
  app: serverless_app
  app_action: delete_task_handler

next:
  selector:
    - type: operation
      data:
        workflow: delete_task_workflow
        next: send_delete_success_message

# we will use the on_error attribute
# to handle server errors and let the user
# know what happend.
on_error:
  selector:
    - type: operation
      data:
        workflow: delete_task_workflow
        next: send_error_message_to_user

payload:
  id:
    # we will grab the user's response
    # from the global redis state using the operation
    # key as a prefix to the state data
    type: redis
    rkey_type: hash_map
    data: ask_the_user_for_task_id_delete:message_data:message:body

key: delete_task

# Step 5: The bot will reply back with a success message

type: Operation

selector:
  workflow:
    - delete_task_workflow
  app: whatsapp
  app_action: send_message

output:
  operation_type: drop_session

payload:
  # we will use the vars option to customize the response message with
  # the data we got from the handler
  content:
    type: raw
    data: The task has been deleted

key: send_delete_success_message

# Writing the serverless handler

The handler will get the data from the RELE.AI's runtime, make a request to the backend service to delete the task.

const axios = require('axios')
const functions = require('firebase-functions')

exports.deleteTaskHandler = functions
	.region('{{ GOOGLE_FUNCTIONS_REGION }}')
	.https
	.onRequest(async (req, res) => {
		// make a request to the backend service
		const response = await axios.delete(`https://jsonplaceholder.typicode.com/todos/${req.query.id}`)

		// format the provided response to our needs
		res.json({})
	})

In addition to the handler we will need to provide RELE.AI with an app_action configuration to make the handler accessible through RELE.AI.

type: AppAction
selector:
  app: serverless_app
request:
  method: DELETE
  uri: /deleteTaskHandler
key: delete_task_handler

# Deployment

To apply & deploy configurations please run:

npm run deploy

# Using the integration

Below is a screenshot from WhatsApp on how to use each workflow:

serverless-screenshot

# Clean up

To delete the cloud functions and to remove the workflows from your environment, run the following command:

npm run cleanup