Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

Comments API を構築する

最終更新 Markdown で表示Agent セットアップ

このチュートリアルでは、D1 と Hono を使い、ブログのコメントを保存・取得する JSON API を構築します。D1 データベースを作成し、スキーマを定義し、データベースの読み書きを行う GETPOST エンドポイントを接続します。

前提条件

  1. Cloudflare アカウント に登録します。
  2. Node.js をインストールします。

Node.js のバージョンマネージャー

権限の問題を避け、Node.js のバージョンを切り替えられるよう、Voltanvm などの Node バージョンマネージャーを使います。このガイドの後半で説明する Wrangler には、Node バージョン 16.17.0 以降が必要です。

1. 新しい Worker プロジェクトを作成する

  1. 次のコマンドを実行し、d1-comments-api という名前の新しいプロジェクトを作成します。

    npm create cloudflare@latest -- d1-comments-api

    セットアップでは、次のオプションを選びます。

    • What would you like to start with? では、Hello World example を選びます。
    • Which template would you like to use? では、Worker only を選びます。
    • Which language do you want to use? では、TypeScript を選びます。
    • Do you want to use git for version control? では、Yes を選びます。
    • Do you want to deploy your application? では、No を選びます(デプロイ前にいくつか変更します)。
  2. プロジェクトディレクトリに移動します。

    cd d1-comments-api

2. Hono をインストールする

Workers 上で API を構築するための軽量 Web フレームワーク Hono をインストールします。

npm i hono

3. データベースを作成する

  1. Wrangler で新しい D1 データベースを作成します。

    npx wrangler@latest d1 create d1-comments-api
  2. Would you like Wrangler to add it on your behalf? と聞かれたら Yes を選びます。Wrangler 設定ファイルに DB バインディングが自動で追加されます。

    Wrangler 設定ファイルに d1_databases バインディングとプロジェクト設定全体が入っていることを確認します。

    {
      "$schema": "./node_modules/wrangler/config-schema.json",
      "name": "d1-comments-api",
      "main": "src/index.ts",
      // Set this to today's date
      "compatibility_date": "2026-09-20",
      "d1_databases": [
        {
          "binding": "DB",
          "database_name": "d1-comments-api",
          "database_id": "<YOUR_DATABASE_ID>"
        }
      ]
    }
    name = "d1-comments-api"
    main = "src/index.ts"
    # Set this to today's date
    compatibility_date = "2026-09-20"
    
    [[d1_databases]]
    binding = "DB" # available in your Worker on env.DB
    database_name = "d1-comments-api"
    database_id = "<YOUR_DATABASE_ID>"

    <YOUR_DATABASE_ID> を、wrangler d1 create コマンドが出力した ID に置き換えます。

バインディング を使うと、Worker はコード内の変数名で D1 データベース、KV 名前空間、R2 バケットなどのリソースにアクセスできます。D1 データベースには Worker 内の env.DB からアクセスします。

4. スキーマを作成してデータベースにシードする

  1. 次の内容で schemas/schema.sql ファイルを作成します。

    DROP TABLE IF EXISTS comments;
    CREATE TABLE IF NOT EXISTS comments (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      author TEXT NOT NULL,
      body TEXT NOT NULL,
      post_slug TEXT NOT NULL
    );
    CREATE INDEX idx_comments_post_slug ON comments (post_slug);
    
    -- Optionally, uncomment the below query to insert seed data
    -- INSERT INTO comments (author, body, post_slug) VALUES ('Kristian', 'Great post!', 'hello-world');
  2. まずローカルデータベースにスキーマを適用します。

    npx wrangler d1 execute d1-comments-api --local --file schemas/schema.sql
  3. テーブルがローカルに作成されたことを確認します。

    npx wrangler d1 execute d1-comments-api --local --command "SELECT name FROM sqlite_schema WHERE type = 'table'"
    ┌──────────┐
    │ name     │
    ├──────────┤
    │ comments │
    └──────────┘
  4. スキーマに問題がなければ、リモート(本番)データベースに適用します。

    npx wrangler d1 execute d1-comments-api --remote --file schemas/schema.sql

5. Hono アプリケーションを初期化する

src/index.ts の内容を次のコードに置き換えます。型付きの Bindings インターフェイスを持つ Hono アプリケーションを用意し、env.DBD1Database として正しく型付けされるようにします。

import { Hono } from "hono";

const app = new Hono();

app.get("/api/posts/:slug/comments", async (c) => {
	// Do something and return an HTTP response
	// Optionally, do something with c.req.param("slug")
});

app.post("/api/posts/:slug/comments", async (c) => {
	// Do something and return an HTTP response
	// Optionally, do something with c.req.param("slug")
});

export default app;
import { Hono } from "hono";

type Bindings = {
	DB: D1Database;
};

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

app.get("/api/posts/:slug/comments", async (c) => {
	// Do something and return an HTTP response
	// Optionally, do something with c.req.param("slug")
});

app.post("/api/posts/:slug/comments", async (c) => {
	// Do something and return an HTTP response
	// Optionally, do something with c.req.param("slug")
});

export default app;

6. コメントを取得する

指定した投稿のコメントを取得する GET エンドポイントのロジックを追加します。D1 の Workers Binding API を使い、パラメーター付きクエリを準備して実行します。

app.get("/api/posts/:slug/comments", async (c) => {
	const { slug } = c.req.param();
	const { results } = await c.env.DB.prepare(
		"SELECT * FROM comments WHERE post_slug = ?",
	)
		.bind(slug)
		.run();
	return c.json(results);
});
app.get("/api/posts/:slug/comments", async (c) => {
	const { slug } = c.req.param();
	const { results } = await c.env.DB.prepare(
		"SELECT * FROM comments WHERE post_slug = ?",
	)
		.bind(slug)
		.run();
	return c.json(results);
});

