W-03. 노션(Notion)으로 어플리케이션을 만들 수 있을까?

feat. notion-api

Ethan Lee
8 min readOct 3, 2022
Photo by Jo Szczepanska on Unsplash

노션으로 어플리케이션을 만들 수 있을까?

관리자 앱을 만들어야하는 진퇴양난의 상황 속에서부터 시작된 고민이었다. 결국 관리자 앱을 노션으로 감당하는 것은 어렵다고 판단 내렸지만, 소규모의 토이프로젝트 정도는 충분히 감당 가능하단 것을 알게되었다.

지난 포스트에서 소개했듯이, 노션에서는 테이블의 데이터를 관계형 DB와 같이 관리하도록 설계를 했다. 테이블 간의 참조관계를 통해 보다 체계적으로 데이터를 관리할 수 있다.

이러한 데이터를 노션 내부에서만 쓰는 것이 아니라, 외부에서도 사용 가능하도록 API를 서비스 하고 있는데 nodejs 뿐만 아닌 java, python, 그리고 많은 언어들이 오픈소스로 개발되고 있다.

✅ 이런 분들이 읽으면 좋을 것 같다

  • 간단한 데이터 관리가 필요한 서비스를 운영하는 사람들
  • 데이터 관리가 쉽고, 비용 들이지 않고 싶은 사람들
  • 노션 좋아하는 개발자

노션 API에 대해서는 우리 니콜라스 형님이 잘 정리해주셨다. 간략한 소개가 필요하신 분은 다음 영상을 참고하기 바란다.

큰 그림부터 잡아보자.

  1. 노션에 테이블을 만들고, 데이터를 추가한다.
  2. 테이블에 대한 CRUD 권한을 노션 API에 할당한다.
  3. 노션 API SDK에 토큰을 세팅하게 되면 어플리케이션에서 SDK를 통해 테이블에 CRUD가 가능하다.
  4. 맘껏 놀아보자.

Step 1. 노션 테이블 만들기

💡노션 데이터베이스에 대해 잘 이해하고 있다면 2번으로 바로 가도 된다.

단순히 테이블을 하나만 만들어서도 쿼리가 가능하지만, 관계형 DB의 장점을 보여주기 위해선 적어도 두 개 이상의 테이블을 사용해 실험해보는 것을 추천한다.

노션 테이블 A 와 노션 테이블 B, 두 개의 테이블을 만들었다

처음에 만든 테이블은 각각 독립적이다. 여기에서 A 테이블과 B 테이블의 관계를 설정해보자.

A 테이블에서 관계형 필드를 추가한다.
어떤 테이블과 관계를 맺을 것인지 선택한다.
다음과 같이 원하는 값과 관계를 생성한다.
다음으로는 테이블 B에 value 라는 필드를 추가하고, 값을 넣었다.
A에서 롤업 필드를 추가하고, B 테이블과의 관계를 선택한다.

롤업이라는 필드는 해당 테이블이 참조하고 있는 다른 테이블에서 원하는 필드의 값을 가져올 수 있는 기능이다.

테이블 B의 value 필드를 선택하면 관계된 값이 나온다.

자 여기까지 하면 A와 B 테이블의 관계 설정이 끝났다.

Step 2. 노션 API를 테이블과 통합하기

👉 이 곳으로 이동해서 API 통합을 해보자

API 키를 만든다.
생성 후에는 시크릿 키를 복사 할 수 있다.
API 키가 만들어지고 나면, 자신의 워크스페이스에서 연결 추가를 할 수 있다.

여기까지 진행했다면, 이제 SDK를 사용할 준비는 끝났다.

Step 3. 노션 SDK 세팅하기

# npm
npm install @notionhq/client
# yarn
yarn add @notionhq/client

Step 2에서의 노션 API 시크릿 키를 복사해서 SDK에 설정한다.

import { Client } from '@notionhq/client';...const client = new Client({
auth: <NOTION_API_SECRET_KEY>
});

