Creating a GraphQL API for Notion with Hasura Actions

Notion launched their REST API recently letting you connect your pages and databases with any tool to create powerful workflows. In this post, we will leverage the Notion API to create a GraphQL wrapper for their pages API endpoint. This will use Hasura Actions to define GraphQL types and we will be writing an Action webhook handler that will make the call to Notion's API.

Let's get started by setting up things on Notion's side for the API integration.

Create a workspace in Notion

You need to be an admin of a workspace to be able to use the APIs. If you are an admin in an existing workspace, you can skip this step.

Let's get started by creating a new personal workspace by following the docs on Creating a personal workspace.

Create a new integration in Notion

The next step is to create an integration. Head to My integrations and create a new integration with relevant name and workspace.

After submitting the above form, note down the Internal Integration secret. This will be used during the API integration. The integration type will be internal. There's also the option of making it external so that the API is public and can be used by anyone. For this example, we will stick to internal since the Hasura Action will be used only for one private workspace.

Sharing a page / database

According to Notion docs,

Integrations don't have access to any pages (or databases) in the workspace at first. A user must share specific pages with an integration in order for those pages to be accessed using the API

This means, the API can be used only for those pages that already exist and has the right permissions (sharing) added to be used by the API.

Note that, you need to create a database as a full page to be able to use the API. You can do this by typing in /database and selecting Table - Full Page.

Head to Share on the top menu and click on Copy link. Paste this somewhere to identify the ID of the newly created database.

For example, it will look something along the lines of

https://www.notion.so/0664caf380ec482d92b74b01730480f9?v=8b0f9cba5e72499380fcb251369f5b4b

where the database ID is the one before the query parameter. Note down the database ID because we will reference that in the API request for making changes.

Deploy Hasura to get a GraphQL API

  1. Click on the following button to deploy GraphQL engine on Hasura Cloud. <!--kg-card-begin: markdown-->
  1. Open the Hasura console by clicking on the button "Launch console".

  2. Head to the Data tab and click on Create Heroku Database to create a new Postgres database. Note that this is an optional step. We will not use the database in this example since we are writing an Action to create a GraphQL wrapper over Notion.

  1. Now let's create an Action. Head to Actions tab from the top menu and click on Create to create a new action.

  2. We will add a custom mutation to add a new item to the DB.

The mutation type would look something like this:

type Mutation {
  addNotionItem (
    databaseId: String!
    properties: jsonb!
    children: jsonb
  ): ItemOutput
}

and the output type for ItemOutput would look something like this:

type ItemOutput {
  id : String!
  object : String!
  created_time : timestamptz!
  last_edited_time : timestamptz!
}

Note: You can modify the above type defintions to include more fields as required or return a more generic JSON so that everything from Notion's response can be returned back as output.

Leave the other inputs as it is and we will come back to modifying the handler URL later.

Notion API to add item to DB

To add an item to the DB, we can make use of the following API

{
    "parent": { "database_id": <database_id> },
    "properties": {
      "Name": {
        "title": [
          {
            "text": {
              "content": "iPhone 12"
            }
          }
        ]
      }
    }
  }

The above request body assumes you have the default Name field in your table.

Writing and Deploying Action Code

Once the Action is created with the above types, head to the Codegen tab and click on Try on glitch. This will create an app on Glitch platform with the Node.js boilerplate. We need to add an env variable in the .env file. Add the env NOTION_TOKEN and paste the Integration token that we obtained at the beginning of this post.

Here's the code for the new endpoint that you can paste in the Glitch app source code under src/server.js.

app.post('/addNotionItem', async (req, res) => {

  // get request input
  const { databaseId, properties, children } = req.body.input;

  // run some business logic
  const NOTION_TOKEN = process.env.NOTION_TOKEN;
  const NOTION_PAGES_ENDPOINT = "https://api.notion.com/v1/pages";
  const headers = {
    "Authorization": `Bearer ${NOTION_TOKEN}`,
    "Content-Type": "application/json"
  };

  const reqBody = {
    "parent": { "database_id": databaseId },
    "properties": properties
  }

  if(children) {
    reqBody["children"] = children;
  }

  const options = {
    method: 'POST',
    headers: headers,
    body: JSON.stringify(reqBody)
  }

  const fetchResponse = await fetch(NOTION_PAGES_ENDPOINT, options);
  const responseJson = await fetchResponse.json();

  console.log(responseJson);

  /*
  // In case of errors:
  return res.status(400).json({
    message: "error happened"
  })
  */

  // success
  return res.json({
    id: responseJson.id,
    object: responseJson.object,
    created_time: responseJson.created_time,
    last_edited_time: responseJson.last_edited_time
  })

});

Enter fullscreen mode Exit fullscreen mode
Hasura Action Handler Code

Once this Action code is updated on the Glitch app, copy the Glitch app URL and add the /addNotionItem to that and modify the Action URL to point to the glitch endpoint.

An example glitch endpoint would look something like this https://lovely-bustling-environment.glitch.me/addNotionItem

Test the GraphQL API

Head to Hasura Console API Explorer and make the following GraphQL mutation:

mutation addNotionItem ($databaseId: String!, $properties: jsonb!) {
  addNotionItem(databaseId: $databaseId, properties: $properties) {
    id
    object
    created_time
    last_edited_time
  }
}

With the following variables:

{
  "databaseId": "<database_id>",
  "properties": {
    "Name": {
        "title": [
          {
            "text": {
              "content": "iPhone 12"
            }
          }
        ]
      }
  }
}

Replace the <database_id> with your own database ID.

You should get a response which looks something like:

{
  "data": {
    "addNotionItem": {
      "id": "4bf24dc3-8a98-4247-856c-ba15500082cc",
      "object": "page",
      "created_time": "2021-05-21T12:57:46.751Z",
      "last_edited_time": "2021-05-21T12:57:46.751Z"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
GraphQL Response for Notion Mutation

Adding Permissions

You can also define permissions for this Action so that you can restrict or allow access only to certain roles.

Head to the Permissions tab of the created Action and allow access for specific roles.

We have written a GraphQL wrapper for Notion's Create a page API. Now of course you can repeat this for any of the Notion's REST API by defining custom types appropriately and writing the webhook handler.

53