| name | backend-dev |
| description | Backend development skill for Cloudflare Workers, D1 database, and REST API. Use when implementing API features. |
Backend Development Skill
Platform-specific knowledge for backend/API development.
Tech Stack
| Component | Technology |
|---|
| Runtime | Cloudflare Workers |
| Framework | Hono |
| Database | D1 (SQLite) |
| ORM | Drizzle ORM |
| Validation | Zod |
| Auth | JWT / Cloudflare Access |
Coding Standards
Naming
- Functions/Variables:
camelCase
- Types/Interfaces:
PascalCase
- Constants:
SCREAMING_SNAKE_CASE
- Database tables:
snake_case
- API endpoints:
kebab-case
- Language: English
File Organization
src/
โโโ index.ts # Entry point, Hono app
โโโ routes/
โ โโโ users.ts # /api/users routes
โ โโโ posts.ts # /api/posts routes
โโโ services/
โ โโโ user-service.ts
โ โโโ post-service.ts
โโโ db/
โ โโโ schema.ts # Drizzle schema
โ โโโ migrations/
โโโ middleware/
โ โโโ auth.ts
โ โโโ cors.ts
โโโ types/
โโโ index.ts
Hono Patterns
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const app = new Hono<{ Bindings: Env }>()
const createUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
})
app.post('/users', zValidator('json', createUserSchema), async (c) => {
const data = c.req.valid('json')
const db = c.env.DB
const result = await db
.prepare('INSERT INTO users (name, email) VALUES (?, ?)')
.bind(data.name, data.email)
.run()
return c.json({ id: result.lastRowId }, 201)
})
export default app
Build Commands
pnpm dev
pnpm deploy
pnpm db:generate
pnpm db:migrate
pnpm db:migrate:prod
pnpm cf-typegen
D1 Database
Schema with Drizzle
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
name: text('name').notNull(),
email: text('email').notNull().unique(),
createdAt: text('created_at').default(sql`CURRENT_TIMESTAMP`),
})
export const posts = sqliteTable('posts', {
id: integer('id').primaryKey({ autoIncrement: true }),
userId: integer('user_id').references(() => users.id),
title: text('title').notNull(),
content: text('content'),
})
Query Patterns
import { drizzle } from 'drizzle-orm/d1'
import { eq } from 'drizzle-orm'
import * as schema from './db/schema'
const db = drizzle(c.env.DB, { schema })
const users = await db.select().from(schema.users).all()
const user = await db.select()
.from(schema.users)
.where(eq(schema.users.id, id))
.get()
const result = await db.insert(schema.users)
.values({ name, email })
.returning()
await db.update(schema.users)
.set({ name: newName })
.where(eq(schema.users.id, id))
await db.delete(schema.users)
.where(eq(schema.users.id, id))
API Design
REST Conventions
GET /api/users # List users
POST /api/users # Create user
GET /api/users/:id # Get user
PUT /api/users/:id # Update user
DELETE /api/users/:id # Delete user
GET /api/users/:id/posts # List user's posts
Response Format
{
"data": { ... },
"meta": {
"total": 100,
"page": 1,
"limit": 20
}
}
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid email format",
"details": [...]
}
}
Error Handling
import { HTTPException } from 'hono/http-exception'
if (!user) {
throw new HTTPException(404, { message: 'User not found' })
}
app.onError((err, c) => {
if (err instanceof HTTPException) {
return c.json({ error: { message: err.message } }, err.status)
}
console.error(err)
return c.json({ error: { message: 'Internal Server Error' } }, 500)
})
Authentication
JWT Pattern
import { jwt } from 'hono/jwt'
app.use('/api/*', jwt({ secret: c.env.JWT_SECRET }))
app.get('/api/me', (c) => {
const payload = c.get('jwtPayload')
return c.json({ userId: payload.sub })
})
import { sign } from 'hono/jwt'
const token = await sign(
{ sub: user.id, exp: Math.floor(Date.now() / 1000) + 60 * 60 },
env.JWT_SECRET
)
Testing
import { describe, it, expect } from 'vitest'
import app from './index'
describe('Users API', () => {
it('GET /api/users returns users', async () => {
const res = await app.request('/api/users')
expect(res.status).toBe(200)
const data = await res.json()
expect(Array.isArray(data.data)).toBe(true)
})
})
Common Issues
"D1_ERROR: no such table"
- Run migrations:
wrangler d1 migrations apply DB
- Check wrangler.toml database binding
CORS errors
Environment variables
- Local:
.dev.vars file
- Production:
wrangler secret put SECRET_NAME