# Cloudflare Hono API

> Scaffolds or extends HTTP APIs on Cloudflare Workers with Hono, D1 via Drizzle, Zod, and Firebase Auth JWT verification. Use when the user wants a new API, a new resource, new routes, Workers handlers, D1 schema, or mentions Hono, Cloudflare Workers, or D1.

- Skill: `sujay46/cloudflare-hono-api` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add sujay46/cloudflare-hono-api`
- Raw SKILL.md: https://api.skillmd.com/api/skills/sujay46/cloudflare-hono-api/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: sujay46 (https://skillmd.com/u/sujay46)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/sujay46/cloudflare-hono-api

---


# Cloudflare Hono API

This skill has one job: **ask enough questions, then either create a new API or add a resource/routes**. Do not start coding until the questions below are answered (from the user or from existing code).

Stack: Cloudflare Workers + Hono + D1 + Drizzle + Zod. Auth identity is Firebase. R2 only if the resource needs files.

Do not use Express, Firebase Functions, Firestore, or Joi. Do not use Firebase Admin on Workers. Do not mint Firebase custom tokens from the Worker.

Conventions and templates: [reference.md](reference.md).

## 1. Ask first

Ask only what you cannot infer from the repo. Stop and wait if anything material is missing.

**Mode**

- Create a **new API** (Worker + Hono app + auth + db wiring), or
- **Add a resource / routes** to an existing API?

**Resource and routes**

- Resource name (singular, lowercase), e.g. `task`
- Which operations: `POST /`, `GET /`, `GET /:id`, `PATCH /:id`, `DELETE /:id`, extra `/:id/action` routes?
- Request body fields for writes; list/filter query params
- Persist in D1? If yes, columns, indexes, unique constraints

**Auth** (default A unless they need B)

- Public routes vs authenticated
- **A — Firebase ID token on each request** (verify Bearer JWT via Google JWKS; set `{ uid, email }`)
- **B — App token with custom claims** — only if they need claims Firebase does not already provide. Ask which claims (e.g. `role`, `tenantId`). Do not add `POST /auth/exchange` "just in case"

**Optional**

- Role / admin checks on specific routes?
- Multi-tenant isolation (`tenant_id` on rows and in queries)?
- File upload/download (R2)?

## 2. Then implement

Read [reference.md](reference.md) and follow it.

### New API

Create the Worker skeleton only: `src/index.ts`, `src/env.ts`, `src/db/` (if D1), `src/auth/` (if any route is authenticated), Wrangler bindings. Then add each requested resource with the five-file pattern.

Auth mode B: add `src/auth/appToken.ts` and `POST /auth/exchange` only when claims were requested.

### Add resource / routes

If the API already exists, match its `src/` layout. Add or extend one resource folder. Mount new routers in `index.ts`. Add Drizzle tables + D1 migrations only for new persisted fields.

One resource folder, five files:

```
src/routes/<resource>/
├── <resource>Router.ts
├── <resource>Schema.ts
├── <resource>DataValidation.ts
├── <resource>DataAccess.ts
└── <resource>Service.ts
```

File suffix is `DataValidation.ts`.

### Hard rules

- Routers chain middleware only. No DB calls in routers.
- Services own HTTP status and `try/catch`. They call data access; they do not import Drizzle tables.
- Data access owns Drizzle. One function = one DB or R2 operation. Prefixes: `db*` (D1), `r2*` (R2).
- Zod + `@hono/zod-validator`. Bindings from `c.env`, not `process.env`.
- IDs: `crypto.randomUUID()`, stored as a column.
- Tenant-scoped tables: indexed `tenant_id`; every `dbGet*` / `dbList*` filters by it.
- Route chain: `auth → (role check) → (validate) → logic`.

### New resource checklist

1. Drizzle table + migration if persisted
2. Schema (types, enums, Zod bodies)
3. Data access (`db*` / `r2*`)
4. Service (`*Logic`)
5. Validation (`validate*`)
6. Router
7. `app.route("/<resource>", resourceRouter)` in `index.ts`

## Errors

| Condition | Status | Body |
|---|---|---|
| Missing/invalid token | 401 | `{ "error": "Unauthorized: ..." }` |
| Authenticated but not allowed | 403 | `{ "error": "Forbidden: ..." }` |
| Invalid body | 400 | `{ "error": "<zod message>" }` |
| Missing row | 404 | `{ "error": "<resource> not found" }` |
| Upstream failure | 502 | `{ "error": "Failed to ..." }` |
| Unhandled | 500 | `{ "error": "Internal server error" }` |
| Created | 201 | resource object |
| Read/update | 200 | resource object or message |

