Docs Menu

Docs HomeAtlas App Services

Write a Serverless GitHub Contribution Tracker

On this page

  • Overview
  • Prerequisites
  • Create a New App
  • Welcome New GitHub Contributors
  • Create an Endpoint
  • Update Your Endpoint Function's Authentication Settings
  • Connect the Endpoint to a GitHub Webhook
  • Get a GitHub Access Token
  • Store the Token as a Value
  • Install the GitHub API Client
  • Write the Endpoint Logic
  • Test the Endpoint
  • Generate a Weekly Community Report
  • Specify Which Repos Should Generate Reports
  • Create a Function that Generates Reports
  • Generate And Save Reports Every Week
  • Send Report Notifications
  • Test the Report Triggers
  • What's Next?
  • Keep Building
  • Explore the Documentation
  • Give Us Feedback
  • Join the Community

Estimated time to complete: 30 minutes

In this tutorial, you will use Atlas App Services to build a serverless application that monitors GitHub repositories and tracks contributions.

The App Services App that you build will have several features:

  • Keep track of users that have contributed to your project in a MongoDB Atlas collection.

  • Leave a comment that welcomes new contributors when they open a pull request or file an issue.

  • Generate and send a weekly report that summarizes contributions to your repository.

After completing this tutorial, you will know how to do the following tasks:

  • Write and run serverless functions that handle your App's logic.

  • Automate scheduled tasks and respond to changing data with Atlas Triggers.

  • Store and access static data, like API keys, in Values.

  • Connect to an external service through an API

You need the following before you can begin this tutorial:

Also note that this tutorial doesn't use the deployment draft workflow. A deployment draft is a collection of App changes that you can deploy or discard as a single unit. Unless you disabled deployment drafts, you may need to save your changes to a draft and then deploy them manually.

First, you need to create an application in App Services.

To create the App:

  1. Open your Atlas project at cloud.mongodb.com.

  2. In the top navigation, click App Services.

  3. Click Create a New App.

  4. Name the App github-tracker-tutorial.

  5. Select your cluster to link it to the new App.

  6. Click Create App Service.

We'll connect the app to your GitHub repository using a GitHub repository webhook. Webhooks let your App know whenever some event, like a new commit or pull request, happens in your repository. Each event runs a serverless function where you can do something in response.

For this tutorial, we'll use the GitHub REST API to send a welcome message to contributors whenever they open their first pull request or issue on a repository.

Webhooks work by sending a request about the event to a URL that your App controls. In your App, you'll need to expose a custom endpoint with a unique URL to receive and handle the webhook requests.

To create the endpoint:

  1. In the left navigation menu, click HTTPS Endpoints.

  2. Click Add An Endpoint.

  3. Name the endpoint route /greetNewContributors.

  4. Leave the HTTP method set to POST.

  5. For authorization, choose Require a Secret.

  6. Enter a new secret name and click Create to create a new secret. Then, enter tutorial as the secret value. This requires all incoming requests to include the query parameter secret=tutorial in the request URL.

  7. Create a new Atlas Function for the endpoint and name it endpoints/greetNewContributors.

  8. For now, set up a basic handler that only responds to incoming calls without doing other work. Copy the following code into the Function body:

    exports = async function greetNewContributors(request, response) {
    return response
    .setStatusCode(200)
    .setBody("Successfully received a GitHub webhook event")
    }
  9. Click Save and deploy the endpoint.

  10. Under Operation Type, copy the endpoint callback URL. You'll need it to set up the GitHub webhook later.

Now that you've created the endpoint in your App, you need to change the authentication settings for the endpoint's Function so that the GitHub webhook is accepted.

To update your Function's authentication settings:

  1. In the left navigation menu, click Functions.

  2. Find your endpoint Function and select it.

  3. Click Settings.

  4. Under Authentication, change the authentication method to System.

  5. Click Save to deploy the Function.

With your endpoint Function ready to go, you need to set up a webhook in your GitHub repository that sends events to the endpoint.

