Persistence

Generally speaking, adding database storage or persistence to your Probot App will greatly complicate your life. It's perfectly doable, but for most use-cases you'll be able to manage relevant data within GitHub issues, pull requests and your app's configuration file.

Contents:

Using GitHub for persistent data

Probot includes a wrapper for the GitHub API which can enable you to store and manipulate data in the GitHub environment. Since your Probot App will usually be running to supplement work done on GitHub, it makes sense to try to keep everything in one place and avoid extra complications.

  • Comments: The API can read, write and delete comments on issues and pull requests.
  • Status: The API can read and change the status of an issue or pull request.
  • Search: GitHub has a powerful search API that can be used
  • Repository: Built-in context.config() allows storing configuration in the repository or the organization's .github repository.
  • Labels: The API can read labels, and add or remove them from issues and pull requests.

If your Probot App needs to store more data than Issues and Pull Requests store normally, you can use the probot-metadata extension to hide data in comments. It isn't meant to be super secure or scalable, but it's an easy way to manage some data without a full database.

There are even more APIs that you can use to increase the functionality of your Probot App. You can read about all of the ones available in Probot on the @octokit/rest documentation.

Using a Database

For when you absolutely do need external data storage, here are some examples using a few popular database solutions. Note that these are just suggestions, and that your implementation will vary.

MongoDB (with Mongoose)

MongoDB is a popular NoSQL database, and Mongoose is a widely used Node.js wrapper for it.

// PeopleSchema.js

import * as mongoose from "mongoose";

const Schema = mongoose.Schema;

const PeopleSchema = new Schema({
  name: {
    type: String,
    required: true,
  },
});

export default mongoose.model("People", PeopleSchema);
// index.js

import * as mongoose from "mongoose";

// Connect to the Mongo database using credentials
// in your environment variables
const mongoUri = `mongodb://${process.env.DB_HOST}`;

mongoose.connect(mongoUri, {
  user: process.env.DB_USER,
  pass: process.env.DB_PASS,
  useMongoClient: true,
});

// Register the mongoose model
import People from "./PeopleSchema.js";

export default (app) => {
  app.on("issues.opened", async (context) => {
    // Find all the people in the database
    const people = await People.find().exec();

    // Generate a string using all the peoples' names.
    // It would look like: 'Jason, Jane, James, Jennifer'
    const peoplesNames = people.map((person) => person.name).join(", ");

    // `context` extracts information from the event, which can be passed to
    // GitHub API calls. This will return:
    //   { owner: 'yourname', repo: 'yourrepo', number: 123, body: 'The following people are in the database: Jason, Jane, James, Jennifer' }
    const params = context.issue({
      body: `The following people are in the database: ${peoplesNames}`,
    });

    // Post a comment on the issue
    return context.octokit.issues.createComment(params);
  });
};

MySQL

Using the @databases/mysql module, we can connect to our MySQL database and perform queries.

// connection.js
import { connect } from "@databases/mysql";

// DATABASE_URL = mysql://my-user:my-password@localhost/my-db
const connection = connect(process.env.DATABASE_URL);

export default connection;
// index.js
import { sql } from "@databases/mysql";
import connection from "./connection.js";

export default (app) => {
  app.on("issues.opened", async (context) => {
    // Find all the people in the database
    const people = await connection.query(sql`SELECT * FROM people`);

    // Generate a string using all the peoples' names.
    // It would look like: 'Jason, Jane, James, Jennifer'
    const peoplesNames = people.map((key) => people[key].name).join(", ");

    // `context` extracts information from the event, which can be passed to
    // GitHub API calls. This will return:
    //   { owner: 'yourname', repo: 'yourrepo', number: 123, body: 'The following people are in the database: Jason, Jane, James, Jennifer' }
    const params = context.issue({
      body: `The following people are in the database: ${peoplesNames}`,
    });

    // Post a comment on the issue
    return context.octokit.issues.createComment(params);
  });
};

Postgres

Using the @databases/pg module, we can connect to our Postgres database and perform queries.

// connection.js
import { connect } from "@databases/pg";

// DATABASE_URL = postgresql://my-user:my-password@localhost/my-db
const connection = connect(process.env.DATABASE_URL);

export default connection;
// index.js
import { sql } from "@databases/pg";
import connection from "./connection.js";

export default (app) => {
  app.on("issues.opened", async (context) => {
    // Find all the people in the database
    const people = await connection.query(sql`SELECT * FROM people`);

    // Generate a string using all the peoples' names.
    // It would look like: 'Jason, Jane, James, Jennifer'
    const peoplesNames = people.map((key) => people[key].name).join(", ");

    // `context` extracts information from the event, which can be passed to
    // GitHub API calls. This will return:
    //   { owner: 'yourname', repo: 'yourrepo', number: 123, body: 'The following people are in the database: Jason, Jane, James, Jennifer' }
    const params = context.issue({
      body: `The following people are in the database: ${peoplesNames}`,
    });

    // Post a comment on the issue
    return context.octokit.issues.createComment(params);
  });
};

Firebase

Firebase is Google's services-as-a-service that includes a simple JSON database. You can learn more about dealing with the JavaScript API here. Note that for security purposes, you may also want to look into the Admin API.

// index.js

import * as firebase from "firebase";
// Set the configuration for your app
// TODO: Replace with your project's config object
const config = {
  apiKey: "apiKey",
  authDomain: "projectId.firebaseapp.com",
  databaseURL: "https://databaseName.firebaseio.com",
};
firebase.initializeApp(config);

const database = firebase.database();

export default (app) => {
  app.on("issues.opened", async (context) => {
    // Find all the people in the database
    const people = await database
      .ref("/people")
      .once("value")
      .then((snapshot) => {
        return snapshot.val();
      });

    // Generate a string using all the peoples' names.
    // It would look like: 'Jason, Jane, James, Jennifer'
    const peoplesNames = Object.keys(people)
      .map((key) => people[key].name)
      .join(", ");

    // `context` extracts information from the event, which can be passed to
    // GitHub API calls. This will return:
    //   { owner: 'yourname', repo: 'yourrepo', number: 123, body: 'The following people are in the database: Jason, Jane, James, Jennifer' }
    const params = context.issue({
      body: `The following people are in the database: ${peoplesNames}`,
    });

    // Post a comment on the issue
    return context.octokit.issues.createComment(params);
  });
};

Found a mistake or want to help improve this documentation? Suggest changes on GitHub

Get occasional updates on new apps & features.

Star

with by the Probot community

Code licensed ISC Docs licensed CC-BY-4.0