Notion ORM

Feb 14, 2023

⚠️ This package is Still in development 🏗️

A library to simplify adding and querying Notion databases/tables. Giving typeahead/intellisense support on columns and expected column values on user specified databases. Built on top of Notion API

Databases with the following column types are supported:

  • Multi-select
  • Select
  • Status
  • Date
  • Text
  • Url
  • Checkbox
  • Email
  • Phone Number

Installation

The only requirement is a Notion Developer API key (here) and database IDs you want. Be sure to connect your integration (🚧 Permissions section) with your tables

npm install @haustle/notion-orm --save-dev

At the root of your project create a notion.config.js file

You’ll need to pass your developer key and database IDs. How to get database IDs here

// notion.config.js
const auth = process.env.NOTION_KEY
const NotionConfig = {
auth,
databaseIds: [
'a52239e4839d4a3a8f4875376cfbfb02',
'5f4bf76a1e3f48d684d2506ea2690d64',
],
}
module.exports = NotionConfig

Execute the following command from the root project directory.

npx notion-orm generate

Package Size Unpackaged size is 70.6KB and the installation size is 5.12MB (5.03MB from @notionhq/client dependency)

Implementation

Databases can be imported via barrel file or from the individual database file. All database names will be camelCase 🐫.

// Barrel Import (access to all databases)
import * as notion from '@haustle/notion-orm'
notion.databaseName.add()
notion.databaseName2.query()
// Individual Database Import
import {
databaseName,
DatabaseSchemaType,
QuerySchemaType,
} from '@haustle/notion-orm/build/db/databaseName'
databaseName.add()
  • DatabaseSchemaType: Object type accepted in the database’s add() function
  • QuerySchemaType: Object type accepted in the database’s query() function

The following examples for querying & adding are for server-side calls. If you’re looking to use this framework to execute client-side calls (ex. button click add/query X) visit the Client (React) section after reading

Adding

Only required column required is the title.

notion.books.add({
bookName: 'Raphael, Painter in Rome: a Novel', // title
author: 'Stephanie Storey', // text
status: 'In progress', // status
numberOfPages: 307, // number
genre: ['Historical Fiction'], // multi-select
rating: '⭐️⭐️⭐️⭐️', // select
startDate: {
// date
start: '2023-01-01',
},
phone: '0000000000', // phone
email: 'tyrus@haustle.studio', // email
})

All column types in Notion databases are mapped to a typescript type.

Column TypeObject
Titlestring
Textstring
Selectstring
Multi-selectArray (string)
Statusstring
Numbernumber
DateObject
Phone numberstring
Emailstring

Querying

For each column type you’ll be presented with the available querying filter. Find all filter conditions here

While the querying functionality works, it’s not complete and there is room for user error. For instance, the filter object should contain one child. Either the column name (signifies single filter), or and or or (signify compound filters). However there is no typecheck in place to stop adding multiple children

Unlike add() , there is no transformation after the inputted object. So the querying object you’re creating is exactly what you’d normally use to query the Notion API. Learn more about them here

Example of a single filter

notion.books.query({
filter: {
genre: {
contains: "Sci-Fi",
},
},
sort: [
{
property: "name:,
direction: "ascending"
},
{
property: "Author name:,
direction: "ascending"
}
]
});

Example of compound filters, which is signified with and and or. You can nest these are far as you want (i.e and filters within or filter). Learn more here

await notion.books.query({
filter: {
or: [
{
genre: {
contains: 'Sci-Fi',
},
},
{
genre: {
contains: 'Biography',
},
},
],
},
})

Down below is what’s returned on a successful response. results being a simplified extracted version of the rawResponse (response from Notion API)

{
rawResponse: <whatever Notion API returns>,
results: [
{
bookName: "How to Change Your Mind",
genre: ["Non-fiction"],
numberPages: 460,
rating: "⭐️⭐️⭐️⭐️"
},
// ... more results
]
}

Client-side (React)

Notion API currently blocks calls from browser (per CORS)

You can get around this by creating API endpoints on stack of your choice. I’ve provided examples only for Next.js, but the high level implementation should work with any backend.

If you’re planning to only make server-side calls to your Notion database (from GetStaticProps or GetServerSideProps). These calls work totally fine, as these functions are executed server-side before page load. So you can ignore the proceeding steps

export const getStaticProps: GetStaticProps = async () => {
const response = await NotionClient.books.query({
filter: {
genre: {
is_not_empty: true,
},
},
});
return {
props: {
apiResponse: response
}
}

To execute calls client-side (ex. on button click) an API endpoint is needed to get around CORS. In this example we’re passing the databases DatabaseSchemaType as the body of the API call.

import { DatabaseSchemaType } from '@haustle/notion-orm/build/db/books'
async function addPageToNotionDatabase() {
const example: DatabaseSchemaType = {
bookName: 'How to Change Your Mind',
genre: ['Non-fiction'],
}
// make sure this route reflects your API path
await fetch('/api/notion/books', {
method: 'POST',
body: JSON.stringify(example),
})
}
<button onClick={ async() => await addPageToNotionDatabase()}>

Example API endpoint below, where we’re taking the body of type DatabaseSchemaType and passing it into the respected databases add() function to add a new page to the database. Learn more about Next.js API’s and routing here.

// pages/api/notion/yourDatabaseName.ts
import type { NextApiRequest, NextApiResponse } from "next";
import {
DatabaseSchemaType,
yourDatabaseName,
} from "@haustle/notion-orm/build/db/yourDatabaseName";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { method, body } = req;
if (method === "POST") {
const bodyJSON = JSON.parse(body) as DatabaseSchemaType;
await yourDatabaseName.add(bodyJSON);
}
}

Why?

While taking a databases class I got exposed to Prisma and thought the developer experience was magical. Around the same time the Notion Developer API was rolling out and I thought the idea of bringing creating a ORM to Notion databases would be a fun cool idea, but I was terrible at TypeScript.

This project was started as a way to strengthen my TypeScript knowledge. I’ve succeeded in some regard, but there are still a few @ts-ignore in the project.