Using Notion as a Human-Readable Database
Capture form submissions from your web app and store them where your team works.
Sam Magura
When developing an application, data is usually stored in a relational database or a NoSQL database. While these databases are extremely powerful, they aren't easy to use for non-technical users, and they are overkill for simple tasks. In this post, we'll build an application that uploads form submissions to a Notion database. If you aren't familiar with it, Notion is essentially a wiki platform with many advanced features.
🔗 The full code for this example is available in the zerosecrets/examples GitHub repository.
Secure your secrets conveniently
Zero is a modern secrets manager built with usability at its core. Reliable and secure, it saves time and effort.
What We're Building
For this example project, we'll build a lead capture form where a prospective buyer can leave their email address and name. Later, a salesperson would reach out to the user to provide them additional information about the product.
The web application will be built on Next.js. The goal is to upload the form submissions (name and email) to a Notion database, with each form submission becoming a row in the database. Your organization's salespeople would reference the Notion database to figure out who to contact.
To get the data into Notion, we'll use the Notion API through the official Notion JavaScript SDK . The Notion API key will be retrieved from the cloud at runtime with the help of the Zero secrets manager.
Creating a Notion Database
If you don't already have a Notion account, you can sign up for one for free at https://notion.so . The first step is to create a Notion database . To do this, you'll create a blank Notion page and then select "Table" at the bottom. Notion's terminology is inconsistent in this area — a table and a database are really the same thing.
You'll now be able to customize the columns of the database. Our database will have just two columns, Name (type = text) and Email (type = email). The rows of the database should be left blank, since those will be filled in by our application.
Creating a Notion Integration
To work with the Notion API, we'll need to create an internal Notion integration, as described on this docs page . An internal integration just means the integration is used inside your organization, not by outside users.
You can create the integration on the integration settings page . You really just need to enter a name for the integration, e.g. Lead Form, and select the Notion workspace to connect it to.
Once the integration is created, you'll be presented with the integration's secret key (API key). Please copy this key into a text file for now.
Not only does the integration need to be connected to your workspace, you also have to grant the integration access to the specific pages it should be able to modify. In our case, the integration should be able to modify the database and all its subpages. So navigate to the database page, and grant the integration access to it using the menu item shown here .
Importing the API Key into Zero
Since our app will be pulling the secret from Zero at runtime, we need to import the Notion API key into Zero. Log in to Zero and create a new project. Create a token for the project and save it in that same text file. The application will exchange this token for the secrets when it calls the Zero API.
Once the project is created, add a new secret. For the name, enter "Notion", and for the fields, write API_KEY
and paste in the Notion API key.
Building the Next.js App
Now, let's start coding. To bootstrap a Next.js app, run
npx create-next-app@latest notion-app
and select TypeScript.
The goal is to create a lead capture form that looks like this:
When the form is submitted, it should call a backend API method (a Route Handler, in Next.js terminology). I'll skip over the code for creating the form. As always, you can find the full code in the zerosecrets/examples GitHub repository.
Fetching the Notion API Key
Before we code up our Route Handler, we'll need to pull down the Notion API key from Zero. First, install the Zero TypeScript SDK and the Notion SDK:
npm install @zerosecrets/zero @notionhq/client
Now, we'll use the standard pattern for fetching secrets from Zero, which is used in almost all of our blog posts:
import {Client} from '@notionhq/client'
import {zero} from '@zerosecrets/zero'
let client: Client | undefined
export async function getNotionClient() {
if (client) {
return client
}
if (!process.env.ZERO_TOKEN) {
throw new Error('Did you forget to set the ZERO_TOKEN environment variable?')
}
const secrets = await zero({
token: process.env.ZERO_TOKEN,
pick: ['notion'],
}).fetch()
if (!secrets.notion) {
throw new Error('Did not receive Notion secret.')
}
client = new Client({auth: secrets.notion.api_key})
return client
}
The getNotionClient
function can now be called from any backend API method that needs to access Notion.
Coding the API Method
The API method POST api/createLead
is quite simple actually. It just needs to extract the name and email from the request body, and then call the Notion API to insert a new page into the database. First, scaffold the Route Handler like this:
export async function POST(request: Request) {
const body = await request.json()
const notion = await getNotionClient()
// TODO Call Notion API
return Response.json(null)
}
To call the Notion API, you'll need to locate your database ID, which is documented here . We'll utilize the notion.pages.create
API method, since each row in the Notion database is actually a page. It took me a while to figure out the proper syntax for the properties, but fortunately, the Notion SDK provides TypeScript Intellisense that can help you figure it out. Here's the final code, which should be incorporated into the Route Handler skeleton shown above:
const notionResponse = await notion.pages.create({
parent: {
// TODO Put your database ID here
database_id: '147c283ff3318039867efce5512f6173',
},
properties: {
Name: {
type: 'title',
title: [
{
type: 'text',
text: {
content: body.name,
},
},
],
},
Email: {
type: 'email',
email: body.email,
},
},
})
console.log(`Created Notion page: ${notionResponse.id}`)
Testing it Out
To test your app, you'll need to provide your project's Zero token as an environment variable:
ZERO_TOKEN='YOUR_ZERO_TOKEN' npm run dev
Fill out the lead capture form with any name and email and click Submit. If everything worked, you should see a record in your Notion database!
In Conclusion
This post showed that you don't always have to use a full-featured database like SQL Server or PostgreSQL to capture data from a web application. Using Notion as a database makes the data completely human-readable. Plus, you can add additional columns and data to the Notion database with ease. An alternative to Notion in this scenario would be to send a Slack message each time the form is submitted. There are pros and cons to each approach, so it's really up to you what you choose.
Other articles
Managing Files in Amazon S3 from Node.js
Adding and removing files from S3 is a breeze with the AWS JavaScript SDK.
Monitor a Node.js App with Datadog and Winston
Quickly set up error logging in SvelteKit or any another Node.js-based framework.
Secure your secrets
Zero is a modern secrets manager built with usability at its core. Reliable and secure, it saves time and effort.