0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NotionAPI×Hono×cloudflareでサクッとAPI構築

0
Last updated at Posted at 2025-12-05

初めに

NotionAPIはDB(データベース)のようにサクッとデータを取得することが可能で、さらに1レコードに対してノート(記事コンテンツ)も取得できます。ブログ記事を更新する画面実装も必要ないので、手軽にブログサイトを構築できます。ただデータを取得するのに少し癖がありますが。

2025/9/3 からNotionAPIの仕様が変更されたので、変更バージョンで説明していきます。

Notoin画面でDBとテーブルを作成

Notionの登録(無料)が必要になります。
https://www.notion.com/ja

1、チームスペース作成

ログイン後、新規チームスペースを作成します。プライベートでも可能ですが、今回はチームスペースで作成します↓

003530.png

今後の権限などを考えクローズドで作成します。1人の場合、オープンまたはデフォルトでも大丈夫です。名前は任意になります↓

004124.png

2、チームスペース内で新規ページ追加(DBやテーブル的なもの)

image.png

空のデータベースを選択↓
image.png

以下のように表示されるので、テーブル名や項目を設定していきます↓
image.png

今回はブログを想定しているので、以下の項目で設定していきます↓

Title  
Slug         テキスト
Category     選択(例:カテゴリ1,カテゴリ2,カテゴリ3)
Status      選択(draft,published,private)
PublishedAt  時間(公開日)
UpdatedAt   時間(更新日)
Description  テキスト

任意のテーブル名「blog」を入力し、項目に値を追加したイメージは以下になります↓

image.png

コンテンツ(ノート)を作成する場合は、以下のようにTitle項目の値にカーソルを当て「開く」をクリック↓

image.png

コンテンツ(ノート)画面になるので、こちらでメイン画像(ヘッダー画像)や記事内容を記載していきますが、コンテンツ取得まで説明すると長くなるので省略します(今回は項目の値を取得するまで説明します)↓

image.png

3、インテグレーション作成

レコードも作成できたので、次は以下のURLでインテグレーションを作成します(APIキー作成)↓
https://www.notion.so/profile/integrations

image.png

インテグレーション名は任意で、作成したアカウントのワークスペースを選択、種類は内部にして、保存します↓
image.png

インテグレーション設定を押下します↓

image.png

デフォルト設定はAPIでの更新・挿入・ユーザー情報を読み取る権限があるので今回はなしにします。必要に応じて機能から設定してください。また内部インテグレーションシークレットがAPIキーになるので、後ほど.envに設定していきます↓

image.png
image.png

次に、アクセスからAPIキーとチームスペースを紐づけします。アクセス権限を編集を押下します↓
image.png

先ほど作成したチームスペース(データベース)を選択して保存します↓
image.png

これでNotion側の設定が終わったので、次はHonoを利用してAPIを実装していきます。

Hono実装

今回はnpmでプロジェクトを構築するので、nodeのインストールが事前に必要です(nodeインストール手順は省略します)。

1.プロジェクト構築

以下のコマンドでプロジェクト(notion-api)を作成し、必要なライブラリをインストールします↓

npm create hono@latest notion-api
✔ Which template do you want to use? cloudflare-workers
✔ Do you want to install project dependencies? Yes
✔ Which package manager do you want to use? npm

npm install @notionhq/client
npm install --save-dev @cloudflare/workers-types

Honoとは関係ないですが、以下もインストールします(一度だけ使用するスクリプトファイル用)↓

# dotenv本体をインストール
npm install dotenv

# Node.jsの型定義(pathやprocessを使うために必要)を開発用としてインストール
npm install --save-dev @types/node

2.tsconfig.jsonに"types" 追記

console.logを使用するために"types"を追記します↓

tsconfig.json
{
  "compilerOptions": {
    "target": "ESNext",
    ・・・省略
    "types": ["@cloudflare/workers-types"],
    "jsx": "react-jsx",
    "jsxImportSource": "hono/jsx"
  }
}

3.env作成

NOTION_DATA_SOURCE_IDは後ほど取得するので、一旦空にします↓

