herohoroブログ

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



🔄   2022-08-02

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

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

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

原因は、

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

って部分。

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

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

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

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

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

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

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

今回は

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

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

簡単なDB

画像が読み込まれない場合はページを更新してみてください。

ブログに表示

画像が読み込まれない場合はページを更新してみてください。

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にデータベースを用意
画像が読み込まれない場合はページを更新してみてください。

Name列→RenameでTitleに変更

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

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

画像が読み込まれない場合はページを更新してみてください。

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

Notion APIを用意

画像が読み込まれない場合はページを更新してみてください。

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

データベースとAPIを連携

画像が読み込まれない場合はページを更新してみてください。

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

必要な文字列をメモ

データベースID

画像が読み込まれない場合はページを更新してみてください。
Step 3: Add an item to a databaseの前に記載有り

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

API トークンキー

Internal Integration Token

画像が読み込まれない場合はページを更新してみてください。

🕺🏻
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 から登録

    画像が読み込まれない場合はページを更新してみてください。

    code sandboxの場合

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

    画像が読み込まれない場合はページを更新してみてください。

    🤔
    環境変数に収納しておくことで、使いたい場所で環境変数を記述すれば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なの?????

    根拠

    画像が読み込まれない場合はページを更新してみてください。

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

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

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

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

    画像が読み込まれない場合はページを更新してみてください。

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

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

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

    value はProperty objectを参照せよ

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

    画像が読み込まれない場合はページを更新してみてください。

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

    画像が読み込まれない場合はページを更新してみてください。

    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の種類ごとに説明がある。

    画像が読み込まれない場合はページを更新してみてください。
    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を使う
    画像が読み込まれない場合はページを更新してみてください。

    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に関わる部分

    データベースの内容を取得する
    画像が読み込まれない場合はページを更新してみてください。

    Gets a list of Pages contained in the database,

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

    画像が読み込まれない場合はページを更新してみてください。

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

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

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

    画像が読み込まれない場合はページを更新してみてください。

    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列のデータは.....

    画像が読み込まれない場合はページを更新してみてください。

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

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

    画像が読み込まれない場合はページを更新してみてください。

    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 とするってあったよな.....

    画像が読み込まれない場合はページを更新してみてください。

    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列のデータは....

    画像が読み込まれない場合はページを更新してみてください。

    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

    画像が読み込まれない場合はページを更新してみてください。

    できたーーーーー\(^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フォロー★

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

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


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

    https://herohoro.com/atom

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

    Twitter Timeline


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

    Twitter Timeline


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