INTEGRITY Документация

Создание API для комментариев

В этом руководстве вы будете использовать D1 и Hono чтобы создать JSON API, который сохраняет и получает комментарии для блога. Вы создадите базу данных D1, определите схему и настроите GET и POST конечные точки, которые читают из базы данных и записывают в неё.

Предварительные требования

  1. Зарегистрируйтесь для получения Аккаунт Cloudflare.
  2. Установка Node.js.

менеджер версий Node.js

Используйте менеджер версий Node, например Volta или nvm чтобы избежать проблем с правами доступа и переключать версии Node.js. Wrangler, о котором пойдёт речь далее в этом руководстве, требует версию Node 16.17.0 или более поздней версии.

1. Создайте новый проект Worker

  1. Создайте новый проект с именем d1-comments-api выполнив:

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

    Для настройки выберите следующие параметры:

    • Для С чего вы хотите начать?, выберите Hello World example.
    • Для Какой шаблон вы хотите использовать?, выберите Worker only.
    • Для Какой язык вы хотите использовать?, выберите TypeScript.
    • Для Хотите использовать git для контроля версий?, выберите Yes.
    • Для Хотите развернуть приложение?, выберите No (мы внесём некоторые изменения перед развёртыванием).
  2. Перейдите в каталог проекта:

    cd d1-comments-api

2. Установите Hono

Установка Hono, легковесный веб-фреймворк для создания API на Workers:

npm i hono

3. Создайте базу данных

  1. Создайте новую базу данных D1 с помощью Wrangler:

    npx wrangler@latest d1 create d1-comments-api
  2. При появлении запроса Would you like Wrangler to add it on your behalf?, выберите Yes. Это автоматически добавляет DB привязку в конфигурационный файл Wrangler.

    Убедитесь, что конфигурационный файл 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-08-28",
      "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-08-28"
    
    [[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 команда.

Bindings позволяют вашим Workers обращаться к ресурсам, таким как базы данных D1, KV namespaces и бакеты 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 с помощью следующего кода. Это настраивает приложение Hono с типизированным Bindings интерфейс, чтобы env.DB корректно типизируется как D1Database:

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 middleware. Импортируйте cors модуль из Hono и добавьте его перед своими маршрутами:

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 будет автоматически создавать и добавлять заголовки CORS к ответам вашего API.

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"
      }
    ]

Полный пример

Полный src/index.ts со всеми маршрутами и поддержкой CORS:

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;

Следующие шаги