Building a Serverless Joke Bot for Twitter!

This was my entry for the #NoServerNovember contest that is being run by Serverless!

It’s almost 2019 and the whole world is going gaga about Serverless! To those who are just waking up into the post Serverless era, we’re not talking about magic mushrooms that serve your app, literally without any servers. No, that time hasn’t come yet.

The word Serverless doesn’t actually mean there aren’t any servers. What it means is that you don’t have to worry about provisioning, monitoring, maintaining and scaling those darn servers. Instead, you focus on your code and your app’s features and that’s about it. Well, almost.

But, for us, the broader picture here is that instead of imagineering a monolithic application and then losing 90% of your hair in architecting and realizing your server infrastructure, you actually break down your app into functions and for most parts, forget about servers altogether.

Services such as AWS’ Lambda, Google’s Cloud Functions and Microsoft’s Azure Functions offer a streamlined, developer-friendly way to deploy these ‘serverless’ functions on the cloud. When invoked using an ‘event’, such as a call to an endpoint, the function individually runs and you’re typically billed only for that specific duration. This effectively optimises your operational expenditure on your infrastructure.

As a Node.js developer and instructor, I absolutely love ‘serverless’ functions for their simplicity and power. From concept to the cloud, you can literally get your app, up and running in less than 10 minutes.

And what makes this process even more fruitful and thoroughly enjoyable is the outstanding Serverless framework. This framework is essentially a set of tools built as a Node.js CLI that allows you to architect and deploy your serverless apps with ease. So, when Serverless announced the #NoServerNovember series of challenges, I leapt at the opportunity and built together a Twitter bot that posts cheesy Dad jokes every 6 hours. This was one of the suggested ideas for the challenge.

From reading the rules of the contest to deploying the bot, took me under 20 minutes. That is the power of Serverless! So, let me walk you through the steps here, so you can build a joke bot of your own and experience the sheer fun and power that this framework has to offer.

Step 1 — Creating a Twitter Account

Behold Papa Schrute, the owner of Schrute Farms and a Dad, despite his claims to be a bilden kinder. For fans of the American TV show ‘The Office’, the character of Dwight Schrute stands out from the rest as perhaps the most eccentric, self-loving crack who gets into situations purely because of his own doing.

In our world, he’s a grumpy old Dad who’s retired from Dunder Mifflin and cracks jokes every six hours for no apparent reason.

Go follow @PapaSchrute on Twitter for cheesy jokes every six hours!

Now, for our serverless function to be able to post to this Twitter account, we need to create a Twitter app. This can be done by first going to developer.twitter.com and applying for a Developer account.

From your developer dashboard on developer.twitter.com, click on ‘Create an app’

Once Twitter approves and enables your Developer account, you can click on the ‘Create an app’ button to get started with the process. On the form that appears, fill in the App Name, Application Description, Website URL (This could be your own blog/site URL for now) and finally describe how this account will be used in the ‘Tell us how this app will be used’ box. For instance, ‘This app will be used to post Dad jokes as a part of a contest run by Serverless.com. The app will be used by a serverless script to post a dad joke every 6 hours’.

Once your app is created, head straight to the ‘Keys and Tokens’ section and take a note of the Consumer API Key and API Secret Key. Additionally hit the ‘Create’ button for the Access Token and Access token secret keys and take a note of those too. This is all we need from Twitter to post to our account.

Step 2 — Setting up our Serverless bot

Before we proceed here, I’m going to ask you to complete the following steps:

  • Signup for a free account on AWS by going to aws.amazon.com.
  • I’m going to assume you have Node.js installed already and that you’re familiar with its use. Install the Serverless framework CLI by typing 
    npm i -g serverless
  • Then watch this video for setting up your AWS account to work with the Serverless framework → https://www.youtube.com/watch?v=HSd9uYj2LJA Note: This video is a bit old and the UI that you see may not match up to what you get on AWS. But most of the stuff is easily accessible, so you shouldn’t face any problems.

Alright, with the above steps complete, we’re ready to build our bot. First up, we’ll use the aws-nodejs template to bootstrap our serverless project.

$ sls create --template aws-nodejs --path papa-schrute
$ cd papa-schrute
$ npm init -y

Now, the Serverless framework provides you with a serverless.yml configuration file which is the heart and soul of every Serverless project. When we deploy our app, the configuration is translated into a series of steps that involve uploading the app to an AWS S3 bucket and constructing an AWS CloudFormation template which is quite literally a schema for provisioning and deploying resources and permissions for your app on the AWS infrastructure.

AWS then uses the CloudFormation template that it receives to provision and set up a Lambda function for you.

While CloudFormation makes it convenient to set up and wire a ton of services and resources on AWS by simply writing a configuration file, the serverless.yml file makes this process a walk in the park. At this time, your serverless.yml file should look like this (after removing all the comments) :

service: papa-schrute
provider:
name: aws
runtime: nodejs8.10
functions:
hello:
handler: handler.hello

In the simplest of words, we’re creating a service called ‘papa-schrute’ which will be deployed on AWS and will use Node.js 8.10 (This cannot be arbitrarily set and must be a version supported by AWS Lambda). Additionally, we have a function called hello in a file called handler.js, also present in the same folder. What we don’t have at this point is an event.

In the Lambda world, an event is a condition which causes your function to get up and run. Otherwise, it sleeps like a well-fed cat. This is good because you’re not billed until the cat is up and meowing!

The handler.js file is where your function resides. We’re using Node.js and consequently JavaScript to write our bot, but you can use Python, Go and several other languages for writing your functions. At this point, our handler.js file looks like this:

