Claude-skill-registry Frameworks Integration

Use when asking about "Hono", "itty-router", "routing frameworks", "middleware", "API framework", "web framework for Workers", "request routing", "Express-like", or choosing between web frameworks for Cloudflare Workers.

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/frameworks-integration" ~/.claude/skills/majiayu000-claude-skill-registry-frameworks-integration && rm -rf "$T"
manifest: skills/data/frameworks-integration/SKILL.md
source content

Web Frameworks for Workers

Purpose

This skill provides guidance on web frameworks for Cloudflare Workers, with focus on Hono and itty-router—the two most popular choices. Use this when building API endpoints, adding middleware, or choosing between vanilla fetch handlers and framework-based routing.

When to Use a Framework

Use a framework when:

  • Multiple routes/endpoints (>3-4)
  • Need middleware (auth, logging, CORS)
  • Building a REST API
  • Want type-safe routing
  • Team is familiar with Express-like patterns

Stay vanilla when:

  • Single endpoint or simple proxy
  • Maximum performance critical
  • Minimal dependencies desired
  • Learning Workers fundamentals

Framework Comparison

FeatureHonoitty-routerVanilla
Bundle size~14KB~1KB0KB
Type safetyExcellentGoodManual
MiddlewareBuilt-in systemBasicManual
Learning curveMediumLowLow
DocumentationExtensiveModerateN/A
Active developmentVery activeActiveN/A
Best forFull APIsSimple routingSingle endpoint

Hono

Why Hono

  • Express-like API, easy to learn
  • Excellent TypeScript support
  • Rich middleware ecosystem
  • Built for edge runtimes
  • Active community and development

Basic Setup

import { Hono } from 'hono';

type Bindings = {
  DATABASE: D1Database;
  AI: Ai;
  CACHE: KVNamespace;
};

const app = new Hono<{ Bindings: Bindings }>();

// Routes
app.get('/', (c) => c.text('Hello from Hono!'));

app.get('/api/users', async (c) => {
  const users = await c.env.DATABASE
    .prepare('SELECT * FROM users')
    .all();
  return c.json(users.results);
});

app.post('/api/users', async (c) => {
  const body = await c.req.json();
  // Validate and create user
  return c.json({ id: '123', ...body }, 201);
});

export default app;

Middleware

import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';
import { bearerAuth } from 'hono/bearer-auth';

const app = new Hono<{ Bindings: Bindings }>();

// Global middleware
app.use('*', logger());
app.use('*', cors({
  origin: ['https://example.com'],
  allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
}));

// Route-specific middleware
app.use('/api/*', bearerAuth({ token: 'secret' }));

// Or custom middleware
app.use('/api/*', async (c, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  c.header('X-Response-Time', `${ms}ms`);
});

Route Groups

const app = new Hono<{ Bindings: Bindings }>();

// API routes
const api = new Hono<{ Bindings: Bindings }>();
api.get('/users', usersHandler);
api.get('/users/:id', userByIdHandler);
api.post('/users', createUserHandler);

// Mount the group
app.route('/api', api);

// Or inline grouping
app.route('/api/v2', new Hono()
  .get('/health', (c) => c.json({ status: 'ok' }))
  .get('/version', (c) => c.json({ version: '2.0' }))
);

Error Handling

import { HTTPException } from 'hono/http-exception';

app.onError((err, c) => {
  if (err instanceof HTTPException) {
    return c.json({ error: err.message }, err.status);
  }
  console.error('Unhandled error:', err);
  return c.json({ error: 'Internal server error' }, 500);
});

// Throwing HTTP exceptions
app.get('/api/users/:id', async (c) => {
  const id = c.req.param('id');
  const user = await getUser(id);

  if (!user) {
    throw new HTTPException(404, { message: 'User not found' });
  }

  return c.json(user);
});

With Workers AI

app.post('/api/chat', async (c) => {
  const { message } = await c.req.json();

  const response = await c.env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
    messages: [
      { role: 'system', content: 'You are a helpful assistant.' },
      { role: 'user', content: message }
    ]
  });

  return c.json({ response: response.response });
});

// Streaming response
app.post('/api/chat/stream', async (c) => {
  const { message } = await c.req.json();

  const stream = await c.env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
    messages: [{ role: 'user', content: message }],
    stream: true
  });

  return new Response(stream, {
    headers: { 'Content-Type': 'text/event-stream' }
  });
});

Validation with Zod

import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';

const createUserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  age: z.number().positive().optional(),
});

app.post('/api/users',
  zValidator('json', createUserSchema),
  async (c) => {
    const user = c.req.valid('json');
    // user is typed and validated
    return c.json({ id: '123', ...user }, 201);
  }
);

itty-router

Why itty-router

  • Extremely lightweight (~1KB)
  • Dead simple API
  • No build step required
  • Good for simple APIs
  • Easy to understand source code

