[Vue] Vue CLI 환경에서 노션 API 이용하기

한음·2022년 7월 24일
1
post-thumbnail

노션 API 를 통해 노션을 간단한 DB 로 사용할 수 있다는 영상을 봤다.

테스트 정도만 해보려 했는데 주말을 날리게 될줄이야.
Vue CLI 환경에서 노션 API 이용하는 글이 없길래 나처럼 삽질하는 사람을 위해 기록 남기기.


**노션 SDK 를 이용하던, 자체적으로 HTTP 요청을 날리던 노션 내에서 인증 키를 받고 원하는 페이지에 (DB의 경우 DB 페이지) 해당 키로 공유 설정을 해줘야한다.

[참고]
노션 API Beta 오픈! 노션에 API 연동하기 (Notion API 가이드 따라 노션 표에 페이지 추가하기)
https://ordinary-code.tistory.com/76


1. CORS

노션에서 자체적으로 제공하는 SDK 가 있다. 이름은 @notionhq/client.
문제는 이걸 이용해 브라우저에서 다이렉트로 요청을 꽂을 수가 없다.
요청이 모듈을 통해서 가기 때문에 프록시 설정을 해도 먹히지 않는다.

그래서 자체적으로 요청하는 방법을 택했다.
vue.config.js 파일에서 proxy 설정을 해주고 요청을 보내면 될 거라고 생각했다.

devServer: {
    proxy: {
      "api.notion.com/": {
        target: "https://api.notion.com/v1/pages",
        changeOrigin: true,
        secure: false,
        ws: false,
      },
      "/api": {
        target: "https://api.notion.com/v1/pages",
        changeOrigin: true,
        secure: false,
        ws: false,
      },
	  "/": {
        target: "https://api.notion.com/v1/pages",
        changeOrigin: true,
        secure: false,
        ws: false,
      },  
    },
  },

안된다.

요청 url, 설정을 수도없이 바꾸고, axios, fetch 돌려가면서 해봐도 CORS 해결이 안됐다. 파라미터를 잘못 넣었나 싶어 postman 으로 테스트하니 열받게 또 잘 받아온다.

크롬 탭이 스무 개, 서른 개가 넘어가도록 구글을 뒤져보니 노션 API를 이용한 요청은 브라우저에서 다이렉트로 꽂을 수 없다고 한다.

VUE CLI 로 만든 Vue 서버도 서버 아닌가?

해결법으로 세 가지 정도를 떠올렸는데,

  1. 서버 열고, 서버 거쳐서 요청하기
  2. 중개인 역할의 CORS 해결해주는 도메인에 요청
  3. CORS 해결 라이브러리 사용

2번은 짜치는 느낌이고, 3번은 왠지 안땡겨서 간단하게 node.js http 모듈 이용해서 서버를 열기로 했다.

** 진행하면서 안건데 Vue CLI 자체적으로 dotenv 지원해줘서 따로 설치할 필요가 없다.

2. 서버 열기 & 프록시 설정

서버 여는건 아래 글을 참조했다.
https://dev.to/alexeagleson/how-to-connect-a-react-app-to-a-notion-database-51mc

순서는 다음과 같음

1. Vue CLI 루트 폴더 밖에 서버 폴더를 만들어준다.

> mkdir server
> cd server
> npm init -y
> npm install -D typescript @types/node 
> npx tsc --init
> mkdir src
> touch src/server.ts

2. tsconfig 로 이동해 아래 항목을 추가.

"outDir": "./dist"

-> server.ts 를 실행하면 루트 경로의 dist 디렉토리에 server.js 를 맹글어준다.

3. axios 와 dotenv 를 설치.

> npm i axios dotenv

4. 서버 파일 작성

나는 아래와 같이 작성했다. 개같이 부활하며 작성한 결과물인데 시행착오 과정 기록이 없네,,

import http from "http";
import dotenv from "dotenv";
import axios from "axios";
dotenv.config();

const host = "localhost";
const port = 8000;

const NOTION_ACCESS_KEY = process.env.NOTION_ACCESS_KEY;
const NOTION_DB_ID = process.env.NOTION_DB_ID;

