herohoroブログ

Notion APIの公式リファレンスを徹底的に根拠付けしながらブログにtitle,text,dateを一覧にして表示させる方法_Blog learn03



🔄   2023-03-14

いよいよNotion APIを使ってみる回になりました\(^o^)/

Notion APIを使った実装はネットに沢山ありますが、

私はなかなか習得することができずにいました。

原因は、

「何を根拠にしたらそのコードになるのか.....?」

って部分。

公式リファレンスを参照しながら実装していくのはいいものの、

「このコードってリファレンスのどの部分を根拠にしてるの?」

と細かい部分で詰まっていました。

Blog learn用にeasy-notion-blogのコードをなるべくシンプルに実装していく過程で、

「あ、これがあそこに書いてあったサンプルか💡」

「これ、さっきのあの変数とつながってるやつじゃん😀」

といろいろ手探りながらつかめてきました。

今回は

公式リファレンスのどこを根拠に記述しているのか

といった部分にこだわって簡単なDBデータをブログに表示させる過程を解説していきます。

簡単なDB

image block

ブログに表示

image block

title・text・dateタイプの列のデータを表示させていきます\(^o^)/

実装の際に使ったコードはこちらに収納していますー。

No.branchdescriptionリンクPR
89cp6j7NotionAPIからDBのタイトル・テキスト・日付列を表示させる_他_lib/環境変数module/getStaticProps/propshttps://github.com/herohoro/Blog_learn/pull/11#11

https://github.com/herohoro/Blog_learn/wiki

準備

コードについて触れる前に、

Notion上で用意しておくデータベースやAPIを準備しておきますー。

Notionにデータベースを用意
image block

Name列→RenameでTitleに変更

Tags列→Edit propertyから列名TagsをExcerpt→Typeをtextに変更

列追加→列名PropertyをDate→TypeをDateに変更

image block

