š§š½āš» Notion as a CMS
If you take notes with Notion, this is for you.
Overview
Youāve heard of SaaS and PaaS and BaaS. Get ready for the latest and greatest aaS yet - NaaBaaS (Notion as a Backend as a Service).
Idea
The idea was simple. Use Notion to power my portfolioās blog functionality, and make pushing updates as easy as possible.
Some of my intuitive ideas were as follows:
Leverage Notionās built-in deployment capabilities and somehow embed the pages into my siteUse Notionās API to extract my blog data, and convert JSON to Svelte componentsUse a 3rd party library to npm install my way to victory. There are great options for #3. A couple āehā ones for #1. Of course I opted for #2.
Steps
Notion Setup
Set up your Notion integration (Menu ā Connect To ā Manage Connections)Select āDevelop or manage integrationsāName integration, keep internal, and uncheck the box for āmake user info availableāReturn to the desired database and connect to new integration (Menu ā Connect To ā Node.js + Typescript
The next few steps are used to extract and parse the data from your selected database
Initialize a node project npm init -yInstall Notion API and dotenv using npm install @notionhq/client dotenvCreate an entry point file called index.js and create a structure like this šš½
import { Client } from "@notionhq/client";
import { DataFetcher, DataTransformer } from "./lib";
const DATABASE_NAME = "Example";
const notion = new Client({ auth: process.env.API_KEY });
async function main() {
const fetcher = new DataFetcher(notion, DATABASE_NAME);
await fetcher.getDatabaseId();
const pages = fetcher.getDatabasePages();
await fetcher.saveToFile(pages, outPath);
const transformer = new DataTransformer(inputfile);
await transformer.loadPages();
await transformer.parseData();
await transformer.saveTransformedPages(outPath);
)
Note: This code is meraly outline and not intended as a working sample.
export class DataFetcher {
private notionClient;
private databaseId?;
constructor(notion, databaseId?) { ... }
async getDatabaseIdFromQuery(dbName) {
try {
const response = await this.notion.search({
query: query
...
this.databaseId = response.results[0].id;
} catch(err) { }
}
async getDatabasePages() {
try {
const { results } = await this.notion.databases.query({
database_id: this.databaseId,
});
const { pagesWithBlocks } = await Promise.all(
results.map(async (page) => {
const blocks = await this.getBlocks(page.id);
}
return pagesWithBlocks;
} catch (err) { }
}
async saveToFile(pages, outPath) {
await fs.writeFile(outPath, JSON.stringify(pages, null, 2));
}
The output of the Fetcher class is huge. Just a few pages can quickly generate thousands of lines of JSON code. This is what our Transformer is for
class DataTransformer {
private inputFile;
private pages = [];
constructor(inputFile, pages) { ... }
async loadPages() {
try {
const data = fs.readFile(...)
this.pages = JSON.parse(data);
}
}
async transformPages() {
pages.map((page) => {
const {
return {
...,
blocks: resolveBlocks(children);
}
}
}
async saveTransformedPages(outPath) {
fs.writeFile(...)
}
resolveBlocks = (blocks) => {
return blocks
.map((block) => {
console.log(block.type);
const blockHandler =
this.blockMap[block.type as keyof typeof this.blockMap];
if (blockHandler) {
return blockHandler(block);
}
console.log("NOT SUPPORTED:", block.type);
return null;
})
}
private blockMap = {
paragraph: (block) => { ... },
image: (block) => { ... },
heading_1: (block) => { ... }
}
}
Now what?
If you either cloned my repo or tried to fill in the gaping holes in my pseudo-code (congrats btw), then you are now the proud owner of a clean and intelligible JSON document. The next step is to write a React (or Svelte, Vue, etc) component that can consume the data in the exported format. As an example, Iāll attach my component (Svelte) below:
<script lang="ts">
const renderBlock = (block: {
component: string;
text: string;
code: string;
}) => {
switch (block.component) {
case "Paragraph":
return `<p class="my-1">${block.text}</p>`;
case "Heading1":
return `<h1 class="text-3xl my-4 font-bold">${block.text}</h1>`;
}
};
</script>
<div class="flex flex-col justify-center mx-auto sm:px-16 sm:mt-2">
<div class="mb-6">
<a href="/notes" class="text-primary hover:text-accent">Go Back</a>
</div>
<p class="text-4xl lg:text-5xl font-bold mb-8 lg:leading-tight">
{data.note.icon}
{data.note.title}
</p>
<div class="text-neutral-400 flex flex-col gap-2">
<div class="grid grid-cols-3 gap-4">
<div class="col-span-1 flex gap-2 items-center">
<IonList class="inline" />
<span>Tags</span>
</div>
<div class="col-span-2">
{#each data.note.tags as tag}
<span class="text-xs bg-primary text-white rounded-full px-2 mr-2"
>{tag}</span
>
{/each}
</div>
</div>
<div class="grid grid-cols-3 gap-4 mb-8">
<div class="col-span-1 flex gap-2 items-center">
<BiCalendar class="inline" />
<span>Date Created</span>
</div>
<div class="col-span-2">
{new Date(data.note.dateCreated).toLocaleDateString()}
</div>
</div>
</div>
{#each data.note.blocks as block}
{@html renderBlock(block)}
{/each}
</div>
Further Help
If you get stuck, check out the code on Github or reach out via the āContactā section of this website!
https://github.com/jakeevans00/notion-scripts