To create the webhook in your repository:

  1. Open the repository's settings and select Webhooks in the left navigation menu.

  2. Add a new webhook and set the Payload URL to the URL of the endpoint that you just created. The URL will look like one of the following depending on your app's deployment model:

  3. Set the webhook content type to application/json.

  4. Set the Secret to the same secret value you defined for the endpoint: tutorial.

  5. Choose to select individual events and configure the webhook to only send events for Issues and Pull requests.

  6. Click Add webhook to save the new webhook.

To confirm that the webhook succesfully calls your endpoint, check your application logs in App Services for entries with the type Endpoint. You can get there by clicking Logs in the left navigation menu.

You can also check the webhook's request logs in GitHub under Recent Deliveries on the webhook's settings page. Each successful request has a green checkmark next to it.

A log entry in GitHub that shows the custom endpoint's response to a ping event. The response has a status code of 200 and the response body says "Successfully received a GitHub webhook event".

The webhook is now set up to send events from GitHub to your endpoint. However, to respond to events in the endpoint with the GitHub API you'll need an access token. This tutorial uses personal access tokens, but you could also set up a GitHub app and use that token instead.

To create a personal access token:

  1. Open your GitHub user settings (not your repository settings) and select Developer settings in the left navigation menu.

  2. In the left navigation menu, select Personal access tokens and then click Generate new token.

  3. Configure the token with a descriptive name and a reasonable expiration time. Since this is a tutorial, consider expiring the token after 7 days.

  4. Select the repo scope.

  5. Click Generate token.

  6. Copy the token somewhere secure where you can access it again. GitHub will never show you the token again after this point.

Back in your App, add a new Value to hold the personal access token you just generated. You'll be able to reference the Value from your endpoint without hardcoding the token into your Functions.

To create the Value:

  1. In the left navigation menu, click Values.

  2. Click Create New Value.

  3. Name the Value GitHubAccessToken.

  4. Leave the type set to Value.

  5. Paste the personal access token into the Value's input. The Value must be valid JSON, so make sure you have surrounding quotation marks.

  6. Click Save.

The endpoint will interact with GitHub's REST API to leave comments. You could write and send HTTP requests directly to the API using the built-in context.http client or an external library. However, in this tutorial we use GitHub's official Node.js library called Octokit that wraps the API. Once installed, you'll be able to import the library from any Function in your App.

To add the Octokit library to your App:

  1. In the left navigation menu, click Functions.

  2. Select the Dependencies tab.

  3. Click Add Dependency.

  4. Enter the package name: @octokit/request.

  5. Click Add.

  6. Wait for App Services to install the package. The installation should complete in a few seconds, but may take up to a minute.

Now that you have an access token and have installed Octokit, you can update the endpoint Function to actually do something when it receives events. Specifically, the Function should:

  • Parse the incoming webhook event

  • Log the contribution in MongoDB

  • Add a comment through the GitHub API

  • Send an informative response back to GitHub

To update the Function:

  1. In the left navigation menu, click Functions.

  2. Click endpoints/greetNewContributors to open the endpoint Function editor.

  3. Replace the basic Function with the following code:

    functions/endpoints/greetNewContributors.js
    exports = async function greetNewContributors(request, response) {
    // Parse the webhook event from the incoming request.
    const event = JSON.parse(request.body.text());
    // Don't do anything unless this is a new issue or pull request
    if (event.action !== "opened") {
    return response.setStatusCode(200);
    }
    // Get data from the GitHub webhook event.
    // Based on the webhook configuration the event will be one of the following:
    // - issues: https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#issues
    // - pull_request: https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#pull_request
    const sender = event.sender;
    const repo = event.repository;
    const contribution = event.issue || event.pull_request;
    const contribution_url = event.issue
    ? event.issue.url
    : event.pull_request.issue_url;
    const issue_number = contribution.number;
    // Record this contribution in the user's contributor document.
    // If this user hasn't contributed to the repo before, create a document for them.
    const atlas = context.services.get("mongodb-atlas");
    const contributors = atlas.db("community").collection("contributors");
    const contributor = await contributors.findOneAndUpdate(
    // Look up the user by their GitHub login
    { login: sender.login },
    // Add this issue or pull request to their list of contributions
    {
    $push: {
    contributions: {
    date: new Date(),
    type: event.issue ? "issue" : "pull_request",
    url: contribution_url,
    },
    },
    },
    // If they haven't contributed before, add them to the database
    { upsert: true, returnNewDocument: true }
    );
    // Send a welcome message to first time contributors on their issue or pull request
    const isFirstTimeContributor = contributor.contributions.length === 1;
    if (isFirstTimeContributor) {
    const octokit = require("@octokit/request");
    await octokit.request(
    "POST /repos/{owner}/{repo}/issues/{issue_number}/comments",
    {
    headers: {
    authorization: `token ${context.values.get("GitHubAccessToken")}`,
    },
    owner: repo.owner.login,
    repo: repo.name,
    issue_number: issue_number,
    body: `Hi there ${sender.login} 👋 Thanks for your first contribution!`,
    }
    );
    }
    // Configure the HTTP response sent back to GitHub
    return response
    .setStatusCode(200)
    .setHeader("Content-Type", "application/json")
    .setBody(
    isFirstTimeContributor
    ? `This is ${sender.login}'s first contribution!`
    : `${sender.login} has contributed before.`
    );
    };
  4. Click Save and deploy the endpoint.