.env
# Notion API Configuration
# API キー(Integration の設定ページで取得)
NOTION_API_KEY=XXXX

# データベース ID(32文字のID)
# データベース URL の https://notion.so/xxxxx の部分(クエリパラメータより前)
NOTION_DATABASE_ID=XXXX

# データソース ID(API v2025-09-03 以降で必要)
# 取得方法: npm run get-datasource-id を実行
NOTION_DATA_SOURCE_ID=

# APIのトークン
SERVICE_SECRET=test

NOTION_DATABASE_IDは以下の青い部分になります↓
(※水色の部分はIDではないので注意が必要です)

スクリーンショット 2025-12-05 020132.png

4.NOTION_DATA_SOURCE_IDを取得するファイルを作成

NOTION_DATABASE_IDを使用して、NOTION_DATA_SOURCE_IDを取得します。
そのため取得は一度のみ行います(以後、.envに設定すれば取得する必要がないため)

  • NOTION_DATABASE_ID → NOTION_DATA_SOURCE_IDを取得するために使用
  • NOTION_DATA_SOURCE_ID → 全レコードを取得するために使用

以下がNOTION_DATA_SOURCE_IDを取得するスクリプトになります↓

プロジェクト名/scripts/get-datasource-id.ts
/**
 * Notion データベース ID からデータソース ID を取得するスクリプト
 *
 * 使用方法:
 * npx tsx scripts/get-datasource-id.ts
 */

import { config } from "dotenv";
import { Client } from "@notionhq/client";
import * as path from "path";

// .env.local ファイルを読み込む
config({ path: path.resolve(process.cwd(), ".env") });

// 環境変数から取得
const NOTION_API_KEY = process.env.NOTION_API_KEY;
const NOTION_DATABASE_ID = process.env.NOTION_DATABASE_ID;

if (!NOTION_API_KEY) {
  console.error("❌ NOTION_API_KEY が設定されていません");
  process.exit(1);
}

if (!NOTION_DATABASE_ID) {
  console.error("❌ NOTION_DATABASE_ID が設定されていません");
  process.exit(1);
}

const notion = new Client({
  auth: NOTION_API_KEY,
});

async function getDataSourceId() {
  try {
    console.log("📊 データベース情報を取得中...");
    console.log(`データベース ID: ${NOTION_DATABASE_ID}\n`);

    // データベース情報を取得
    const database = await notion.databases.retrieve({
      database_id: NOTION_DATABASE_ID!,
    });

    console.log("✅ データベース情報:");
    console.log(
      `  タイトル: ${
        (database as any).title?.[0]?.plain_text || "(タイトルなし)"
      }`
    );

    // データソース情報を取得
    if ("data_sources" in database && Array.isArray(database.data_sources)) {
      const dataSources = database.data_sources;

      console.log(`\n📁 データソース数: ${dataSources.length}`);

      dataSources.forEach((ds: any, index: number) => {
        console.log(`\nデータソース ${index + 1}:`);
        console.log(`  データソース ID: ${ds.id}`);
        console.log(`  タイプ: ${ds.type || "database"}`);
      });

      if (dataSources.length > 0) {
        const firstDataSource = dataSources[0];
        console.log("\n" + "=".repeat(60));
        console.log("📝 .env に追加してください:");
        console.log("=".repeat(60));
        console.log(`NOTION_DATA_SOURCE_ID=${firstDataSource.id}`);
        console.log("=".repeat(60));
      }
    } else {
      console.log("\n⚠️  データソース情報が見つかりません");
      console.log(
        "このデータベースは v2025-09-03 に対応していない可能性があります"
      );
    }
  } catch (error: any) {
    console.error("\n❌ エラーが発生しました:");

    if (error.code === "object_not_found") {
      console.error("\nデータベースが見つかりません。以下を確認してください:");
      console.error("1. NOTION_DATABASE_ID が正しいか");
      console.error(
        "2. Notion インテグレーションがデータベースに接続されているか"
      );
    } else if (error.code === "unauthorized") {
      console.error("\nAPI キーが無効です。NOTION_API_KEY を確認してください");
    } else {
      console.error(error.message);
      console.error("\n詳細:", error);
    }
    process.exit(1);
  }
}

