💻 Frontend
Notion API 어디까지 가능하니?
0) 데모 서비스 미리보기(환자관리 게시판) 1) 배경1-1) 사내의 부족한 백엔드개발자 대체1-2) 불필요한 어드민 서비스 제작 최소화 및 관리 효율 증대1-3) 검증항목2) 노션 API 연결노션 API 키 발급노션 SDK 세팅메인이 되는 DATABASE 만들고 key 값 넣어주기3) 메인이 되는 데이터베이스를 만들자. (DATABASE)3-1) 데이터베이스의 속성들 읽어오기3-2) 관계형 데이터베이스 불러오기4) 페이지 CRUD5) 블록 CRUD6) API를 통해 어떤 기능들을 할 수 있는가?6-1) 페이지네이션6-2) 필터 및 정렬6-3) 검색6-4) 댓글7) 결론7-1) 사내 활용방안 7-2) 속도 개선 가능한지 체크 필요함.
0) 데모 서비스 미리보기(환자관리 게시판)
1) 배경
1-1) 사내의 부족한 백엔드개발자 대체
- 사내의 백엔드 개발자 부족
- 서비스 구현에 속도를 내기 위해서 백엔드 구성을 심플하게 가져가는 것이 필요함.
1-2) 불필요한 어드민 서비스 제작 최소화 및 관리 효율 증대
- 회사는 별도의 어드민 및 DB 관리를 notion으로 일원화하는 것에 대한 필요성
- 이를 통해 회사에서 제공하는 연계 상품들에 대한 페이지를 노션 DB를 통한 관리에 대해서 고민하기 시작
- 이를 위해 환자관리 게시판을 만들어야한다는 가정하에 CRUD 가능성을 검토하고, API를 통해 어떤 기능들을 제공할 수 있는지 검토
1-3) 검증항목
- CRUD가 되는가? 어떻게 되는가?
- API를 통해 어떤 기능들을 할 수 있는가?
- 데이터를 처리하는데 속도가 어느정도 걸리는가? 느리다면 개선방안은?
- 회사에서 어느정도로 활용할 수 있는가?
2) 노션 API 연결
노션 API 키 발급
notion developer 사이트에서 API 키를 발급하자

env에 key 값을 설정해서 넣어주면 해당 값을 읽어올 수 있다.

노션 SDK 세팅

메인이 되는 DATABASE 만들고 key 값 넣어주기
const databaseID = 'a35a25bbb8874a75a7d1c739c9e2fbac';
const response = await notion.databases.query({
database_id: databaseID,
});3) 메인이 되는 데이터베이스를 만들자. (DATABASE)

메인이 되는 데이터베이스를 만들자
데이터베이스 ID가 필요하고, 해당 데이터베이스를 기반으로 진행된다.
만약 데이터베이스를 안만든다면? 작업 자체를 할 수가 없다.
3-1) 데이터베이스의 속성들 읽어오기
const response = await notion.databases.query({
database_id: databaseID,
});데이터베이스는 아래와 같이 query 메서드를 통해서 불러올 수 있다.
이를 통해 데이터베이스를 불러오게 되면 아래와 같이 나오게 된다.

3-2) 관계형 데이터베이스 불러오기
관계형을 설정한 데이터베이스는 불러오기가 조금 어려운데, notion API 특성상 관계형 데이터베이스로 설정된 값들은 data key 값만 넘겨주기 때문에 해당되는 데이터들을 하나씩 조회해야만 해당 데이터를 불러올 수 있다.
거기에서 내가 생각한 방식은 상위 카테고리에 종속되는 데이터들을 모아서 보여주는 것이다.

위와 같이 대분류 중분류에 해당되는 데이터들을 관계형으로 설정해서 불러오고자 한다.
각 질병목록(대분류)를 클릭하면 거기에 해당되는 병원목록(중분류)를 알려주고, 해당 중분류를 선택하면 거기에 해당되는 병원 상세페이지(소분류)를 보여주도록 데이터를 설계했다.

