Claude-skill-registry hono-d1-integration
Use this skill whenever the user wants to design, set up, or refactor Cloudflare D1 database usage in a Hono + TypeScript app running on Cloudflare Workers/Pages, including schema management, bindings, query patterns, and data-access structure.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/hono-d1-integration" ~/.claude/skills/majiayu000-claude-skill-registry-hono-d1-integration && rm -rf "$T"
skills/data/hono-d1-integration/SKILL.mdHono + Cloudflare D1 Integration Skill
Purpose
You are a specialized assistant for using Cloudflare D1 as the database layer in a Hono + TypeScript app running on Cloudflare Workers / Pages.
Use this skill to:
- Wire D1 bindings into a Hono app (
)c.env.DB - Design schema and migrations for D1 (via Wrangler)
- Implement query helpers and data-access patterns
- Structure repositories/services on top of D1
- Use prepared statements, parameters, and minimal ORM-style helpers
- Handle migrations and seeding for local/dev/test
- Keep D1 usage runtime-safe and type-friendly
Do not use this skill for:
- Hono app scaffolding → use
hono-app-scaffold - Non-D1 databases (Postgres/MySQL/etc.) → use TypeORM/other DB skills
- R2 or KV storage → use dedicated storage skills (e.g.
)hono-r2-integration
If
CLAUDE.md exists, obey its rules for D1 usage, directories, naming and migration strategy.
When To Apply This Skill
Trigger this skill when the user asks for things like:
- “Use D1 as DB for this Hono API on Cloudflare.”
- “Wire up Hono routes to Cloudflare D1.”
- “Design tables and queries for D1 in this Worker.”
- “Refactor my raw D1 queries into a cleaner structure.”
- “Add migrations/seeding for D1 in this Hono app.”
Avoid this skill when:
- The Hono app is not running on Cloudflare Workers / Pages.
- The project has chosen a different DB for this service (e.g. PlanetScale, Supabase).
Runtime & Binding Assumptions
This skill assumes:
-
The app runs on Cloudflare Workers or Pages Functions.
-
D1 is configured as a binding in
, e.g.:wrangler.toml[[d1_databases]] binding = "DB" database_name = "my_db" database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -
In code, D1 is available as
.c.env.DB
We will type the Env binding and keep DB access explicit and centralized.
Project Structure
This skill works best with a structure like:
src/ app.ts index.ts routes/ v1/ users.routes.ts posts.routes.ts db/ schema.sql migrations/ 0001_init.sql 0002_add_posts.sql d1.ts # D1 helpers and typed Env services/ user.service.ts post.service.ts types/ env.d.ts # Env interface with DB binding
Adjust paths to match the project, but keep D1-related files under a
db/ or infrastructure/ style folder.
Typing Env and D1 Binding
Create a shared Env type for Workers:
// src/types/env.d.ts export interface Env { DB: D1Database; // other bindings e.g. R2, KV, etc. }
Then in routes/services:
import type { Env } from "../types/env"; export type AppContext = { Bindings: Env; };
Usage with Hono:
import { Hono } from "hono"; import type { AppContext } from "./types"; export const app = new Hono<AppContext>();
This ensures
c.env.DB is correctly typed.
D1 Helper Module
Create a small helper for D1 access and typed operations:
// src/db/d1.ts import type { Env } from "../types/env"; export type D1 = D1Database; export function getDb(env: Env): D1 { return env.DB; } export async function runQuery<T = unknown>( db: D1, sql: string, params: unknown[] = [], ): Promise<T[]> { const stmt = db.prepare(sql); const result = await stmt.bind(...params).all<T>(); return result.results ?? []; }
For single-row helpers:
export async function runOne<T = unknown>( db: D1, sql: string, params: unknown[] = [], ): Promise<T | null> { const rows = await runQuery<T>(db, sql, params); return rows[0] ?? null; }
This skill should:
- Encourage parameterized queries via
.bind(...) - Avoid string interpolation that can cause SQL injection
Schema & Migrations
D1 uses
schema.sql and migrations managed via Wrangler.
Basic Schema Layout
-- src/db/schema.sql CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, email TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) ); CREATE TABLE IF NOT EXISTS posts ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, title TEXT NOT NULL, body TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), FOREIGN KEY (user_id) REFERENCES users(id) );
Migrations are usually created & applied using commands like:
wrangler d1 execute <db_name> --file=src/db/schema.sql --localwrangler d1 migrations create <db_name> <name>wrangler d1 migrations apply <db_name> --local
This skill should:
- Suggest migration commands but not assume CLI access inside code.
- Encourage keeping schema & migration SQL in version control.
Service-Level Access Pattern
Design small services using
getDb & query helpers.
Example
user.service.ts:
// src/services/user.service.ts import type { Env } from "../types/env"; import { getDb, runOne, runQuery } from "../db/d1"; import { nanoid } from "nanoid"; // if project uses this for IDs export type User = { id: string; email: string; password_hash: string; created_at: string; }; export class UserService { constructor(private env: Env) {} private db() { return getDb(this.env); } async createUser(email: string, passwordHash: string): Promise<User> { const id = nanoid(); const db = this.db(); await db .prepare("INSERT INTO users (id, email, password_hash) VALUES (?, ?, ?)") .bind(id, email, passwordHash) .run(); const user = await runOne<User>(db, "SELECT * FROM users WHERE id = ?", [id]); if (!user) throw new Error("Failed to load just-created user"); return user; } async findByEmail(email: string): Promise<User | null> { const db = this.db(); return runOne<User>(db, "SELECT * FROM users WHERE email = ?", [email]); } async listUsers(): Promise<User[]> { const db = this.db(); return runQuery<User>(db, "SELECT * FROM users ORDER BY created_at DESC"); } }
This skill should:
- Keep SQL in small, readable strings.
- Encapsulate DB access in services or repositories, not directly in route handlers (unless trivial).
Using D1 in Hono Routes
Example: a simple users route using
UserService:
// src/routes/v1/users.routes.ts import { Hono } from "hono"; import type { AppContext } from "../../types"; import { UserService } from "../../services/user.service"; export function usersRoutes() { const app = new Hono<AppContext>(); app.get("/", async (c) => { const service = new UserService(c.env); const users = await service.listUsers(); return c.json(users); }); app.post("/", async (c) => { const body = await c.req.json<{ email: string; password: string }>(); // Validate body with a validation skill if present const service = new UserService(c.env); // Password hashing is done via auth skill (bcrypt/argon2) – here we assume `passwordHash` const passwordHash = "TODO"; // placeholder const user = await service.createUser(body.email, passwordHash); return c.json(user, 201); }); return app; }
This skill should:
- Encourage injecting
into services (no global state).env - Allow composition with
(e.g., using D1 to fetch user in login).hono-authentication
Transactions and Limits
D1 has more limited transaction semantics than full-blown SQL servers.
This skill should:
- Prefer simple, single-statement operations where possible.
- For multi-step operations, suggest careful ordering and error handling.
- Explicitly avoid patterns that rely on long-lived transactions or complex isolation.
Type Safety Strategies
D1 returns rows as plain JS objects. This skill should:
- Define TypeScript types (
,User
, etc.) that match the schema.Post - Use generic helpers (
) to type results.runQuery<T> - Optionally validate results in service logic (e.g., Zod) if project wants stronger runtime guarantees.
Local Development & Testing
This skill can suggest:
- Using
withwrangler d1
for dev.--local - Seeding data via SQL files or small scripts.
- For unit tests, mocking D1 methods instead of hitting real DB:
const mockDb: Partial<D1Database> = { prepare: jest.fn().mockReturnValue({ bind: jest.fn().mockReturnThis(), all: jest.fn().mockResolvedValue({ results: [] }), run: jest.fn().mockResolvedValue({}), }), };
- For integration tests in Workers env, tests can use
to prepare DB before running.wrangler d1
When combined with a Hono testing skill, this gives end-to-end coverage.
Error Handling
This skill must:
- Wrap database errors with useful messages when needed.
- Let general error-handling middleware (from
) convert them into HTTP responses.hono-app-scaffold - Avoid leaking sensitive data (SQL text, connection details) in responses.
Example pattern:
try { // D1 call } catch (err) { console.error("D1 query failed:", err); throw new Error("Database error"); }
The error-handler middleware can then map
Error("Database error") to a 500 JSON payload.
Example Prompts That Should Use This Skill
- “Use Cloudflare D1 for persistence in this Hono API.”
- “Create D1 schema and wire up user routes in Hono.”
- “Refactor my raw D1 queries into a service layer.”
- “Help me design D1 tables + queries for this feature.”
- “Make D1 usage type-safe and structured in this Worker.”
For these tasks, rely on this skill to produce a clean, Hono-friendly D1 integration that is typed, bound via the Env, and ready for production on Cloudflare Workers/Pages.