getDataSourceId();

スクリプトを実行します↓

npx tsx scripts/get-datasource-id.ts

Need to install the following packages:
tsx@4.21.0
Ok to proceed? (y) y

[dotenv@17.2.3] injecting env (4) from .env -- tip: ⚙️  enable debug logging with { debug: true }

以下が取得したNOTION_DATA_SOURCE_ID情報になるので.envに張り付けます↓

📊 データベース情報を取得中...
データベース ID: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX(NOTION_DATABASE_ID)

✅ データベース情報:
  タイトル: blog

📁 データソース数: 1

データソース 1:
  データソース ID: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX(NOTION_DATA_SOURCE_ID)
  タイプ: database

============================================================
📝 .env に追加してください:
============================================================
NOTION_DATA_SOURCE_ID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX(NOTION_DATA_SOURCE_ID)
============================================================

5.NotionAPI取得ファイル作成

NOTION_DATA_SOURCE_ID を使用してNotionAPI取得実装をしていきます↓

プロジェクト名/src/client.ts
import { Client } from "@notionhq/client";

// 環境変数の型定義(index.tsと共通化してもOK)
export type Bindings = {
  NOTION_API_KEY: string;
  NOTION_DATA_SOURCE_ID: string;
};

/**
 * 環境変数を受け取って Notion Client と ID を返す関数
 */
export const getNotion = (env: Bindings) => {
  // --- 環境変数の検証 ---
  if (!env.NOTION_API_KEY) {
    throw new Error("NOTION_API_KEY が設定されていません");
  }

  if (!env.NOTION_DATA_SOURCE_ID) {
    throw new Error("NOTION_DATA_SOURCE_ID が設定されていません");
  }

  // --- Notion APIクライアントの初期化 ---
  const client = new Client({
    auth: env.NOTION_API_KEY,
    fetch: (url, init) => {
      return fetch(url, init);
    }, // Cloudflare Workersのネイティブfetchを明示的に渡します
  });

  // v2025-09-03 以降対応
  const dataSourceId = env.NOTION_DATA_SOURCE_ID;

  return {
    client,
    dataSourceId,
  };
};

メインのAPI実装の取得条件(filter)は以下にします↓

  • "Title" → 必須
  • "Slug" → 必須
  • "Category" → 必須
  • "PublishedAt" → 必須
  • "Status" → "published"(公開のみ)
プロジェクト名/src/index.ts
import { Hono } from "hono";
import { getNotion } from "./client";

// 環境変数の型定義
type Bindings = {
  NOTION_API_KEY: string;
  NOTION_DATA_SOURCE_ID: string;
  SERVICE_SECRET: string;
};

const app = new Hono<{ Bindings: Bindings }>();

// ルートパス(動作確認用)
app.get("/", (c) => {
  return c.text("Notion API 起動中");
});

