💻 Frontend

Notion API 어디까지 가능하니?

date
Feb 22, 2023
slug
notion-api
author
status
Public
tags
Notion
summary
Notion의 백엔드(서버,데이터베이스) 활용가능성 검토
type
Post
thumbnail
스크린샷 2023-04-13 오전 5.10.01.png
category
💻 Frontend
updatedAt
Apr 12, 2023 08:11 PM
 

0) 데모 서비스 미리보기(환자관리 게시판)

 
 
 

1) 배경

1-1) 사내의 부족한 백엔드개발자 대체

  • 사내의 백엔드 개발자 부족
  • 서비스 구현에 속도를 내기 위해서 백엔드 구성을 심플하게 가져가는 것이 필요함.
 

1-2) 불필요한 어드민 서비스 제작 최소화 및 관리 효율 증대

  • 회사는 별도의 어드민 및 DB 관리를 notion으로 일원화하는 것에 대한 필요성
  • 이를 통해 회사에서 제공하는 연계 상품들에 대한 페이지를 노션 DB를 통한 관리에 대해서 고민하기 시작
  • 이를 위해 환자관리 게시판을 만들어야한다는 가정하에 CRUD 가능성을 검토하고, API를 통해 어떤 기능들을 제공할 수 있는지 검토

1-3) 검증항목

  • CRUD가 되는가? 어떻게 되는가?
  • API를 통해 어떤 기능들을 할 수 있는가?
  • 데이터를 처리하는데 속도가 어느정도 걸리는가? 느리다면 개선방안은?
  • 회사에서 어느정도로 활용할 수 있는가?
 

2) 노션 API 연결

노션 API 키 발급

notion developer 사이트에서 API 키를 발급하자
notion image
 
env에 key 값을 설정해서 넣어주면 해당 값을 읽어올 수 있다.
notion image

노션 SDK 세팅

notion image
 

메인이 되는 DATABASE 만들고 key 값 넣어주기

const databaseID = 'a35a25bbb8874a75a7d1c739c9e2fbac';
const response = await notion.databases.query({
				database_id: databaseID,
});
 

 

3) 메인이 되는 데이터베이스를 만들자. (DATABASE)

notion image
메인이 되는 데이터베이스를 만들자
데이터베이스 ID가 필요하고, 해당 데이터베이스를 기반으로 진행된다.
만약 데이터베이스를 안만든다면? 작업 자체를 할 수가 없다.
 

3-1) 데이터베이스의 속성들 읽어오기

const response = await notion.databases.query({
				database_id: databaseID,
			});
데이터베이스는 아래와 같이 query 메서드를 통해서 불러올 수 있다.
이를 통해 데이터베이스를 불러오게 되면 아래와 같이 나오게 된다.
notion image
 

3-2) 관계형 데이터베이스 불러오기

관계형을 설정한 데이터베이스는 불러오기가 조금 어려운데, notion API 특성상 관계형 데이터베이스로 설정된 값들은 data key 값만 넘겨주기 때문에 해당되는 데이터들을 하나씩 조회해야만 해당 데이터를 불러올 수 있다.
 
거기에서 내가 생각한 방식은 상위 카테고리에 종속되는 데이터들을 모아서 보여주는 것이다.
notion image
위와 같이 대분류 중분류에 해당되는 데이터들을 관계형으로 설정해서 불러오고자 한다.
각 질병목록(대분류)를 클릭하면 거기에 해당되는 병원목록(중분류)를 알려주고, 해당 중분류를 선택하면 거기에 해당되는 병원 상세페이지(소분류)를 보여주도록 데이터를 설계했다.
notion image
 
이렇게 되면 데이터베이스를 연동해서 보여줄 수 있다.
다만, 이렇게 2단계에 걸쳐진 데이터베이스와 부모에 해당되는 자식 데이터로 가는 것은 가능하나
이 단계가 복잡해지거나, 부모데이터에서 다른 부모데이터로 가서 또 자식데이터를 탐색해서 맞는 데이터를 불러오는 것은 로직이 꽤나 복잡해진다.
 
그렇기 때문에 관계형데이터베이스를 활용하는 것은 적정 수준으로 설계하는 것이 필요하다
 

4) 페이지 CRUD

notion image
  • 게시글 생성
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,
								},
							},
						],
					},
 
  • 게시글 삭제
    • notion image
 
  • 게시글 읽기
// 게시글 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

notion image
notion image
notion image
<블록>
  • 블록 생성
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) 페이지네이션

notion image
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을 기반으로 빠르게 그려내기