データベースの名前や各列に記入したテキストはお好みでOK(*´∀`*)

Notion APIを用意

image block

https://developers.notion.com/docs/getting-started#step-1-create-an-integration

データベースとAPIを連携

image block

https://developers.notion.com/docs/getting-started#step-2-share-a-database-with-your-integration

必要な文字列をメモ

データベースID

image block
Step 3: Add an item to a databaseの前に記載有り

🕺🏻
easy-notion-blogではDATABASE_ID という環境変数に入れますー。(あとで触れます)

API トークンキー

Internal Integration Token

image block

🕺🏻
easy-notion-blogではNOTION_API_SECRET という環境変数に入れますー。(これも後で触れます)

概要

用意したNotionデータベースを読み込む大まかな流れ

  1. 環境変数に文字列を登録する
  2. 読み込むデータの型を定義する
  3. 読み込むデータを整頓する
  4. 整頓したデータを活用して表示する

🤔
読み込みたいデータはすぐ使えるってことでもなさそう......

環境変数に文字列を登録する

ローカル環境で行う場合

.env.local ファイルをtop階層に設置

NOTION_API_SECRET=
DATABASE_ID=
.env.local

👆ここに準備の段階で取得した文字列を記入(「”」でくくらずそのまま転記でOK)

Vercelの場合

setting/Environment Variables から登録

image block

code sandboxの場合

サイドバーServer Control Panel/Secret Keys から登録

image block

🤔
環境変数に収納しておくことで、使いたい場所で環境変数を記述すればIDやトークンを伏せたまま使えるってことか......

moduleにして取り出しやすくする

const NOTION_API_SECRET = process.env.NOTION_API_SECRET;
const DATABASE_ID = process.env.DATABASE_ID;

module.exports = {
  NOTION_API_SECRET,
  DATABASE_ID
};
src/lib/sever-containts.js

公式リファレンスではmoduleにせずprocess.env.NOTION_API_SECRET を直接記述したコードだけど、

easy-notion-blogではmoduleにしていざ使う場面では NOTION_API_SECRET と記述するのみ。

環境変数をよく理解できていない私でも

process.env が無いってだけで「これはAPIのトークンキーのことかな」

ってなんとなく把握できたのですごく助かりましたm(_ _)m

読み込むデータの型を定義する

※ 本家ではnotion関連のファイルをlib/notionの中に収納していますが、今回はそこまで複雑な実装はしないのでlibのフォルダ内に入れていきますー

export interface Post {
  PageId: string;
  Title: string;
  Excerpt: string;
  Date: string;
}
src/lib/interfaces.ts

😵
なんでstringなの?????

根拠

image block

https://developers.notion.com/reference/database

公式リファレンスのDatabase objectにある表が手がかり!!!

Type列にある記述がデータの型として定義する文字。

必要なPropertyは......ひとまずpropertiesかな???

image block

Type列に「object」とあるのでいろいろネストされてるっぽい。。。。

propertiesは......
key が列名に表示されている文字列

ってことは、データベースを用意する時に列名にしたTitle/Excerpt/Date のこと。

value はProperty objectを参照せよ

ってことなので、リンクを踏むと......(踏めなかったので普通に開くと.....)

image block

https://developers.notion.com/reference/property-object

image block

Typeはstringってことを確認!!!

export interface Post {
  PageId: string;
  Title: string;👉納得
  Excerpt: string;👉Textじゃないの??rich_textって初耳....
  Date: string;👉納得
}
src/lib/interfaces.ts

Text propertyについては....

Database propertiesの表の下にpropertyの種類ごとに説明がある。

image block
Database propertiesの表の下(Title configurationの下)

👀
rich_text propertyになったのね!

export interface Post {
  PageId: string;👉これはなんだ?
  Title: string;👉納得
  Excerpt: string;👉納得
  Date: string;👉納得
}
src/lib/interfaces.ts

PageIDはDatabaseのデータを取得する時に触れるのでひとまず保留。。。。。

読み込むデータを整頓する

//型定義
import { Post } from "./interfaces";
//環境変数
import { NOTION_API_SECRET, DATABASE_ID } from "./sever-containts";

const { Client } = require("@notionhq/client");
const client = new Client({
  auth: NOTION_API_SECRET
});
export async function getPosts() {
  const params = {
    database_id: DATABASE_ID
  };
  const data = await client.databases.query(params);
  return data.results.map((item) => _buildPost(item));
}
function _buildPost(data) {
  const prop = data.properties;
  const post: Post = {
    PageId: data.id,
    Title: prop.Title.title[0].plain_text,
    Excerpt: prop.Excerpt.rich_text[0].plain_text,
    Date: prop.Date.date.start
  };
  return post;
}
src/lib/client.tsの完成形

いきなり見ても辛いので、少しずつ解読していきます.....💃

Notion APIとSDKを使う
image block

https://developers.notion.com/reference/authentication

予め環境変数をmoduleにしているのでサンプルよりシンプル!

import { NOTION_API_SECRET, DATABASE_ID } from "./sever-containts";

const { Client } = require("@notionhq/client");
const client = new Client({
  auth: NOTION_API_SECRET
});
src/lib/client.tsのAuthenticationに関わる部分

データベースの内容を取得する
image block

Gets a list of Pages contained in the database,

データベースはページ一覧を含んでる.....

image block

データベースのタイトルをクリックすると表示されるページのことを意味している......😲

このpageについても取得できるように型定義しないとな.....

っていうのがさっき保留にしたPageIDの謎でした。

image block

https://developers.notion.com/reference/page#all-pages

export interface Post {
  PageId: string;👉解決!!
  Title: string;👉納得
  Excerpt: string;👉納得
  Date: string;👉納得
}
src/lib/interfaces.ts

データを取得する方向性

今回APIの実装を理解するためになるべくシンプルな記述にすべく、

FilterやSortはひとまず外して実装していきますーーーー🕺🏻🕺🏻🕺🏻🕺🏻🕺🏻🕺🏻🕺🏻

const { Client } = require('@notionhq/client');

const notion = new Client({ auth: process.env.NOTION_API_KEY });

(async () => {
  const databaseId = '897e5a76-ae52-4b48-9fdf-e71f5945d1af';
  const response = await notion.databases.query({
    database_id: databaseId,
	});
  console.log(response);
})();
公式リファレンスにあるサンプルより

こんな感じでシンプルになってしまいました.....😋

このサンプルを元に実装しようとすると.....

//環境変数
import { NOTION_API_SECRET, DATABASE_ID } from "./sever-containts";

const { Client } = require("@notionhq/client");

const client = new Client({
  auth: NOTION_API_SECRET
});

export async function getPosts() {
  const params = {
    database_id: DATABASE_ID
  };
  const data = await client.databases.query(params);

  return data.results.map((item) => _buildPost(item));
}
src/lib/client.ts

returnの部分は複数取得したデータベースの行を1行ずつ仕分けしていく操作をしています。

🤔
_buildPostって何?

取得の動きが詳しく分かる_buildPost関数

easy-notion-blogのlib/client.tsを覗くと思考停止しそうなexport async function がたくさんあり、

なかでも、export async functionで使うためのfunctionが_◯◯◯◯という名前で末尾に登場します。

今回、データベースを取得するために必要な_buildPost関数を追加して

1行ずつ仕分けができるようにしていこうと思います\(^o^)/

return data.results.map((item) => _buildPost(item));
}
//このあと.....
function _buildPost(data) {
  const prop = data.properties;
  const post: Post = {
    PageId: data.id,
    Title: prop.Title.title[0].plain_text,
    Excerpt: prop.Excerpt.rich_text[0].plain_text,
    Date: prop.Date.date.start
  };
  return post;
}
src/lib/client.tsの続き

🤔
data.propertiesって何?
data.propertiesとは....

データベースのPropertyの一部

https://developers.notion.com/reference/database#all-databases

Property
object
id👌PageID
created_time
created_by
last_edited_time
last_edited_by
title
icon
properties👉これ
parent
url
archived

列名として用意したTitle/Excerpt/Dateに記入したデータを取得するには

propertiesの子要素に収納されている💨

Title列のデータは.....

image block

https://developers.notion.com/reference/property-item-object#title-property-values

🤔
property が「title」の中にrich text objectsがあるんだね....

image block

https://developers.notion.com/reference/rich-text

🤔
typeは必ず記載せよってことか.....今回はtitleもtextだねー。

{
  "Name": {👉これが列名に表示されている(サンプルではNameという列名)
    "object": "list",
    "results": [
      {
        "object": "property_item",
        "id": "title",
			// ここからrich text
        "type": "title",👉これがpropertyの種類
        "title": {
          "type": "text",//ここはrich textのProperty type
          "text": {
            "content": "The title",
            "link": null
          },
          "annotations": {
            "bold": false,
            "italic": false,
            "strikethrough": false,
            "underline": false,
            "code": false,
            "color": "default"
          },
          "plain_text": "The title",👉これが列に記入したデータ
          "href": null
				// ここまでrich text
        }
      }
    ],
    "next_cursor": null,
    "has_more": false,
    "type": "property_item",
    "property_item": {
      "id": "title",
      "next_url": null,
      "type": "title",
      "title": {}
    }
  }
}
Title property valuesより

https://developers.notion.com/reference/property-item-object#title-property-values

😋
列名.propertyの種類.plain_text で取得
return data.results.map((item) => _buildPost(item));
}
//このあと.....
function _buildPost(data) {
  const prop = data.properties;
  const post: Post = {
    PageId: data.id,
    Title: prop.Title.title[0].plain_text,//👉納得
    Excerpt: prop.Excerpt.rich_text[0].plain_text,
    Date: prop.Date.date.start
  };
  return post;
}
src/lib/client.tsの続き
😀
1行ずつ取得するから[0]ってなってる

Excerpt列のデータは.....

🤔
textのpropertyはrich text とするってあったよな.....

image block

https://developers.notion.com/reference/property-item-object#rich-text-property-values

🤔
property がrich_text の中にrich text objectsがあるってことか......

{
  "Details": {👉これが列名に表示されている(サンプルではDetailsという列名)
    "object": "list",
    "results": [
      {
        "object": "property_item",
        "id": "NVv%5E",
			// ここからrich text
        "type": "rich_text",👉これがpropertyの種類
        "rich_text": {
          "type": "text",//ここはrich textのProperty typeの種類
          "text": {
            "content": "Some more text with ",
            "link": null
          },
          "annotations": {
            "bold": false,
            "italic": false,
            "strikethrough": false,
            "underline": false,
            "code": false,
            "color": "default"
          },
          "plain_text": "Some more text with ",👉これが列に記入したデータ
          "href": null
			// ここまでがrich text
        }
      },
			// 複数行記載があればリピート
    ],
    "next_cursor": null,
    "has_more": false,
    "type": "property_item",
    "property_item": {
      "id": "NVv^",
      "next_url": null,
      "type": "rich_text",
      "rich_text": {}
    }
  }
}
Rich Text property valuesより

😋
これも 列名.propertyの種類.plain_text で取得
return data.results.map((item) => _buildPost(item));
}
//このあと.....
function _buildPost(data) {
  const prop = data.properties;
  const post: Post = {
    PageId: data.id,
    Title: prop.Title.title[0].plain_text,//👉納得
    Excerpt: prop.Excerpt.rich_text[0].plain_text,//👉納得
    Date: prop.Date.date.start
  };
  return post;
}
src/lib/client.tsの続き

Date列のデータは....

image block

https://developers.notion.com/reference/property-item-object#date-property-values

⚠️
配列(array)ではないから[0]は不要

{
  "Shipment Time": {👉列名
    "object": "property_item",
    "id": "i%3Ahj",
    "type": "date",👉propertyの種類
    "date": {
      "start": "2021-05-11T11:00:00.000-04:00",👉これを取得したい
      "end": null,
      "time_zone": null
    }
  }
}
Date property valuesより

😋
列名.propertyの種類.start で取得

return data.results.map((item) => _buildPost(item));
}
//このあと.....
function _buildPost(data) {
  const prop = data.properties;
  const post: Post = {
    PageId: data.id,
    Title: prop.Title.title[0].plain_text,//👉納得
    Excerpt: prop.Excerpt.rich_text[0].plain_text,//👉納得
    Date: prop.Date.date.start//👉納得
  };
  return post;
}
src/lib/client.tsの続き

😨
取得したDate....2021-05-11T11:00:00.000-04:00 これじゃ見にくい......

見やすくするために....

easy-notion-blogにはblog-helper.tsというファイル内に記述がありました(*´ω`*)(*´ω`*)

export const getDateStr = date => {
  const dt = new Date(date)
  const y = dt.getFullYear()
  const m = ('00' + (dt.getMonth() + 1)).slice(-2)
  const d = ('00' + dt.getDate()).slice(-2)
  return y + '-' + m + '-' + d
}
src/lib/blog-helper.tsより

今回これもlibに追加していざ呼び出す時に使います〜。

完成
//型定義
import { Post } from "./interfaces";
//環境変数
import { NOTION_API_SECRET, DATABASE_ID } from "./sever-containts";

const { Client } = require("@notionhq/client");

const client = new Client({
  auth: NOTION_API_SECRET
});

export async function getPosts() {
  const params = {
    database_id: DATABASE_ID
  };
  // dataにnotionDBの情報を丸っと放り投げる
  const data = await client.databases.query(params);
  // 列名ごと必要な情報を残していく
  return data.results.map((item) => _buildPost(item));
}

function _buildPost(data) {
  const prop = data.properties;
  const post: Post = {
    PageId: data.id,
    // keyは列名 valueはproperty value object
    // property value objectは「property item object」のリファレンス参照
    Title: prop.Title.title[0].plain_text,
    Excerpt: prop.Excerpt.rich_text[0].plain_text,
    Date: prop.Date.date.start
  };
  return post;
}
src/lib/client.ts

整頓したデータを活用して表示する

componentで一旦作っていつでも貼り付けられるように仕込みます。

import * as interfaces from "../lib/interfaces";
import { getDateStr } from "../lib/blog-helpers";
//client.tsで用意したpostデータを取り出していく
// postの中にある.... っていった感じで引数にpostを入れる

export const PostDate = ({ post }) => <div>{getDateStr(post.Date)}</div>;

export const PostTitle = ({ post }) => {
  const postTitle = post.Title ? post.Title : "";
  return <h3>{postTitle}</h3>;
};
export const PostExcerpt = ({ post }) => (
  <div>
    <p>{post.Excerpt ? post.Excerpt : ""}</p>
  </div>
);
src/components/blog-parts.ts

client.tsで作っておいたpost が引っ張りだこ🕸️

postには.....

  • post.Date
  • post.Title
  • post.Excerpt
  • post.PageID

が収納されていて、使いたい場面でpost.〇〇で指定すれば出てきてくれます🧞‍♂️

😀
blog-helpersで調整した日付の表示getDateStrもここで仕事をするんだね~

あとは三項演算子で「あるの?無いの?ハッキリして(# ゚Д゚)」と問いかける。

いざ出陣!!

準備が整ったのでcomponentを使って表示させていきますー。

import { PostDate, PostTitle, PostExcerpt } from "../../components/blog-parts";
import styles from "../../styles/blog.module.css";
import { getPosts } from "../../lib/client";

export async function getStaticProps() {
  // clientにある関数をPenderPostsの引数にして活用
  const posts = await getPosts();

  return {
    props: { posts },
    revalidate: 60
  };
}

const RenderPosts = ({ posts = [] }) => {
  return (
    <div className={styles.container}>
      <div className={styles.mainContent}>
        <p>***main-content***</p>
        <h2>BlogList page</h2>
        <br />
        {""}
        <section>
          <div>
            {/* 変数postsを1つの塊ごと取り出していく */}
            {posts.map((post) => {
              return (
                <div key={post.Date}>
                  <PostDate post={post} />
                  <PostTitle post={post} />
                  <PostExcerpt post={post} />
                </div>
              );
            })}
          </div>
        </section>
        <br />
      </div>
      <div className={styles.subContent}>
        <p>***sub-content***</p>
      </div>
    </div>
  );
};
export default RenderPosts;
src/pages/blog/index.tsx

image block

できたーーーーー\(^o^)/

code sandboxのリンクや追記などはPRのコメントにありますー。

No.branchdescriptionリンクPR
89cp6j7NotionAPIからDBのタイトル・テキスト・日付列を表示させる_他_lib/環境変数module/getStaticProps/propshttps://github.com/herohoro/Blog_learn/pull/11#11

https://github.com/herohoro/Blog_learn/wiki

まとめ

長丁場になってしまったので要点を.....

  1. 環境変数に文字列を登録する
    1. sever-containts.js
  2. 読み込むデータの型を定義する
    1. interfaces.ts
  3. 読み込むデータを整頓する
    1. client.ts
    2. blog-helpers.ts
  4. 整頓したデータを活用して表示する
    1. components/blog-parts.tsx
    2. pages/blog/index.tsx

以上!!!🕺


公式リファレンスをなかなか理解できなかった要因は.....

「ネストされまくりのデータを理解できなかったから」

idと言われてもあっちこっちidが出てくるし、

propertyと言われてもdatabaseでpage propertyがどうこう言い出すしで

表の見方が全然分かりませんでした。(Propertyとpropertiesが別物で説明されてるとかもう.....😭)

でも、

code sandboxで練習用にeasy-notion-blogのコードをピックアップしながら

実装していくうちに点と点がつながってきて、

公式リファレンスの内容も分かってきました。

次は、タイトルをクリックすると記事のページが表示されるように画面遷移を作ってみようと思います\(^o^)/

Twitterでは更新のお知らせを随時行っています

興味ある方はLet'sフォロー★

▼ この記事に興味があったら同じタグから関連記事をのぞいてみてね

Buy Me A Coffee

新着記事を通知したい??


RSSリーダーにatomのリンクを登録すると通知が行くよ🐌

https://herohoro.com/atom

やってみてね(*´ω`*)(*´ω`*)

Twitter Timeline


フォロー大歓迎\(^o^)/