// サンプルデータ取得API
app.get("/api", async (c) => {
  // 1. セキュリティチェック
  const authHeader = c.req.header("x-service-secret");
  if (authHeader !== c.env.SERVICE_SECRET) {
    return c.json({ error: "Unauthorized" }, 401);
  }

  // Cloudflareのエッジサーバーやブラウザに対し「このレスポンスは保存するな」と命令(念のため)
  c.header("Cache-Control", "no-store, no-cache, must-revalidate");

  const { searchParams } = new URL(c.req.url);
  const slug = searchParams.get("slug")!;

  // 2. Notion クライアントの初期化
  const { client, dataSourceId } = getNotion(c.env);

  try {
    console.log("Notion API 取得開始");
    // 3. Notion API 実行
    const data = await client.dataSources.query({
      data_source_id: dataSourceId,
      filter: {
        and: [
          {
            property: "Title",
            title: {
              is_not_empty: true,
            },
          },
          {
            property: "Slug",
            rich_text: slug ? { equals: slug } : { is_not_empty: true },
          },
          {
            property: "Status",
            select: {
              equals: "published", // 公開記事のみ取得
            },
          },
          {
            property: "Category",
            select: { is_not_empty: true },
          },
          {
            property: "PublishedAt",
            date: {
              is_not_empty: true,
            },
          },
        ],
      },
      sorts: [
        {
          property: "PublishedAt",
          direction: "descending",
        },
      ],
    });

    if (data.results.length === 0) {
      return c.json({ error: "Not found", message: "取得記事は0件です" }, 404);
    }

    // data.results が Notion から返ってきた全レコードの配列
    const posts = data.results.map((item: any) => {
      const p = item.properties; // 短く書くために変数に入れます

      return {
        id: item.id, // NotionのページID

        // 1. タイトル (Titleプロパティ)
        title: p.Title?.title?.[0]?.plain_text ?? "No Title",

        // 2. テキスト (Slug, Description) -> rich_text配列の中身を取得
        slug: p.Slug?.rich_text?.[0]?.plain_text ?? "",
        description: p.Description?.rich_text?.[0]?.plain_text ?? "",

        // 3. セレクト (Category, Status) -> selectオブジェクトのnameを取得
        category: p.Category?.select?.name ?? null,
        status: p.Status?.select?.name ?? null,

        // 4. 日付 (PublishedAt, UpdatedAt) -> dateオブジェクトのstartを取得
        publishedAt: p.PublishedAt?.date?.start ?? null,
        updatedAt: p.UpdatedAt?.date?.start ?? null,
      };
    });

    // 4. JSONを返す
    return c.json({
      count: posts.length,
      results: posts,
      fetchedAt: new Date().toISOString(),
    });
  } catch (error) {
    console.error(error);
    return c.json({ error: "Notion API Error" }, 500);
  }
});

export default app;

6.ローカル確認

npm run dev

取得APIを実行します↓

curl -X GET -H "x-service-secret: test" "http://localhost:8787/api"
結果.json
{
 "count":2,
 "results":[
  {
   "id":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
   "title":"タイトル2",
   "slug":"title2",
   "description":"タイトル2の説明",
   "category":"カテゴリ3",
   "status":"published",
   "publishedAt":"2025-12-02",
   "updatedAt":"2025-12-02"
  },
  {
   "id":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
   "title":"タイトル1",
   "slug":"title1",
   "description":"タイトル1の説明",
   "category":"カテゴリ2",
   "status":"published",
   "publishedAt":"2025-12-01",
   "updatedAt":"2025-12-02"
   }
 ],
 "fetchedAt":"2025-12-04T18:11:35.404Z"
}

本番確認(cloudflareにデプロイ)

前提でcloudflareに登録が必要になります。
https://www.cloudflare.com/

デプロイから除外するファイル設定(.wranglerignore作成)していきます(自動で判断して除外されるとは思いますが、念のため記載します)↓

.wranglerignore
# scriptsフォルダを除外
scripts/

# その他、不要なファイルがあれば
README.md
.env

GITHUBを使用せずに直接デプロイする場合

ターミナルでcloudflareログイン(認証)していきます↓

npx wrangler login

ブラウザに移動すると思いますので、ブラウザでログインして認証します(Allowを押下)↓
image.png

デプロイします↓

npm run deploy

デプロイだけでは、API実行時エラーになるので本番の環境変数も設定します↓
(今回はコマンドで環境変数を登録しますが、cloudflare画面からでも環境変数の登録は可能です)

npx wrangler secret put NOTION_API_KEY
npx wrangler secret put NOTION_DATA_SOURCE_ID
npx wrangler secret put SERVICE_SECRET

デプロイ後は、発行されたURL(デプロイ後のログ または cloudflareから確認できます)APIを実行します↓

curl -X GET -H "x-service-secret: test" "https://発行されたURL/api"

GITHUBをつかってデプロイする場合

GITHUBでリポジトリを作成してHonoプロジェクトをpushします(pushまでの説明は省略します)。

GITHUBにプッシュしたら自動で更新されるようにしたい(CI/CD)ので、GITHUB ACTIONSを使用します。

1.CloudflareでAPIトークンを発行する

Cloudflare画面の右上の「プロファイル」→「プロフィール」→「APIトークン」→「トークンを作成する」→ Cloudflare Wokersを編集する「テンプレートを使用する」押下↓