Basic Setup

import { Router } from 'itty-router';

interface Env {
  DATABASE: D1Database;
}

const router = Router();

router.get('/', () => new Response('Hello!'));

router.get('/api/users', async (request, env: Env) => {
  const users = await env.DATABASE
    .prepare('SELECT * FROM users')
    .all();
  return Response.json(users.results);
});

router.post('/api/users', async (request, env: Env) => {
  const body = await request.json();
  // Create user
  return Response.json({ id: '123', ...body }, { status: 201 });
});

// 404 fallback
router.all('*', () => new Response('Not Found', { status: 404 }));

export default {
  fetch: (request: Request, env: Env, ctx: ExecutionContext) =>
    router.handle(request, env, ctx)
};

With Middleware Pattern

import { Router } from 'itty-router';

const router = Router();

// Simple auth middleware
const withAuth = async (request: Request, env: Env) => {
  const token = request.headers.get('Authorization')?.replace('Bearer ', '');
  if (!token || token !== env.API_KEY) {
    return new Response('Unauthorized', { status: 401 });
  }
  // Don't return anything to continue to next handler
};

// Apply middleware to routes
router.get('/api/*', withAuth);
router.get('/api/data', (request, env) => Response.json({ data: 'secret' }));

Route Parameters

router.get('/api/users/:id', async (request, env) => {
  const { id } = request.params;
  const user = await env.DATABASE
    .prepare('SELECT * FROM users WHERE id = ?')
    .bind(id)
    .first();

  if (!user) {
    return new Response('Not Found', { status: 404 });
  }

  return Response.json(user);
});

// Optional parameters
router.get('/api/posts/:id?', (request) => {
  const { id } = request.params;
  if (id) {
    return Response.json({ post: id });
  }
  return Response.json({ posts: [] });
});

Choosing Between Them

Choose Hono When

  • Building a substantial API (>10 endpoints)
  • Need built-in middleware (CORS, auth, validation)
  • Want excellent TypeScript integration
  • Team comes from Express/Fastify background
  • Building something that will grow

Choose itty-router When

  • Simple API with few endpoints
  • Bundle size is critical
  • Want minimal abstraction
  • Quick prototype or proof of concept
  • Learning Workers fundamentals

Choose Vanilla When

  • Single-purpose Worker (proxy, redirect, etc.)
  • Maximum control needed
  • Zero dependencies required
  • Edge case the frameworks don't handle well

Vanilla Fetch Handler Pattern

For comparison, here's the vanilla approach:

interface Env {
  DATABASE: D1Database;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const path = url.pathname;
    const method = request.method;

    // Manual routing
    if (path === '/' && method === 'GET') {
      return new Response('Hello!');
    }

    if (path === '/api/users' && method === 'GET') {
      const users = await env.DATABASE
        .prepare('SELECT * FROM users')
        .all();
      return Response.json(users.results);
    }

    if (path.match(/^\/api\/users\/[\w-]+$/) && method === 'GET') {
      const id = path.split('/').pop();
      const user = await env.DATABASE
        .prepare('SELECT * FROM users WHERE id = ?')
        .bind(id)
        .first();

      if (!user) {
        return new Response('Not Found', { status: 404 });
      }
      return Response.json(user);
    }

    return new Response('Not Found', { status: 404 });
  }
};

Migration Patterns

From Vanilla to Hono

// Before (vanilla)
export default {
  async fetch(request: Request, env: Env) {
    if (request.method === 'GET' && new URL(request.url).pathname === '/') {
      return new Response('Hello');
    }
    return new Response('Not Found', { status: 404 });
  }
};

// After (Hono)
import { Hono } from 'hono';

const app = new Hono<{ Bindings: Env }>();
app.get('/', (c) => c.text('Hello'));
export default app;

From itty-router to Hono

// Before (itty-router)
import { Router } from 'itty-router';
const router = Router();
router.get('/api/users/:id', (req, env) => {
  const { id } = req.params;
  return Response.json({ id });
});

// After (Hono)
import { Hono } from 'hono';
const app = new Hono<{ Bindings: Env }>();
app.get('/api/users/:id', (c) => {
  const id = c.req.param('id');
  return c.json({ id });
});

Best Practices

Type Safety

Always type your bindings:

type Bindings = {
  DATABASE: D1Database;
  CACHE: KVNamespace;
  AI: Ai;
};

const app = new Hono<{ Bindings: Bindings }>();

Error Handling

Always have a global error handler:

app.onError((err, c) => {
  console.error(err);
  return c.json({ error: 'Something went wrong' }, 500);
});

CORS

Configure CORS appropriately:

app.use('*', cors({
  origin: process.env.NODE_ENV === 'production'
    ? ['https://yourdomain.com']
    : ['http://localhost:3000'],
}));

Logging

Add request logging in development:

app.use('*', logger());  // Hono's built-in logger

Additional Resources