INTEGRITY Dokumentace

Vytvoření API pro komentáře

V tomto tutoriálu použijete D1 a Hono k vytvoření JSON API, které ukládá a načítá komentáře pro blog. Vytvoříte databázi D1, definujete schéma a propojíte GET a POST koncové body, které z databáze čtou a zapisují do ní.

Předpoklady

  1. Zaregistrujte si účet Cloudflare.
  2. Nainstalujte Node.js.

Správce verzí Node.js

Použijte správce verzí Node, jako je Volta nebo nvm abyste se vyhnuli problémům s oprávněními a mohli měnit verze Node.js. Wrangler, o kterém se dozvíte dále v této příručce, vyžaduje verzi Node 16.17.0 nebo novější.

1. Vytvořte nový projekt Worker

  1. Vytvořte nový projekt s názvem d1-comments-api spuštěním:

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

    Při nastavení vyberte následující možnosti:

    • Pro S čím byste chtěli začít?, vyberte Hello World example.
    • Pro Jakou šablonu chcete použít?, vyberte Worker only.
    • Pro Jaký jazyk chcete použít?, vyberte TypeScript.
    • Pro Chcete používat git pro správu verzí?, vyberte Yes.
    • Pro Chcete nasadit svou aplikaci?, vyberte No (před nasazením provedeme ještě několik změn).
  2. Přejděte do adresáře projektu:

    cd d1-comments-api

2. Nainstalujte Hono

Nainstalujte Hono, odlehčený webový framework pro vytváření API na Workers:

npm i hono

3. Vytvořte databázi

  1. Vytvoření nové databáze D1 pomocí Wrangleru:

    npx wrangler@latest d1 create d1-comments-api
  2. Po zobrazení výzvy Would you like Wrangler to add it on your behalf?, vyberte Yes. Tím se automaticky přidá DB binding do konfiguračního souboru Wrangler.

    Ověřte, že konfigurační soubor Wrangler obsahuje d1_databases binding a úplnou konfiguraci projektu:

    {
      "$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>"

    Nahraďte <YOUR_DATABASE_ID> s ID, které vypsal wrangler d1 create příkazu.

Bindings umožňují vašim Workers přistupovat k prostředkům, jako jsou databáze D1, KV namespaces a buckety R2, pomocí názvu proměnné v kódu. Vaše databáze D1 je ve Workeru dostupná na env.DB.

4. Vytvořte schéma a naplňte databázi testovacími daty

  1. Vytvořte schemas/schema.sql soubor s následujícím obsahem:

    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. Nejprve spusťte schéma v místní databázi:

    npx wrangler d1 execute d1-comments-api --local --file schemas/schema.sql
  3. Ověřte, že tabulka byla vytvořena lokálně:

    npx wrangler d1 execute d1-comments-api --local --command "SELECT name FROM sqlite_schema WHERE type = 'table'"
    ┌──────────┐
    │ name     │
    ├──────────┤
    │ comments │
    └──────────┘
  4. Jakmile budete se schématem spokojeni, použijte ho na vzdálenou (produkční) databázi:

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

5. Inicializujte aplikaci Hono

Nahraďte obsah src/index.ts s následujícím kódem. Tím se nastaví aplikace Hono s typovanou Bindings rozhraní tak, aby env.DB má správně určen typ jako 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. Dotazujte komentáře

Přidejte logiku pro GET koncový bod pro načtení komentářů k danému příspěvku. Ten používá D1 Workers Binding API k přípravě a spuštění parametrizovaného dotazu:

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);
});

Kód používá prepare k vytvoření parametrizovaného příkazu, bind k bezpečnému předání hodnoty slug (čímž se zabrání SQL injection), a run ke spuštění dotazu.

7. Vložte komentáře

Přidejte POST koncový bod pro vytváření nových komentářů. Ten před vložením řádku ověří tělo požadavku:

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. (Volitelné) Přidejte podporu CORS

Pokud plánujete toto API volat z frontendové aplikace na jiném originu, přidejte CORS middleware. Naimportujte cors modul z Hono a přidejte jej před vaše trasy:

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());

Když odesíláte požadavky na /api/*, Hono automaticky vygeneruje a přidá hlavičky CORS do odpovědí z vašeho API.

9. Nasaďte aplikaci

  1. Přihlaste se ke svému účtu Cloudflare (pokud jste tak ještě neučinili):

    npx wrangler whoami

    Pokud nejste přihlášeni, Wrangler vás vyzve k přihlášení.

  2. Nasaďte svůj Worker:

    npx wrangler deploy
  3. Otestujte API vložením a následným načtením komentáře:

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

Úplný příklad

Kompletní src/index.ts se všemi trasami a podporou 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;

Další kroky