このコードは、prepare でパラメーター付きステートメントを作成し、bind で slug の値を安全に渡し(SQL インジェクションを防ぎ)、run でクエリを実行します。

7. コメントを挿入する

新しいコメントを作成する POST エンドポイントを追加します。行を挿入する前にリクエストボディを検証します。

app.post("/api/posts/:slug/comments", async (c) => {
	const { slug } = c.req.param();
	const { author, body } = await c.req.json();

	if (!author) return c.text("Missing author value for new comment", 400);
	if (!body) return c.text("Missing body value for new comment", 400);

	const { success } = await c.env.DB.prepare(
		"INSERT INTO comments (author, body, post_slug) VALUES (?, ?, ?)",
	)
		.bind(author, body, slug)
		.run();

	if (success) {
		c.status(201);
		return c.text("Created");
	} else {
		c.status(500);
		return c.text("Something went wrong");
	}
});
app.post("/api/posts/:slug/comments", async (c) => {
	const { slug } = c.req.param();
	const { author, body } = await c.req.json<{
		author: string;
		body: string;
	}>();

	if (!author) return c.text("Missing author value for new comment", 400);
	if (!body) return c.text("Missing body value for new comment", 400);

	const { success } = await c.env.DB.prepare(
		"INSERT INTO comments (author, body, post_slug) VALUES (?, ?, ?)",
	)
		.bind(author, body, slug)
		.run();

	if (success) {
		c.status(201);
		return c.text("Created");
	} else {
		c.status(500);
		return c.text("Something went wrong");
	}
});

8.(任意)CORS を追加する

別オリジンのフロントエンドからこの API を呼び出す場合は、CORS ミドルウェアを追加します。Hono から cors モジュールをインポートし、ルートより前に追加します。

import { Hono } from "hono";
import { cors } from "hono/cors";

const app = new Hono();
app.use("/api/*", cors());
import { Hono } from "hono";
import { cors } from "hono/cors";

type Bindings = {
	DB: D1Database;
};

const app = new Hono<{ Bindings: Bindings }>();
app.use("/api/*", cors());

/api/* へのリクエストでは、Hono が API のレスポンスに CORS ヘッダーを自動で生成して追加します。

9. アプリケーションをデプロイする

  1. Cloudflare アカウントにログインします(まだの場合)。

    npx wrangler whoami

    ログインしていない場合、Wrangler がログインを求めます。

  2. Worker をデプロイします。

    npx wrangler deploy
  3. コメントを挿入してから取得し、API をテストします。

    # Replace <YOUR_SUBDOMAIN> with your workers.dev subdomain
    curl -X POST https://d1-comments-api.<YOUR_SUBDOMAIN>.workers.dev/api/posts/hello-world/comments \
      -H "Content-Type: application/json" \
      -d '{"author": "Kristian", "body": "Great post!"}'
    Created
    curl https://d1-comments-api.<YOUR_SUBDOMAIN>.workers.dev/api/posts/hello-world/comments
    [
      {
        "id": 1,
        "author": "Kristian",
        "body": "Great post!",
        "post_slug": "hello-world"
      }
    ]

完全な例

すべてのルートと CORS 対応を含む、完成版の src/index.ts です。

import { Hono } from "hono";
import { cors } from "hono/cors";

const app = new Hono();
app.use("/api/*", cors());

app.get("/api/posts/:slug/comments", async (c) => {
	const { slug } = c.req.param();
	const { results } = await c.env.DB.prepare(
		"SELECT * FROM comments WHERE post_slug = ?",
	)
		.bind(slug)
		.run();
	return c.json(results);
});

app.post("/api/posts/:slug/comments", async (c) => {
	const { slug } = c.req.param();
	const { author, body } = await c.req.json();

	if (!author) return c.text("Missing author value for new comment", 400);
	if (!body) return c.text("Missing body value for new comment", 400);

	const { success } = await c.env.DB.prepare(
		"INSERT INTO comments (author, body, post_slug) VALUES (?, ?, ?)",
	)
		.bind(author, body, slug)
		.run();

	if (success) {
		c.status(201);
		return c.text("Created");
	} else {
		c.status(500);
		return c.text("Something went wrong");
	}
});

export default app;
import { Hono } from "hono";
import { cors } from "hono/cors";

type Bindings = {
	DB: D1Database;
};

const app = new Hono<{ Bindings: Bindings }>();
app.use("/api/*", cors());

app.get("/api/posts/:slug/comments", async (c) => {
	const { slug } = c.req.param();
	const { results } = await c.env.DB.prepare(
		"SELECT * FROM comments WHERE post_slug = ?",
	)
		.bind(slug)
		.run();
	return c.json(results);
});

app.post("/api/posts/:slug/comments", async (c) => {
	const { slug } = c.req.param();
	const { author, body } = await c.req.json<{
		author: string;
		body: string;
	}>();

	if (!author) return c.text("Missing author value for new comment", 400);
	if (!body) return c.text("Missing body value for new comment", 400);

	const { success } = await c.env.DB.prepare(
		"INSERT INTO comments (author, body, post_slug) VALUES (?, ?, ?)",
	)
		.bind(author, body, slug)
		.run();

	if (success) {
		c.status(201);
		return c.text("Created");
	} else {
		c.status(500);
		return c.text("Something went wrong");
	}
});

export default app;

次のステップ

役に立ちましたか?