const server = http.createServer(async (req, res) => {
  // Avoid CORS errors
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader("Content-Type", "application/json");
  switch (req.url) {
    // Will respond to queries to the domain root (like http://localhost/)
    case "/":
      res.writeHead(200);
      res.end(JSON.stringify({ data: "success" }));
      break;
    case "/notion/database/read":
      axios
        .post(
          `https://api.notion.com/v1/databases/${NOTION_DB_ID}/query`,
          {},
          {
            headers: {
              Authorization: `Bearer ${NOTION_ACCESS_KEY}`,
              "Notion-Version": "2022-02-22",
            },
          }
        )
        .then((result: any) => {
          const contents = result.data.results;
          res.writeHead(200);
          res.end(JSON.stringify({ data: contents }));
        });
      break;
    case "/notion/database/write":
      const buffers = [];
      for await (const chunk of req) {
        buffers.push(chunk);
      }
      const data = JSON.parse(Buffer.concat(buffers).toString());
      axios
        .post(
          `https://api.notion.com/v1/pages`,
          {
            parent: {
              type: "database_id",
              database_id: "312a8185681843a2a6d0fad4d03d9b59",
            },
            properties: {
              id: {
                title: [
                  {
                    text: {
                      content: data.id,
                    },
                  },
                ],
              },
              mood: {
                rich_text: [
                  {
                    text: {
                      content: data.mood,
                    },
                  },
                ],
              },
              sleep: {
                rich_text: [
                  {
                    text: {
                      content: data.sleep,
                    },
                  },
                ],
              },
            },
          },
          {
            headers: {
              Authorization: `Bearer ${NOTION_ACCESS_KEY}`,
              "Notion-Version": "2022-02-22",
            },
          }
        )
        .then((result: any) => {
          const contents = result.data.results;
          res.writeHead(200);
          res.end(JSON.stringify({ data: contents }));
        });
      break;
    default:
      res.writeHead(404);
      res.end(JSON.stringify({ error: "Resource not found" }));
  }
});

server.listen(port, host, () => {
  console.log(`Server is running on http://${host}:${port}`);
});

5. 서버 실행 테스트

> npx tsc && node dist/server.js`

이걸로 실행해도 무방한데, 나는 package.json 에 start 스크립트를 넣어 사용했다.
npm run start 로 서버를 실행시킬 수 있다.

package.json

...
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "npx tsc && node dist/server.js"
  },

핫리로드나 nodemon 이 없는게 이렇게 불편한 줄 몰랐다. 또 Express 랑 다르게 req 에서 바로 헤더를 읽어올 수 없다. 버퍼파일을 읽어서 문자열로 변환하고 다시 오브젝트로 변환해줘야 한다. Node.js 공식문서를 참조했다.

fetch 도 안된다. 브라우저에서 사용할 경우 브라우저가 알아서 해석해주지만, node.js 에서 이용하려면 별도의 모듈을 통해야 한다. node-fetch 를 사용하면 되는데, ts 환경에서 하려니 또 뭐가 잘 안됐고, 간단하게 테스트 해보려던 일이 너무 장황해져 지쳐있었다. 그냥 axios 를 사용했다.

const buffers = [];
      for await (const chunk of req) {
        buffers.push(chunk);
      }
      const data = JSON.parse(Buffer.concat(buffers).toString());

[참조]
https://nodejs.org/ko/docs/guides/anatomy-of-an-http-transaction/

3. 클라이언트측에서 요청 보내기

서버 실행을 확인했으면 클라이언트에서 서버 측으로 요청을 보낸다.
이때는 #1번에서와 달리 프록시를 우리가 만든 서버측으로 돌려줘야한다.

vue.config.js

devServer: {
    proxy: {
      "/notion": {
        target: "http://localhost:8000",
        changeOrigin: true,
        secure: false,
        ws: false,
      },
    },
  },

요청은 요런식으로 하면 된다.

const getDBData = async () => {
  const data = await fetch("/notion/database/read").then((response) =>
    response.json()
  );
  return data;
};

const writeData = async (id, mood, sleep) => {
  await fetch("/notion/database/write", {
    method: "POST",
    body: JSON.stringify({
      id: id,
      mood: mood,
      sleep: sleep,
    }),
  });
};
export { getDBData, writeData };

그러면 감격스럽게도

제대로 읽어온 내 데이터들과,

제대로 꽂힌 요청들을 확인할 수 있다.

아직도 삽질중인 분이 계신다면 아래 레포의 app4 를 참조해보세용

https://github.com/0hhanum/VuePractice/tree/master/app4

어휴 내 일요일~

profile
https://github.com/0hhanum

2개의 댓글

comment-user-thumbnail
2022년 8월 6일

안녕하세요! 저도 노마드 코더 영상보고 간단해보여서 시작한 프로젝튼데... 제 실력으론 너무 어렵더군요.. 선생님은 혹시 그럼 cloudflare workers로 serverless-api를 안 만드신건가요?? 저는 ~dev url을 들어가면 제 노션 페이지가 json형식으로 전달되긴 하는데 이걸 이용해서 제가 python 웹페이지를 제작하려고 하는데 진전이 잘안되더라구요...

1개의 답글