이렇게 되면 데이터베이스를 연동해서 보여줄 수 있다.
다만, 이렇게 2단계에 걸쳐진 데이터베이스와 부모에 해당되는 자식 데이터로 가는 것은 가능하나
이 단계가 복잡해지거나, 부모데이터에서 다른 부모데이터로 가서 또 자식데이터를 탐색해서 맞는 데이터를 불러오는 것은 로직이 꽤나 복잡해진다.
그렇기 때문에 관계형데이터베이스를 활용하는 것은 적정 수준으로 설계하는 것이 필요하다
4) 페이지 CRUD

- 게시글 생성
app.post('/postData', function (req, res) {
// 바디 속성 읽기
const databaseID = 'a35a25bbb8874a75a7d1c739c9e2fbac';
const bodyData = req.body.reviewData;
// 해당 값들을 넘겨서 page 생성하기
try {
const postData = async () => {
const response = await notion.pages.create({
parent: {
type: 'database_id',
database_id: databaseID,
},
properties: {
ReviewTitle: {
title: [
{
text: {
content: bodyData.title,
},
},
],
},
}
}
- 게시글 생성할 때는 post 요청으로 보낼 수 있고, 상위 데이터베이스를 parent로 지정한 뒤에 properties를 모두 채우면 해당 속성들을 보낼 수 있음.
- 해당 properties들을 속성 값으로 넘겨주면 해당 데이터가 추가된 것을 알 수 있다.
- 게시글 업데이트
// 게시글 updateData
app.post('/updateFinal', function (req, res) {
// 바디 속성 읽기
const bodyData = req.body.updateData;
const pageID = bodyData.pageID;
// 해당 값들을 넘겨서 page 생성하기
try {
const updatingData = async () => {
const response = await notion.pages.update({
page_id: pageID,
properties: {
ReviewTitle: {
title: [
{
text: {
content: bodyData.title,
},
},
],
},- 게시글 삭제

- 게시글 읽기
// 게시글 update를 하려면 기존 입력되어있는 updateData를 받아와야함.
// retrieve라는 속성을 이용하면 기존 속성들을 읽어올 수 있음
app.get('/updateData/:id', function (req, res) {
pageID = req.params.id;
if (pageID !== 'undefined') {
try {
const updateGetData = async () => {
const response = await notion.pages.retrieve({
page_id: pageID,
});
}
}5) 블록 CRUD



<블록>
- 블록 생성
app.post('/blockCheckpostingTest', function (req, res) {
const inputData = req.body.inputData;
const parentPageID = req.body.parentPage;
const dataType = req.body.dataType;
let responseContent;
if (dataType === 'paragraph') {
responseContent = [
{
paragraph: {
rich_text: [
{
text: {
content: inputData,
},
},
],
},
},
];
} else if (dataType === 'to_do') {
responseContent = [
{
to_do: {
checked: false,
rich_text: [
{
text: {
content: inputData,
},
},
],
},
},
];
} else if (dataType === 'image') {
responseContent = [
{
image: {
type: 'external',
external: {
url: inputData,
},
},
},
];
}
try {
const blockPosting = async () => {
return await notion.blocks.children.append({
block_id: parentPageID,
children: responseContent,
});
};
blockPosting().then((response) => res.json(response));
} catch (error) {
console.log(error);
}
});- 블록 업데이트
app.post('/updateBlocks', function (req, res) {
const blockData = req.body.updateData;
const updateData = {
block_id: blockData.id,
};
if (blockData.type === 'to_do') {
updateData['to_do'] = {
checked: blockData.checked,
rich_text: [
{
text: {
content: blockData.text,
},
},
],
};
} else if (blockData.type === 'paragraph') {
updateData['paragraph'] = {
rich_text: [
{
text: {
content: blockData.text,
},
},
],
};
} else if (blockData.type === 'image') {
updateData['image'] = {
image: {
type: 'external',
external: {
url: blockData.text,
},
},
};
}- 블록 리딩
app.get('/blockCheck/:id', function (req, res) {
// 페이지 속성들 Review로 연동
// review로 연동된 데이터 불러와서 보여주기
// 데이터 넣기 가능한지 체크.
// 데이터 넣을 수 있는가.
const blockID = req.params.id;
const getBlockContents = async () => {
const response = await notion.blocks.children.list({
block_id: blockID,
});
return response;
};
getBlockContents().then((response) => res.json(response));
});- 블록 delete
app.post('/deleteBlocks', function (req, res) {
try {
const blockID = req.body.blockID;
const deleteCall = async () => {
return await notion.blocks.delete({
block_id: blockID,
});
};
deleteCall().then((response) => res.json(response));
} catch (error) {
console.log(error);
}
});- 블록을 마크다운으로 가져오기도 가능
6) API를 통해 어떤 기능들을 할 수 있는가?
6-1) 페이지네이션

app.get('/getData', function (req, res) {
const databaseID = 'a35a25bbb8874a75a7d1c739c9e2fbac';
let totalPage;
let pageno;
let pagingData = [];
let totalCount;
try {
const boardingData = async () => {
const response = await notion.databases.query({
database_id: databaseID,
});
const countperpage = 3;
pageno = req.query.pageno;
if (pageno === undefined) {
pageno = 1;
}
totalCount = response.results.length;
totalPage = Math.ceil(totalCount / countperpage);
// 시작 번호
let startItemNo = (pageno - 1) * countperpage;
// 종료 번호
let endItemNo = pageno * countperpage - 1;
// 종료 번호가 전체 크기보다 크면 전체 크기로 변경
//전체 데이터에서 페이지만큼 배열 자르기
response.results.forEach((item, idx) => {
if (idx >= startItemNo && idx <= endItemNo) {
let hospitalItem = {
...
};
return pagingData.push(hospitalItem);
}
});
return pagingData;
};
boardingData().then(() =>
res.json({
data: pagingData,
totalpage: totalPage,
pageNow: pageno,
totalCount,
})
);
} catch (error) {
console.log(error);
}
});- 변수목록
- countperpage : 페이지당 보여줄 컨텐츠 개수
- totalPage : 전체 카운트를 페이지당 보여줄 컨텐츠 개수에서 나눈 값
- totalCount : 전체 컨텐츠의 개수
- pageno : default는 1이고, 그 외에는 쿼리값으로 전달해준 값
- startItemNo : 스타트 아이템 넘버
- endItemNo : 끝나는 아이템 넘버
6-2) 필터 및 정렬
- 자체 메서드를 통해 구현하기 : 필터와 정렬기능
6-3) 검색
- 검색기능도 자체 메서드로 제공하고 있음.
6-4) 댓글
- 댓글도 구현이 가능함.
7) 결론
7-1) 사내 활용방안
- 노션을 Admin으로 활용해서 상품소개 페이지만들기
- 고객 문의사항 페이지
7-2) 속도 개선 가능한지 체크 필요함.
notion API 호출시 속도가 많이 걸림.
리페칭, 캐시로 속도이슈 해결, 에러핸들링
- 어디서 속도가 많이 걸리는가?
- 결론: 노션 API 자체가 그냥 호출이 많이 걸림
- filter와 sort를 자체적으로 구현하는 로직으로 서로 비교해봄
- 다만, 이미 페칭된 데이터를 끌고 오면 확실히 성능이 개선될 것으로 보임
- 캐시로 속도이슈 처리
- 프론트
- vue query 활용하기
- 서버
- notion db를 외부 db와 연동시키기
- redis에 캐시
- SSR 활용하기
- 데이터 자체를 한 번 페칭하고, 해당 데이터를 서버사이드로 올려줌
- 해당 ssr을 기반으로 빠르게 그려내기