Step 4. 쿼리하기

노션의 데이터는 가장 작은 단위부터, 큰 단위로 나열을 했을 때 다음과 같이 구분된다. (댓글, 유저 와 같은 분류는 제외했다.)

  1. 블록
  2. 페이지
  3. 데이터베이스

즉, 데이터베이스 안에 페이지의 집합이 있고, 페이지 안에 블록의 집합이 있다고 보면 된다. 그렇기에 데이터베이스를 쿼리하면 페이지 배열을 기대할 수 있다.

위에서 두 테이블 간의 참조 관계를 설정했고, 롤업으로 A 테이블에서 B 테이블의 value를 참조하도록 한 것을 기억하는가? 노션에서 이미 결과 값을 테이블 A에서 보여줄 수 있도록 준비했고, 우리는 이걸 가져오기만 하면 된다.

이를 통해 어플리케이션에서는 n+1 쿼리 문제를 어느정도 해결할 수 있다.

async function queryTableA() {

const databases = client.databases;

const queryResults = await databases.query({
database_id: <테이블 A의 아이디>
});

// 페이지 배열
return queryResults.map(result => {

const props = result.properties;

return {
id: result.id,
title: props['이름'],
// B 테이블의 `value` 롤업 결과 조회
values: props['롤업'].rollup.array.map(item => {
return {
// 추후 관계 추가에 사용할 페이지 아이디 <PAGE_ID>
id: item.id,
text: item.title[0].plain_text;
}
})
}
})
}

<테이블 A의 아이디> 는 다음과 같이 얻을 수 있다.

테이블에 대한 공유 링크를 복사한다.
# 공유 링크 포맷
https://www.notion.so/<DATABASE_ID>?v=
<VIEW_ID>

링크에서 <DATABASE_ID>를 추려서 사용하면 된다.

Step 5. 데이터 추가하기

데이터베이스에 값을 추가한다는 것은 페이지를 생성한다는 것과 같은 의미이다.

async function createPageOnTableA() {

const pages = client.pages;

const newPage = await pages.create({
parent: {
database_id: <테이블 A의 아이디>
},
properties: {
이름: {
title: [
{
text: {
content: 'A-4'
}
}
]
},
'노션 테이블 B': {
relation: [
{
// 노션 테이블 B의 참조할 페이지의 아이디
id: <PAGE_ID>
}
]
}
}
});

const props = newPage.properties;

return {
id: item.id,
text: item.title[0].plain_text;
}
}

나머지 업데이트 삭제도 비슷한 방식으로 처리하면 된다.

마치며

아직까지 불편한 점들이 많다.

  • 타입스크립트 사용자 입장에서 살짝 아쉽다. 매번 코드를 작성할 때마다 타입 가드를 사용해야한다. isFullPage 등.. 그리고, 필드 데이터 타입에 따라 객체 타입이 달라지는 걸 직관적으로 사용하기가 어려운 것 같다.
  • 참조 depth가 깊어질 수록(예를 들어 A > B > C 의 참조관계에서 C의 필드값을 롤업해서 A가 참조하는 경우) API에서는 쿼리가 되지 않는 한계가 있다.
  • sort를 할 때 노션에서는 지원되는 필드가 많은데, API에서는 가능한 필드가 제한적이다.

이런 문제점들이 있다곤 하지만 좀 더 발전되면 정말 끝내주는 프로젝트가 아닐까 싶다.

개인적으로 어디까지 쓸 수 있을지가 궁금해서 사이드 프로젝트를 시작해봤는데, 오호… 그래도 얼추 어플리케이션의 구색이 나왔다.

http://travel-map.enoveh.com/

잠깐 소개하자면 지도에 유투브 영상을 추가해서 볼 수 있는 서비스이다. 모든 데이터는 노션에 저장된 데이터베이스 3개를 통해 제공된다.

이 정도 나왔으면…

여러분도 한 번 실험해 볼 만하지 않을까? 😎

--

--