The welcome message endpoint should now be fully set up. To test that it works correctly, open a new issue or pull request on the repository. The endpoint will add a new comment to the thread the first time you do this but won't add a welcome message on subsequent attempts.

An example of the welcome comment in GitHub added by the endpoint.

GitHub logs repository webhook requests, so you can also check the log entry in GitHub to confirm that everything is working properly. Each request log includes a response message from the endpoint.

A log entry in GitHub that shows the custom endpoint's response for a first-time contributor

Tip

If you want to reset the test, delete the document with your GitHub username from community.contributions. This lets the App "forget" that you've contributed before and it will welcome you on your next contribution.

Your App is connected to GitHub, stores information about contributions, and welcomes new contributors. Now we'll extend it to automatically analyze and generate reports for your repository.

Your App needs a way to know which repositories it should generate reports for every week. For this tutorial, we'll hardcode the list in a Value.

Create a new Value called GitHubProjects that contains an array of objects. Each object specifies the owner and repo name of a GitHub repository. Make sure to include an entry for your repository.

values/projects.json
[
{ "owner": "<GitHub Username>", "repo": "<Repository Name>" }
]

A report is a document that summarizes the contributions to a repository for some time period. We'll use a Function to create on-demand reports for a repo.

Create a new Function named generateCommunityReport and add the following code:

functions/generateCommunityReport.js
exports = async function generateCommunityReport({ owner, repo, startDate }) {
// Look up issues and pull requests that had activity
const octokit = require("@octokit/request");
const { data: issuesWithActivity } = await octokit.request(
"GET /repos/{owner}/{repo}/issues",
{
headers: {
authorization: `token ${context.values.get("GitHubAccessToken")}`,
},
owner: owner,
repo: repo,
since: startDate,
}
);
// Look up users that contributed to the repo
const atlas = context.services.get("mongodb-atlas");
const contributors = atlas.db("community").collection("contributors");
const allContributors = await contributors
.find({
contributions: {
$elemMatch: {
date: { $gt: new Date(startDate) },
owner: owner,
repo: repo,
},
},
})
.toArray();
// Keep track of users who made their first contribution
const newContributors = allContributors.filter((c) => {
new Date(c.contributions[0].date) > new Date(startDate);
});
// Return a report with the data
return {
owner,
repo,
startDate,
issuesWithActivity,
allContributors,
newContributors,
};
};

The Function you just created creates a report for a repository on-demand. However, at this point nothing calls the Function and the generated reports are not saved anywhere. To actually use it, we'll create a scheduled Atlas Trigger that calls the Function once every week and saves the generated reports in your linked cluster.