image.png

権限から設定していきます。デプロイ時や運用時に『権限不足エラー』で止まらないように、関連機能をまとめて許可したテンプレートの設定になります↓

image.png

権限名 レベル Honoでの必須度 説明
Workers KV Storage 編集 任意 KV(Key-Valueストア)を使用する場合に必要。使わない場合は不要ですが、あっても問題ありません。
Workers スクリプト 編集 【必須】 Honoのプログラムコードをアップロード・更新するために必要。これがないとデプロイできません。
Workers ルート 編集 【必須】 どのURL(例: api.example.com/*)でAPIを動かすかを設定するために必要。
アカウント設定 読み取り 【必須】 デプロイツールがアカウントIDを照合し、正しい場所へデプロイするために必要。
ユーザーの詳細 読み取り 【必須】 トークン所有者の認証情報を確認するために使用されます。
Workers Tail 読み取り 推奨 wrangler tail コマンドでリアルタイムログを確認(デバッグ)する際に使用します。
Workers R2 Storage 編集 任意 R2(画像保存などのストレージ)を使用する場合に必要。
Cloudflare Pages 編集 任意 将来的に「Pages」としてデプロイする場合に使用。Workers構成なら基本不要。
Workers の可観測性 編集 推奨 APIのエラー率やアクセス統計などの分析データを記録・閲覧するために使用します。
その他
(Memberships, Buildsなど)
- 任意 チーム所属確認や新しいビルド機能用。テンプレートに含まれていますが、削除しても基本動作には影響しません。

今回は、そのままテンプレートの権限を使用しますが、必須と推奨以外は必要に応じて削除しても問題ありません。また、そのプロジェクト応じて「アカウント,ユーザー,ゾーン」は適切に設定してください。

次にセキュリティの設定になります↓

image.png

  • アカウントリソース:自身のアカウントを選択
  • ゾーンリソース:カスタムドメインがある場合は、特定ゾーンを選択します。今回はないので一旦すべてのゾーンにします
  • クライアントIPアドレスフィルタリング:設定なし。GITHUB ACTIONSのサーバー(Runner)は、毎回違うIPアドレスで動くため
  • TTL:設定なし。起動する期間が決まっている場合は設定

設定が終わったら「概要に進む」→「APIトークン作成」を押下するとトークンが作成されます。後ほどGITHUB ACTIONSで使用するのでコピーします↓

image.png

2.GITHUBに鍵(APIトークン)を登録する。

pushしたリポジトリ選択「Settings」→「Secrets and variables」 → 「Actions」→「New repository secret」押下します↓

image.png
image.png

  • CLOUDFLARE_API_TOKEN → さっきコピーしたトークン
  • CLOUDFLARE_ACCOUNT_ID → アカウントID(CloudflareダッシュボードのURLの dash.cloudflare.com/ の後ろにある英数字)

の2点を登録し、次はローカルでymlを作成していきます。

3.ymlを作成し再push

ローカルのプロジェクト内に「.github/workflows/deploy.yml」を作成し、再pushします↓

.github/workflows/deploy.yml
name: Deploy to Cloudflare Workers

on:
  push:
    branches:
      - main # mainブランチにプッシュされたら実行

jobs:
  deploy:
    runs-on: ubuntu-latest
    name: Deploy
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: npm ci

      - name: Deploy with Wrangler
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          # NotionAPIなどのシークレットは、
          # Cloudflare側で保存(設定)するため、ここでは設定する必要はありません。

GITHUB ACTIONS画面で、ログを確認します(成功したか確認)↓

image.png

4.Notionの本番用環境変数を登録していきます。

コマンドで登録してもよいのですが、今回はcloudflare画面「コンピューティングとAI」→「Workers & Pages」→「notion-api」→「設定」→変数とシークレット「追加」から登録していきます。すべてシークレットで登録します↓

image.png

5.NotionAPI実行して確認

curl -X GET -H "x-service-secret: test" "https://発行されたURL/api"

終わりに

今回のソースコードは以下に公開しております↓
https://github.com/sato-1234/notion-api

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?