Automate Your Replies to GitHub Issues with Serverless
Tags: javascript, serverless, webdev, node
Published: 2019-12-10 05:05:16

---
title: Automate Your Replies to GitHub Issues with Serverless
published: true
description: 25 Days of Serverless Challenge 9 - The holiday season is a time for smiling more and taking the time to say thank you to those around us. Say thank you to everyone who takes the time to create an issue in your Github repository, using serverless technology.
cover_image:
tags: javascript, serverless, webdev, node
---

Have you ever contributed to an OSS project on GitHub, perhaps creating an issue, and instantly received a reply? Wouldn't it be nice if that reply to your issue thanked you? This article walks through one way you can automate creating a holiday themed "thank you" replies to all issues created in one or more of your GitHub repositories. 

Here is a link to the [source code](https://github.com/johnpapa/serverless-thank-you?WT.mc_id=25daysofserverless-github-jopapa) in case you want a reference.

![diagram](https://media.giphy.com/media/U84aCOOw6yb4AKj85i/giphy.gif)

>This article is part of [#25DaysOfServerless](https://25daysofserverless.com). New challenges will be published every day from Microsoft Cloud Advocates throughout the month of December. Find out more about how Microsoft Azure enables your [Serverless functions](https://docs.microsoft.com/azure/azure-functions/?WT.mc_id=25days_devto-blog-cxa). Have an idea or a solution? <a class="twitter-share-button" href="https://twitter.com/intent/tweet?text=I'm joining the @azureadvocates %2325DaysOfServerless challenge!! Learn more at https://aka.ms/25daysofserverless or see solutions at https://dev.to/search?q=25DaysOfServerless! Join me!" data-url="https://aka.ms/25daysofserverless' " data-hashtags="25DaysOfServerless"> Share your thoughts on Twitter!
</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

One possible solution to make this a "Happy Holiday" story is to use Azure Functions to listen to GitHub via webhooks and then respond by creating a comment on the issue. Let's explore what you'll need first and then walk through the steps you can use to try this for yourself.

## The Approach

Okay, let's step back a moment and examine what you're about to do. When an issue is created in GitHub, you want to thank the issue's creator for reporting the issue.

You need to know when that issue is created, so you'll ask GitHub to alert us when this happens. GitHub alerts us via a webhook. The webhook is triggered by the issue being created and then makes an HTTP request to a URL, which you can define, passing along the payload of the issue that was created. So where does that GitHub webhook HTTP request go? It will call a function that you create with Azure Functions.

You'll create a function that accepts the webhooks request and inspects its payload. You'll parse out the creator and other details in the payload and format a response to the issue. Now that you have the data you need to create the comment on the issue, you need a way to talk back to the same issue and create the comment. Then you'll call the GitHub API to create a comment on the issue, using a token that allows the function to make the call.

You're communicating between a GitHub repository and Azure Functions, so you'll need to make sure you have the right permissions to make these HTTP requests between them. This is pretty straightforward, as you'll see through the solution below.

Here is a summary of what you're going to do:

- Create a webhook on one of our GitHub repositories that fires when an issue is created
- Create an Azure Function that accepts HTTP request that the webhook makes
- Generate a personal access token that the function can use when talking to the GitHub API
- Make an HTTP request from the function to the GitHub API to create the comment

Now that you've laid out what you going to do, let's go put it all together.

## Resources and Tools 🚀

Starting with the right tools can make all the difference in the world. Let's start by using [Visual Studio Code](https://code.visualstudio.com/?WT.mc_id=25daysofserverless-github-jopapa) and the [Azure Functions extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions&WT.mc_id=25daysofserverless-github-jopapa). This combination is, in my opinion, the best way to get started and efficiently create Azure Functions.

> If you do not have an Azure account, you'll want to sign up for a [free trial of Azure from this link here](https://azure.microsoft.com/free?WT.mc_id=25daysofserverless-github-jopapa).

### Install the Right Tools

- **[Visual Studio Code](https://code.visualstudio.com/?WT.mc_id=25daysofserverless-github-jopapa)**
- **[Azure Functions Extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions&WT.mc_id=25daysofserverless-github-jopapa)**
- **[Azure Functions Core Tools](https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local?WT.mc_id=25daysofserverless-github-jopapa)**
- **[Free Azure Trial](https://azure.microsoft.com/free?WT.mc_id=25daysofserverless-github-jopapa)**

## Create the function

You'll start by creating the Azure Function app in Visual Studio Code.

### Create the Azure Function project

1. Create a new folder and name it `thank-you`
1. Open VS Code and open the `thank-you` folder
1. Press the _F1_ key to open the Command Palette.
1. Enter and select _Azure Functions: Create New Project_
1. If prompted to create the project in the `thank-you` folder, press _Yes_
1. Select _TypeScript_ for the new project's language
1. Select _HTTP Trigger_ as the template for the function
1. Name the function _SayThankYou_
1. Choose _Anonymous_ as the authorization level

VS Code will now create the function project _thank-you_ and your _SayThankYou_ f

### Test the Function

Let's install the dependencies for the project

1. Open the Command Palette by pressing _F1_
1. Enter and select _Terminal: Create new integrated terminal_
1. Enter `npm install` in the terminal, to install the dependencies for the project
1. Press _F5_ to run the function
1. Go to a browser and enter <http://localhost:7071/api/saythankyou?name=Colleen> into the address bar

The function will respond to the browser by saying _Hello Colleen_

To stop the function app from running:

1. Open the Command Palette by pressing _F1_
1. Enter and select _Debug: Disconnect_

### The Trigger is a webhook

You need to tell the function that it is triggered by a GitHub webhook.

1. Open the `function.json` file
2. modify the JSON to add `", webHookType": "github"` after the `"methods": ["get", "post"],`

### What's the Payload?

You need to know the shape of the payload that the function will receive from the webhook. You can find the payload shape for the `IssuesEvent` in the [GitHub documentation here](https://developer.github.com/v3/activity/events/types/#issuesevent).

You'll reference this payload information as you read the payload in the function.

Before you proceed, let's replace the code in the function with the following starter code.

```typescript
import { AzureFunction, Context, HttpRequest } from '@azure/functions';

const httpTrigger: AzureFunction = async function(context: Context, req: HttpRequest): Promise<void> {
  const { body: payload } = req;

  let body = 'Nothing to see here';

  context.res = { status: 200, body };
};
```

The function gathers the payload from the request, and then always responds with the same body and status. You'll refactor this to read the payload from the webhook next.

### Reading the Payload

You want to create a message that thanks the issue creator. Some information you may want to gather are the issue creator's username, the issue number, the repository name, the owner of the repository, and the action that occurred to trigger the webhook.

Using the `IssuesEvent` [GitHub documentation here](https://developer.github.com/v3/activity/events/types/#issuesevent) as a reference, you can write the following code to gather these values from the payload.

```typescript
// Gather the data from the payload from the webhook
const repo = payload.repository.name;
const owner = payload.repository.owner.login;
const issue_number = payload.issue.number;
const user = payload.issue.user.login;
const action = payload.action;
```

Now your function will look like the following code.

```typescript
import { AzureFunction, Context, HttpRequest } from '@azure/functions';

const httpTrigger: AzureFunction = async function(context: Context, req: HttpRequest): Promise<void> {
  const { body: payload } = req;

  // Gather the data from the payload from the webhook
  const repo = payload.repository.name;
  const owner = payload.repository.owner.login;
  const issue_number = payload.issue.number;
  const user = payload.issue.user.login;
  const action = payload.action;

  let body = 'Nothing to see here';

  context.res = { status: 200, body };
};
```

### Crafting the Response

Now that you can read the payload, you want to craft the message that you'll use to create the comment on the issue. You only want to write the comment if the event that triggered the webhook was creating an issue. You will know it came from an issue event once you create the webhook. You don't want to respond to editing or deleting of the issue, so well look at the `action` to see if the issue event was caused by it being opened.

The following code will create a message only if the issue was opened (created).

```typescript
let body = 'Nothing to see here';
if (action === 'opened') {
  body = `Thank you @${user} for creating this issue!\n\nHave a Happy Holiday season!`;
  context.log(body);
}
```

Your function should now look like the following code.

```typescript
import { AzureFunction, Context, HttpRequest } from '@azure/functions';

const httpTrigger: AzureFunction = async function(context: Context, req: HttpRequest): Promise<void> {
  const { body: payload } = req;

  const repo = payload.repository.name;
  const owner = payload.repository.owner.login;
  const issue_number = payload.issue.number;
  const user = payload.issue.user.login;
  const action = payload.action;

  let body = 'Nothing to see here';
  if (action === 'opened') {
    body = `Thank you @${user} for creating this issue!\n\nHave a Happy Holiday season!`;
    context.log(body);
  }

  context.res = { status: 200, body };
};
```

### Generate a Personal Access Token

Before we start writing the code to create the comment, we'll need to generate a personal access token from GitHub so we can let our function talk to GitHub.

> You only want to token to have access to public repositories, so be sure to only select the _public_repo_ scope.

1. Follow [these steps to generate a personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line?WT.mc_id=25daysofserverless-github-jopapa). Only select _public_repo_ when you are asked to select the scopes for the token.
1. Copy the token to your clipboard

The token is a secret and should not be pasted into our code or stored in a repository. Azure Functions allows for secrets and environment variables to be set in the `local.settings.json` file. This file is in the `.gitignore` file by default, so it only lives on your local computer. Next you will add a setting to this file for your token.

> The `local.settings.json` file is just for when you run the function locally on your computer. When it is time to push this function to Azure, you will see how to push these settings, as well.

1. Open the `local.settings.json` file in your function project.
1. Create a key in the `Values` section named `githubKey`
1. Paste the token as the value

Your `local.settings.json` should look like the following code, except with your token.

```json
{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "githubKey": "YOUR_TOKEN_GOES_HERE"
  }
}
```

### Creating the Comment in the GitHub repository

Now that you have the token and the message that you want to write to a new comment in the repository, you need to tell GitHub to create a comment on the issue. There are a few ways to do this. Some common approaches are to either make a HTTP request directly to the GitHub API using an HTTP library like _axios_, or you can use a library that abstracts and simplifies the HTTP request. Let's choose the latter.

The library [Octokit/rest.js](https://github.com/octokit/rest.js) is a GitHub REST API client for JavaScript. It exposes an API that will make it easy to create the comment. Let's install _@octokit/rest_ next.

1. Open a terminal in your project folder
1. Run `npm install @octokit/rest`

You will want to import `Octokit` and `IssuesCreateCommentParams`, from the library.

```typescript
import * as Octokit from '@octokit/rest';
import { IssuesCreateCommentParams } from '@octokit/rest';
```

Create the comment by crafting an object of type `IssuesCreateCommentParams`.

```typescript
const comment: IssuesCreateCommentParams = {
  repo,
  owner,
  issue_number,
  body
};
```

Now it is time to use the library to create the comment. The important API to create the comment is the asynchronous function `octokit.issues.createComment()`. But before you can call it you need to pass a personal access token from your function to GitHub's API, via Octokit. You can retrieve the token from the `local.settings.json` file by referencing `process.env.githubKey`.

We can write the following function to grab the personal access token, pass it to Octokit, and create the comment.

```typescript
async function createComment(comment: IssuesCreateCommentParams) {
  const auth = process.env.githubKey;
  const octokit = new Octokit({ auth });
  const response = await octokit.issues.createComment(comment);
  return response;
}
```

Now we can call that function right after we craft the comment.

```typescript
if (payload.action === 'opened') {
  body = `Thank you @${user} for creating this issue!\n\nHave a Happy Holiday season!`;
  const comment: IssuesCreateCommentParams = {
    repo,
    owner,
    issue_number,
    body
  };
  await createComment(comment);
}
```

### Your final function

Your function code should is now complete and should look like the following code.

```typescript
import { AzureFunction, Context, HttpRequest } from '@azure/functions';
import * as Octokit from '@octokit/rest';
import { IssuesCreateCommentParams } from '@octokit/rest';

const httpTrigger: AzureFunction = async function(context: Context, req: HttpRequest): Promise<void> {
  const { body: payload } = req;

  const repo = payload.repository.name;
  const owner = payload.repository.owner.login;
  const issue_number = payload.issue.number;
  const user = payload.issue.user.login;
  const action = payload.action;

  let body = 'Nothing to see here';

  if (action === 'opened') {
    body = `Thank you @${user} for creating this issue!\n\nHave a Happy Holiday season!`;
    const comment: IssuesCreateCommentParams = {
      repo,
      owner,
      issue_number,
      body
    };
    await createComment(comment);
  }

  context.res = { status: 200, body };
};

async function createComment(comment: IssuesCreateCommentParams) {
  const auth = process.env.githubKey;
  const octokit = new Octokit({ auth });
  const response = await octokit.issues.createComment(comment);
  return response;
}

export default httpTrigger;
```

Your function is now complete!

## Push Your Function to Azure

Next you want to push your function to Azure. You'll need an Azure account first. If you do not have one, you can [create an Azure free trial here](https://azure.microsoft.com/free?WT.mc_id=25daysofserverless-github-jopapa).

1. Open the Command Palette _F1_
1. Enter and select _Azure Functions: Create Function App in Azure_
1. Enter a globally unique identifier (a name)
1. If prompted, select an OS
1. Select _Node.js 10.x_
1. Select the region to create your function app

You are now creating your function in Azure. VS Code will alert you when it is ready.

Once your app has been created in Azure, you must push your settings in your `local.settings.json` file to the app in Azure.

1. Open the Command Palette _F1_
1. Enter and select _Azure Functions: Upload Local Settings_
1. Select your Function app, when prompted

You just created your function and pushed it to Azure. The next step is to create the webhook for one of your GitHub repositories.

## Setting up a webhook

You want to set up a webhook for one of your repositories that will trigger each time an issue is created in that repository.

There are two steps in setting up this webhook. The first step is to tell the webhook what events should trigger it. The second step is to tell the webhook which URL it should post the HTTP request to. Let's walk through setting up your webhook.

### Create the webhook

Let's create the webhook in one of your existing repositories.

1. Using your web browser, sign in to your [GitHub account](https://www.github.com/).
1. Navigate to one of your repositories.
1. Select the _Settings_ tab.
1. Select _webhooks_ from the menu on the left.
1. Press the _Add webhook_ button to create a new webhook for this repository
1. Set the _content type_ to _applicaiton/json_

Next you'll need the URL to your function app. The URL can be found in VS Code.

1. Go to the Azure Function extension
1. Expand your Function app and the _Functions_ node
1. Right click on your \*SayThankYou& function
1. Select _Copy Function Url_
1. Go back to your browser where you are setting up the webhook
1. Paste your Url into the _Payload URL_ field
1. Select the _individual events_ checkbox
1. Select the _Issues_ checkbox
1. Save the webhook

Now your webhook is ready to trigger the calls whenever something happens to an Issue. The webhook will post the payload to your function, your function will read the payload, craft a message, and then use the GitHub API to create a comment on the issue!

## Try it

All that is left to do is to see if it works. You should go to your repository and create and issue. The webhook will trigger an HTTP request and pass a payload to your function. Your function will write a new comment back to your issue and you should see it momentarily.

Success!

## Show me the Code

You can try this all from scratch by following these steps. You can also [skip straight to the code solution here](https://github.com/johnpapa/serverless-thank-you?WT.mc_id=25daysofserverless-github-jopapa).

If you get stuck, please open an issue in the repo.

## Next Steps 🏃

Learn more about serverless with Free Training!

- ✅ **[Serverless Free Courses](https://docs.microsoft.com/learn/browse/?term=azure%20functions&WT.mc_id=25daysofserverless-github-jopapa)**

## Resources

I recommend these resources as they are great at helping explain all of the key elements.

- **[Azure Functions docs - Webhooks](https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook?tabs=csharp#webhooks&WT.mc_id=25daysofserverless-github-jopapa)**

- **[GitHub - Create a Personal Access Token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line?WT.mc_id=25daysofserverless-github-jopapa)**
- **[GitHub - Webhook documentation](https://developer.github.com/webhooks?WT.mc_id=25daysofserverless-github-jopapa)**
- **[GitHub API - IssuesEvent Reference](https://developer.github.com/v3/activity/events/types/#issuesevent?WT.mc_id=25daysofserverless-github-jopapa)**
- **[Octokit - GitHub REST API client for JavaScript](https://github.com/octokit/rest.js)** and **[Docs](https://octokit.github.io/rest.js/)**
- **[Learn - Monitor GitHub events by using a webhook with Azure Functions](https://docs.microsoft.com/en-us/learn/modules/monitor-github-events-with-a-function-triggered-by-a-webhook?WT.mc_id=25daysofserverless-github-jopapa)**

## Additional Resources ⭐️

Some additional awesome serverless resources, in general, are as follows.

- ✅ **[Azure Functions documentation](https://docs.microsoft.com/azure/azure-functions/?WT.mc_id=25daysofserverless-github-jopapa)**
- ✅ **[Azure SDK for JavaScript Documentation](https://docs.microsoft.com/azure/javascript/?WT.mc_id=25daysofserverless-github-jopapa)**
- ✅ **[Create your first function using Visual Studio Code](https://docs.microsoft.com/azure/azure-functions/functions-create-first-function-vs-code?WT.mc_id=25daysofserverless-github-jopapa)**
- ✅ **[Free E-Book - Azure Serverless Computing Cookbook, Second Edition](https://azure.microsoft.com/resources/azure-serverless-computing-cookbook/?WT.mc_id=25daysofserverless-github-jopapa)**

---

Want to submit your solution to this challenge? Build a solution locally and then [submit an issue](https://github.com/microsoft/25-days-of-serverless/issues/new?assignees=&labels=challenge-submission&template=challenge-solution-submission.md&title=%5BCHALLENGE+SUBMISSION%5D+). If your solution doesn't involve code you can record a short video and submit it as a link in the issue desccription. Make sure to tell us which challenge the solution is for. We're excited to see what you build! Do you have comments or questions? Add them to the comments area below.

<hr/>
Watch for surprises all during December as we celebrate 25 Days of Serverless. Stay tuned here on dev.to as we feature challenges and solutions! Sign up for a [free account on Azure](https://azure.microsoft.com/free/?WT.mc_id=25days_devto-blog-cxa) to get ready for the challenges!