To create the Trigger:

  1. In the left navigation menu, click Triggers.

  2. Click Add a Trigger.

  3. Choose Scheduled for the Trigger type.

  4. Name the Trigger generateAndSaveCommunityReports

  5. Choose the Advanced schedule type

  6. Enter the following cron schedule to run once a week on Monday at 5 AM UTC:

    0 5 * * 1
  7. Create a new Function for the Trigger and name it triggers/generateAndSaveCommunityReports.

  8. Click Add Dependency and install moment, which we use to work with dates in the Function.

  9. Copy the following code into the Function body:

    functions/triggers/generateAndSaveCommunityReports.js
    exports = async function generateAndSaveCommunityReports() {
    const projects = context.values.get("GitHubProjects");
    const lastMonday = getLastMonday(); // e.g. "2022-02-21T05:00:00.000Z"
    // Generate a report for every tracked repo
    const reportsForLastWeek = await Promise.all(
    // Call the `generateCommunityReport` function for each project
    projects.map(async (project) => {
    return context.functions.execute("generateCommunityReport", {
    owner: project.owner,
    repo: project.repo,
    startDate: lastMonday,
    });
    })
    );
    // Save the generated reports in Atlas
    const atlas = context.services.get("mongodb-atlas");
    const reports = atlas.db("community").collection("reports");
    return await reports.insertMany(reportsForLastWeek);
    };
    // Get an ISO string for last Monday at 5AM UTC
    function getLastMonday() {
    const moment = require("moment");
    return moment(new Date().setUTCHours(5, 0, 0, 0))
    .utc()
    .day(1 - 7)
    .toISOString();
    }
  10. Click Save.

  11. Update your new Function's authentication settings to match the new Endpoint's Function from earlier in this tutorial.

Your App will now automatically generate and save reports every week. However, the reports won't be very useful if nobody sees them. We'll create a database Trigger that listens for new reports and creates a formatted message that you can send to end users.

To set up the messages:

  1. In the left navigation menu, click Triggers.

  2. Click Add a Trigger.

  3. Leave the Trigger type set to Database.

  4. Name the Trigger sendCommunityReport.

  5. Add the Trigger to the community.reports collection and listen for Insert events.

  6. Enable Full Document to include each new report document in the change event passed to the Trigger Function.

  7. Create a new Function for the Trigger and name it triggers/sendCommunityReport.

  8. Copy the following code into the Function body:

    functions/triggers/sendCommunityReport.js
    exports = async function sendCommunityReport(changeEvent) {
    // Pull out the report document from the database change event
    const report = changeEvent.fullDocument;
    // Format values from the report to include in the message
    const projectName = `${report.owner}/${report.repo}`;
    const moment = require("moment");
    const formattedDate = moment(report.startDate).utc().format("MMMM Do, YYYY");
    const numIssuesWithActivity = report.issuesWithActivity.length;
    const numContributors = report.allContributors.length;
    const numNewContributors = report.newContributors.length;
    // Create a message string that describes the data in the report
    const message = [
    `# Community contributions to ${projectName} since ${formattedDate}`,
    `Last week, there was activity on ${numIssuesWithActivity} issues and pull requests.`,
    `We had ${numContributors} people contribute, including ${numNewContributors} first time contributors.`,
    ].join("\n");
    // For this tutorial we'll just log the message, but you could use a
    // service to send it as an email or push notification instead.
    console.log(message);
    };
  9. Click Save and deploy the Trigger.

Your App is set up to automatically generate, save, and send reports every week. To make sure that everything works, you can run this report flow manually.

Open the Function editor for your scheduled Trigger, triggers/generateAndSaveCommunityReports, and then click the Run button. This should generate and save on-demand reports for every repo that you listed in the GitHubProjects Value.

To confirm:

  1. Check community.reports for the new report documents.

  2. Check your App's database Trigger logs to find the formatted message for each report

Congratulations! You've succesfully set up a serverless GitHub contribution tracker and reached the end of this tutorial.

If you want to keep developing, you can try to add some new features to the tracker. For example, you could:

  • Update the endpoint to handle more webhook event types like issue_comment or pull_request_review.

  • Update the weekly reports to include more information from the GitHub API.

  • Connect to an external service like Twilio or SendGrid to actually send the report via email or SMS instead of just logging it.

App Services includes many services that can power your App. Check out the rest of the documentation to learn more about these services and how you can use them.

We're always working to improve our docs and tutorials. If you have a suggestion or had issues with this tutorial, click Give Feedback at the bottom of this page to rate the tutorial and send us a comment.

The official MongoDB Community Forums are a great place to meet other developers, ask and answer questions, and stay up-to-date with the latest App Services features and releases.

←  SwiftUI iOS TutorialBuild Reverse Search Into Your Application →