'use strict';
module.exports.hello = async (event, context) => {
return {
statusCode: 200,
body: JSON.stringify({
message: 'Go Serverless v1.0! Your function executed successfully!',
input: event,
}),
};
    // Use this code if you don't use the http event with the LAMBDA-PROXY integration
// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};

You can see a function being exported from this file named ‘hello’ which returns an object with a couple of things. In our case, we need our function to fetch a cheesy Dad joke from https://icanhazdadjoke.com and then post it to our Twitter account. That’s it. We don’t need our function to return any data whatsoever.

So, we’ll first begin by modifying out serverless.yml file so it looks like this :

service: papa-schrute
provider:
name: aws
runtime: nodejs8.10
stage: production
region: us-west-2
environment:
consumer_api_key: xxxxxxxxx
consumer_api_secret: xxxxxxxxx
access_token: xxxxxxxxx
access_token_secret: xxxxxxxxx
functions:
papaschrute:
handler: handler.papaSchrute

You’ll notice here I’ve renamed my service as ‘papa-schrute’ and I’ve also added stage and region attributes under the provider block. The region here should be set as per your AWS region.

Also notice a new block called environment where I’ve declared a bunch of environment variables, each for our secrets and keys that we got from Twitter. Set these up to their respective values that you got from Twitter in Step 1. Once declared here, they will be set up on AWS and will be set to the values during deployment. These can then be accessed using process.env.variableName in JavaScript. No manual setup needed!

Next, we’ll begin by installing a package called ‘twitter’ and another one called ‘axios’, using npm. As you may have guessed, axios will be used to query & fetch the icanhazdadjoke API for a new random joke and once we have that, we’ll use the twitter package to post the joke to our Twitter account.

$ npm i twitter axios

Then, I’ll clear out my handler.js file and we’ll begin by declaring our main function called papaSchrute as follows:

'use strict';
module.exports.papaSchrute = async event => {
try {
// Core logic goes here
} catch (e) {
console.log(`Error: ${e}`);
}
}

Since we’re using async/await, we’ll use a try/catch block to capture and log any errors to the console.

We’ll then use Axios to fetch a joke as shown below:

'use strict';
const axios = require('axios');
module.exports.papaSchrute = async event => {
try {
const getJoke = await axios({
url: 'https://icanhazdadjoke.com/',
headers: {
'Accept': 'application/json',
'User-Agent': 'Papa Schrute
(https://twitter.com/PapaSchrute)'
}
});
    } catch (e) {
console.log(`Error: ${e}`);
}
}

To get the joke as a JSON object from the icanhazdadjoke.com’s API, we need to send the ‘Accept’ header as shown above and the ‘User-Agent’ header is something that they ask all users to send as a way for them to know who’s using the API.

Next, I’ll create a twitter module ( twitter/index.js ) which will essentially bring in and initialize the Twitter package using the API keys and tokens as shown below. This instance exposes a set of functions that allow us to interact with Twitter and this interface is what we’re exporting out from this module:

const Twitter = require('twitter');
module.exports = new Twitter({
consumer_key: process.env.consumer_api_key,
consumer_secret: process.env.consumer_api_secret,
access_token_key: process.env.access_token,
access_token_secret: process.env.access_token_secret
});

Now, for posting the joke to Twitter, we’ll add the following code to our handler:

'use strict';
const axios = require('axios');
const tw = require('./twitter');
module.exports.papaSchrute = async event => {
try {
const getJoke = await axios({
url: 'https://icanhazdadjoke.com/',
headers: {
'Accept': 'application/json',
'User-Agent': 'Papa Schrute
(https://twitter.com/PapaSchrute)'
}
});
        await tw.post('statuses/update', {
status: getJoke.data.joke
});
    } catch (e) {
console.log(`Error: ${e}`);
}
}

The object that we get from our API call looks like this:

{
"id": "3w5wAIRZTnb",
"joke": "A police officer caught two kids playing with a
firework and a car battery. He charged one and let the
other one off.",
"status": 200
}

So, we’re extracting the joke and posting it as a status update using our Twitter module. And that’s it for our code!

All we’re left to do now is schedule our function to run every six hours to fetch and post a new joke.

Step 3 — Scheduling our Lambda to run every six hours

If you’re conjuring up images of setIntervals or some other weird way to schedule your script to run every six hours, then you’re in for a treat. The magic is in the Lambda service itself and our Serverless framework, as always, makes this task a breeze. All you need to do is modify your serverless.yaml file to add an event that will fire up your lambda function. :

service: papa-schrute
provider:
name: aws
runtime: nodejs8.10
stage: production
region: us-west-2
environment:
consumer_api_key: xxxxxxxxx
consumer_api_secret: xxxxxxxxx
access_token: xxxxxxxxx
access_token_secret: xxxxxxxxx
functions:
papaschrute:
handler: handler.papaSchrute
events:
- schedule: rate(6 hours)

The schedule expression sets up a recurring trigger that runs the function every six hours. This trigger is precise up to 1 minute.

We’re done with our code. Let’s deploy it!

Step 4 — Deploying our function to AWS Lambda

Nothing could be easier than deploying a function to AWS Lambda using the Serverless framework. All you need to do is execute the following statement and sit back and relax.

$ sls deploy

This will take a couple of minutes as it packages your app, creates a CloudFormation template, uploads the archive to an S3 bucket before deploying everything.

Now, every six hours, your bot will wake up, fetch a new cheesy joke and post it to your Twitter account!

I hope you enjoyed building this bot with me. You can download the completed bot from https://github.com/sachinbhatnagar/PapaSchrute and be sure to checkout @PapaSchrute on Twitter for the cheesiest of jokes, powered by ICanHazDadJoke.

Feel free to connect with me on Twitter: Sachin B.

Let’s discuss a Serverless future!

#NoServerNovember #Serverless #AWSServerless #AWSLambda #CloudFormation #Bots #Twitter