Skip to content
Cloudflare Docs

Build a Comments API

Last reviewed: 3 days ago

In this tutorial, you will use D1 and Hono to build a JSON API that stores and retrieves comments for a blog. You will create a D1 database, define a schema, and wire up GET and POST endpoints that read from and write to the database.

Prerequisites

  1. Sign up for a Cloudflare account.
  2. Install Node.js.

Node.js version manager

Use a Node version manager like Volta or nvm to avoid permission issues and change Node.js versions. Wrangler, discussed later in this guide, requires a Node version of 16.17.0 or later.

1. Create a new Worker project

  1. Create a new project named d1-comments-api by running:

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

    For setup, select the following options:

    • For What would you like to start with?, choose Hello World example.
    • For Which template would you like to use?, choose Worker only.
    • For Which language do you want to use?, choose TypeScript.
    • For Do you want to use git for version control?, choose Yes.
    • For Do you want to deploy your application?, choose No (we will be making some changes before deploying).
  2. Move into the project directory:

    Terminal window
    cd d1-comments-api

2. Install Hono

Install Hono, a lightweight web framework for building APIs on Workers:

Terminal window
npm i hono

3. Create a database

  1. Create a new D1 database with Wrangler:

    Terminal window
    npx wrangler@latest d1 create d1-comments-api
  2. When prompted Would you like Wrangler to add it on your behalf?, select Yes. This automatically adds the DB binding to your Wrangler configuration file.

    Confirm that your Wrangler configuration file contains the d1_databases binding and the full project configuration:

    {
    "$schema": "./node_modules/wrangler/config-schema.json",
    "name": "d1-comments-api",
    "main": "src/index.ts",
    // Set this to today's date
    "compatibility_date": "2026-03-18",
    "d1_databases": [
    {
    "binding": "DB",
    "database_name": "d1-comments-api",
    "database_id": "<YOUR_DATABASE_ID>"
    }
    ]
    }

    Replace <YOUR_DATABASE_ID> with the ID output by the wrangler d1 create command.

Bindings allow your Workers to access resources, like D1 databases, KV namespaces, and R2 buckets, using a variable name in code. Your D1 database is accessible in your Worker on env.DB.

4. Create a schema and seed the database

  1. Create a schemas/schema.sql file with the following contents:

    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. Run the schema against your local database first:

    Terminal window
    npx wrangler d1 execute d1-comments-api --local --file schemas/schema.sql
  3. Verify the table was created locally:

    Terminal window
    npx wrangler d1 execute d1-comments-api --local --command "SELECT name FROM sqlite_schema WHERE type = 'table'"
    ┌──────────┐
    │ name │
    ├──────────┤
    │ comments │
    └──────────┘
  4. Once you are satisfied with the schema, apply it to your remote (production) database:

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

5. Initialize the Hono application

Replace the contents of src/index.ts with the following code. This sets up a Hono application with a typed Bindings interface so that env.DB is correctly typed as a D1Database:

JavaScript
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;

6. Query comments

Add the logic for the GET endpoint to retrieve comments for a given post. This uses the D1 Workers Binding API to prepare and execute a parameterized query:

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

The code uses prepare to create a parameterized statement, bind to safely pass the slug value (preventing SQL injection), and run to execute the query.

7. Insert comments

Add the POST endpoint to create new comments. This validates the request body before inserting a row:

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

8. (Optional) Add CORS support

If you plan to call this API from a front-end application on a different origin, add CORS middleware. Import the cors module from Hono and add it before your routes:

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

When you make requests to /api/*, Hono will automatically generate and add CORS headers to responses from your API.

9. Deploy your application

  1. Log in to your Cloudflare account (if you have not already):

    Terminal window
    npx wrangler whoami

    If you are not logged in, Wrangler will prompt you to log in.

  2. Deploy your Worker:

    Terminal window
    npx wrangler deploy
  3. Test the API by inserting and then retrieving a comment:

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

Full example

The complete src/index.ts with all routes and CORS support:

JavaScript
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;

Next steps