mirror of
https://github.com/prdlk/dotfiles.git
synced 2026-11-03 19:37:52 +00:00
197 lines
4.2 KiB
Markdown
197 lines
4.2 KiB
Markdown
# Workers Frameworks
|
|||
|
|
|
||
|
|
## Hono (Recommended)
|
||
|
|
|
||
|
|
Workers-native web framework with excellent TypeScript support and middleware ecosystem.
|
||
|
|
|
||
|
|
```bash
|
||
|
|
npm install hono
|
||
|
|
```
|
||
|
|
|
||
|
|
### Basic Setup
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { Hono } from 'hono';
|
||
|
|
|
||
|
|
const app = new Hono();
|
||
|
|
|
||
|
|
app.get('/', (c) => c.text('Hello World!'));
|
||
|
|
app.post('/api/users', async (c) => {
|
||
|
|
const body = await c.req.json();
|
||
|
|
return c.json({ id: 1, ...body }, 201);
|
||
|
|
});
|
||
|
|
|
||
|
|
export default app;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Typed Environment
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import type { Env } from './.wrangler/types/runtime';
|
||
|
|
|
||
|
|
const app = new Hono<{ Bindings: Env }>();
|
||
|
|
|
||
|
|
app.get('/data', async (c) => {
|
||
|
|
const value = await c.env.MY_KV.get('key'); // Fully typed
|
||
|
|
return c.text(value || 'Not found');
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
### Middleware
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { cors } from 'hono/cors';
|
||
|
|
import { logger } from 'hono/logger';
|
||
|
|
|
||
|
|
app.use('*', logger());
|
||
|
|
app.use('/api/*', cors({ origin: '*' }));
|
||
|
|
|
||
|
|
// Custom middleware
|
||
|
|
app.use('/protected/*', async (c, next) => {
|
||
|
|
const auth = c.req.header('Authorization');
|
||
|
|
if (!auth?.startsWith('Bearer ')) return c.text('Unauthorized', 401);
|
||
|
|
await next();
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
### Request Validation (Zod)
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { zValidator } from '@hono/zod-validator';
|
||
|
|
import { z } from 'zod';
|
||
|
|
|
||
|
|
const schema = z.object({
|
||
|
|
name: z.string().min(1),
|
||
|
|
email: z.string().email(),
|
||
|
|
});
|
||
|
|
|
||
|
|
app.post('/users', zValidator('json', schema), async (c) => {
|
||
|
|
const validated = c.req.valid('json'); // Type-safe, validated data
|
||
|
|
return c.json({ id: 1, ...validated });
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
**Error handling**: Automatic 400 response with validation errors
|
||
|
|
|
||
|
|
### Route Groups
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const api = new Hono().basePath('/api');
|
||
|
|
|
||
|
|
api.get('/users', (c) => c.json([]));
|
||
|
|
api.post('/users', (c) => c.json({ id: 1 }));
|
||
|
|
|
||
|
|
app.route('/', api); // Mounts at /api/*
|
||
|
|
```
|
||
|
|
|
||
|
|
### Error Handling
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
app.onError((err, c) => {
|
||
|
|
console.error(err);
|
||
|
|
return c.json({ error: err.message }, 500);
|
||
|
|
});
|
||
|
|
|
||
|
|
app.notFound((c) => c.json({ error: 'Not Found' }, 404));
|
||
|
|
```
|
||
|
|
|
||
|
|
### Accessing ExecutionContext
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export default {
|
||
|
|
fetch(request: Request, env: Env, ctx: ExecutionContext) {
|
||
|
|
return app.fetch(request, env, ctx);
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
// In route handlers:
|
||
|
|
app.get('/log', (c) => {
|
||
|
|
c.executionCtx.waitUntil(logRequest(c.req));
|
||
|
|
return c.text('OK');
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
### OpenAPI/Swagger (Hono OpenAPI)
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi';
|
||
|
|
|
||
|
|
const app = new OpenAPIHono();
|
||
|
|
|
||
|
|
const route = createRoute({
|
||
|
|
method: 'get',
|
||
|
|
path: '/users/{id}',
|
||
|
|
request: { params: z.object({ id: z.string() }) },
|
||
|
|
responses: {
|
||
|
|
200: { description: 'User found', content: { 'application/json': { schema: z.object({ id: z.string() }) } } },
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
app.openapi(route, (c) => {
|
||
|
|
const { id } = c.req.valid('param');
|
||
|
|
return c.json({ id });
|
||
|
|
});
|
||
|
|
|
||
|
|
app.doc('/openapi.json', { openapi: '3.0.0', info: { version: '1.0.0', title: 'API' } });
|
||
|
|
```
|
||
|
|
|
||
|
|
### Testing with Hono
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { describe, it, expect } from 'vitest';
|
||
|
|
import app from '../src/index';
|
||
|
|
|
||
|
|
describe('API', () => {
|
||
|
|
it('GET /', async () => {
|
||
|
|
const res = await app.request('/');
|
||
|
|
expect(res.status).toBe(200);
|
||
|
|
expect(await res.text()).toBe('Hello World!');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
## Other Frameworks
|
||
|
|
|
||
|
|
### itty-router (Minimalist)
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { Router } from 'itty-router';
|
||
|
|
|
||
|
|
const router = Router();
|
||
|
|
|
||
|
|
router.get('/users/:id', ({ params }) => new Response(params.id));
|
||
|
|
|
||
|
|
export default { fetch: router.handle };
|
||
|
|
```
|
||
|
|
|
||
|
|
**Use case**: Tiny bundle size (~500 bytes), simple routing needs
|
||
|
|
|
||
|
|
### Worktop (Advanced)
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { Router } from 'worktop';
|
||
|
|
|
||
|
|
const router = new Router();
|
||
|
|
|
||
|
|
router.add('GET', '/users/:id', (req, res) => {
|
||
|
|
res.send(200, { id: req.params.id });
|
||
|
|
});
|
||
|
|
|
||
|
|
router.listen();
|
||
|
|
```
|
||
|
|
|
||
|
|
**Use case**: Advanced routing, built-in CORS/cache utilities
|
||
|
|
|
||
|
|
## Framework Comparison
|
||
|
|
|
||
|
|
| Framework | Bundle Size | TypeScript | Middleware | Validation | Best For |
|
||
|
|
|-----------|-------------|------------|------------|------------|----------|
|
||
|
|
| Hono | ~12KB | Excellent | Rich | Zod | Production apps |
|
||
|
|
| itty-router | ~500B | Good | Basic | Manual | Minimal APIs |
|
||
|
|
| Worktop | ~8KB | Good | Advanced | Manual | Complex routing |
|
||
|
|
|
||
|
|
## See Also
|
||
|
|
|
||
|
|
- [Patterns](./patterns.md) - Common workflows
|
||
|
|
- [API](./api.md) - Runtime APIs
|
||
|
|
- [Gotchas](./gotchas.md) - Framework-specific issues
|