Vollständige Next.js 15 Portfolio-Website mit: - Blog-System mit 100+ Artikeln - Supabase-Integration - Responsive Design mit Tailwind CSS - TypeScript-Konfiguration - Testing-Setup mit Vitest und Playwright Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
486 lines
11 KiB
Markdown
486 lines
11 KiB
Markdown
# Hono: Das ultraschnelle Edge-Framework
|
|
|
|
**Meta-Description:** Hono Framework für Edge Computing. Web Standards, Multi-Runtime Support und nur 12KB. Von Cloudflare Workers bis Bun.
|
|
|
|
**Keywords:** Hono, Edge Framework, Cloudflare Workers, Web Standards, Bun, Deno, Serverless Framework, TypeScript API
|
|
|
|
---
|
|
|
|
## Einführung
|
|
|
|
Hono (炎 = Flamme) ist ein **ultraschnelles Web-Framework** gebaut auf Web Standards. Mit unter 12KB läuft es auf jedem JavaScript-Runtime: Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda und mehr.
|
|
|
|
---
|
|
|
|
## Hono Overview
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ HONO FRAMEWORK │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ Core Features: │
|
|
│ ├── Web Standards API (Request/Response) │
|
|
│ ├── Zero Dependencies │
|
|
│ ├── TypeScript First │
|
|
│ └── < 12KB (hono/tiny) │
|
|
│ │
|
|
│ Supported Runtimes: │
|
|
│ ├── Cloudflare Workers / Pages │
|
|
│ ├── Deno / Deno Deploy │
|
|
│ ├── Bun │
|
|
│ ├── Node.js │
|
|
│ ├── AWS Lambda │
|
|
│ ├── Vercel Edge │
|
|
│ ├── Fastly Compute │
|
|
│ └── Netlify Edge │
|
|
│ │
|
|
│ Built-in Middleware: │
|
|
│ ├── CORS, CSRF, ETag │
|
|
│ ├── JWT, Basic Auth, Bearer Auth │
|
|
│ ├── Logger, Pretty JSON │
|
|
│ ├── Compress, Cache │
|
|
│ └── Streaming, SSE │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Quick Start
|
|
|
|
```bash
|
|
# Neues Projekt
|
|
npm create hono@latest my-app
|
|
|
|
# Runtime wählen:
|
|
# - cloudflare-workers
|
|
# - cloudflare-pages
|
|
# - deno
|
|
# - bun
|
|
# - nodejs
|
|
# - vercel
|
|
|
|
cd my-app
|
|
npm install
|
|
npm run dev
|
|
```
|
|
|
|
---
|
|
|
|
## Basic Routing
|
|
|
|
```typescript
|
|
// src/index.ts
|
|
import { Hono } from 'hono';
|
|
|
|
const app = new Hono();
|
|
|
|
// Basic Routes
|
|
app.get('/', (c) => c.text('Hello Hono!'));
|
|
|
|
app.get('/json', (c) => {
|
|
return c.json({ message: 'Hello', timestamp: Date.now() });
|
|
});
|
|
|
|
// Path Parameters
|
|
app.get('/users/:id', (c) => {
|
|
const id = c.req.param('id');
|
|
return c.json({ userId: id });
|
|
});
|
|
|
|
// Query Parameters
|
|
app.get('/search', (c) => {
|
|
const query = c.req.query('q');
|
|
const page = c.req.query('page') || '1';
|
|
return c.json({ query, page: parseInt(page) });
|
|
});
|
|
|
|
// POST mit Body
|
|
app.post('/users', async (c) => {
|
|
const body = await c.req.json();
|
|
return c.json({ created: body }, 201);
|
|
});
|
|
|
|
// Wildcards
|
|
app.get('/files/*', (c) => {
|
|
const path = c.req.path;
|
|
return c.text(`File path: ${path}`);
|
|
});
|
|
|
|
export default app;
|
|
```
|
|
|
|
---
|
|
|
|
## Typed Routes mit RPC
|
|
|
|
```typescript
|
|
// src/index.ts
|
|
import { Hono } from 'hono';
|
|
import { zValidator } from '@hono/zod-validator';
|
|
import { z } from 'zod';
|
|
|
|
const app = new Hono();
|
|
|
|
// Schema Definition
|
|
const createUserSchema = z.object({
|
|
name: z.string().min(2),
|
|
email: z.string().email()
|
|
});
|
|
|
|
const userParamsSchema = z.object({
|
|
id: z.string().uuid()
|
|
});
|
|
|
|
// Typed Route
|
|
const route = app
|
|
.get('/users', (c) => {
|
|
return c.json([
|
|
{ id: '1', name: 'Alice' },
|
|
{ id: '2', name: 'Bob' }
|
|
]);
|
|
})
|
|
.post(
|
|
'/users',
|
|
zValidator('json', createUserSchema),
|
|
(c) => {
|
|
const data = c.req.valid('json');
|
|
// data ist typisiert: { name: string; email: string }
|
|
return c.json({ id: crypto.randomUUID(), ...data }, 201);
|
|
}
|
|
)
|
|
.get(
|
|
'/users/:id',
|
|
zValidator('param', userParamsSchema),
|
|
(c) => {
|
|
const { id } = c.req.valid('param');
|
|
return c.json({ id, name: 'Alice' });
|
|
}
|
|
);
|
|
|
|
// Type Export für Client
|
|
export type AppType = typeof route;
|
|
|
|
export default app;
|
|
```
|
|
|
|
```typescript
|
|
// Client-Side (hc = Hono Client)
|
|
import { hc } from 'hono/client';
|
|
import type { AppType } from './server';
|
|
|
|
const client = hc<AppType>('http://localhost:8787');
|
|
|
|
// Vollständig typisiert!
|
|
const users = await client.users.$get();
|
|
const newUser = await client.users.$post({
|
|
json: { name: 'Charlie', email: 'charlie@example.com' }
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Middleware
|
|
|
|
```typescript
|
|
import { Hono } from 'hono';
|
|
import { cors } from 'hono/cors';
|
|
import { jwt } from 'hono/jwt';
|
|
import { logger } from 'hono/logger';
|
|
import { prettyJSON } from 'hono/pretty-json';
|
|
import { secureHeaders } from 'hono/secure-headers';
|
|
import { compress } from 'hono/compress';
|
|
|
|
const app = new Hono();
|
|
|
|
// Global Middleware
|
|
app.use('*', logger());
|
|
app.use('*', secureHeaders());
|
|
app.use('*', compress());
|
|
|
|
// CORS für API Routes
|
|
app.use('/api/*', cors({
|
|
origin: ['https://example.com', 'https://app.example.com'],
|
|
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
allowHeaders: ['Content-Type', 'Authorization'],
|
|
credentials: true
|
|
}));
|
|
|
|
// JWT für geschützte Routes
|
|
app.use('/api/protected/*', jwt({
|
|
secret: process.env.JWT_SECRET!
|
|
}));
|
|
|
|
// Pretty JSON in Development
|
|
if (process.env.NODE_ENV === 'development') {
|
|
app.use('*', prettyJSON());
|
|
}
|
|
|
|
// Custom Middleware
|
|
const timing = () => {
|
|
return async (c, next) => {
|
|
const start = Date.now();
|
|
await next();
|
|
const duration = Date.now() - start;
|
|
c.header('X-Response-Time', `${duration}ms`);
|
|
};
|
|
};
|
|
|
|
app.use('*', timing());
|
|
|
|
// Protected Route
|
|
app.get('/api/protected/me', (c) => {
|
|
const payload = c.get('jwtPayload');
|
|
return c.json({ user: payload });
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
```typescript
|
|
import { Hono } from 'hono';
|
|
import { HTTPException } from 'hono/http-exception';
|
|
|
|
const app = new Hono();
|
|
|
|
// Custom Error Handler
|
|
app.onError((err, c) => {
|
|
if (err instanceof HTTPException) {
|
|
return c.json(
|
|
{ error: err.message, status: err.status },
|
|
err.status
|
|
);
|
|
}
|
|
|
|
console.error(err);
|
|
return c.json(
|
|
{ error: 'Internal Server Error', status: 500 },
|
|
500
|
|
);
|
|
});
|
|
|
|
// Not Found Handler
|
|
app.notFound((c) => {
|
|
return c.json({ error: 'Not Found', status: 404 }, 404);
|
|
});
|
|
|
|
// Throwing Errors
|
|
app.get('/users/:id', async (c) => {
|
|
const user = await getUser(c.req.param('id'));
|
|
|
|
if (!user) {
|
|
throw new HTTPException(404, { message: 'User not found' });
|
|
}
|
|
|
|
return c.json(user);
|
|
});
|
|
|
|
// Mit Custom Response
|
|
app.get('/admin', (c) => {
|
|
const isAdmin = checkAdmin(c);
|
|
|
|
if (!isAdmin) {
|
|
throw new HTTPException(403, {
|
|
message: 'Forbidden',
|
|
res: c.json({ error: 'Admin access required' }, 403)
|
|
});
|
|
}
|
|
|
|
return c.json({ admin: true });
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Cloudflare Workers Integration
|
|
|
|
```typescript
|
|
// src/index.ts
|
|
import { Hono } from 'hono';
|
|
|
|
type Bindings = {
|
|
DB: D1Database;
|
|
KV: KVNamespace;
|
|
AI: Ai;
|
|
BUCKET: R2Bucket;
|
|
};
|
|
|
|
const app = new Hono<{ Bindings: Bindings }>();
|
|
|
|
// D1 Database
|
|
app.get('/users', async (c) => {
|
|
const { results } = await c.env.DB
|
|
.prepare('SELECT * FROM users LIMIT 10')
|
|
.all();
|
|
return c.json(results);
|
|
});
|
|
|
|
// KV Storage
|
|
app.get('/cache/:key', async (c) => {
|
|
const key = c.req.param('key');
|
|
const value = await c.env.KV.get(key);
|
|
|
|
if (!value) {
|
|
return c.json({ error: 'Not found' }, 404);
|
|
}
|
|
|
|
return c.json({ key, value: JSON.parse(value) });
|
|
});
|
|
|
|
app.put('/cache/:key', async (c) => {
|
|
const key = c.req.param('key');
|
|
const body = await c.req.json();
|
|
|
|
await c.env.KV.put(key, JSON.stringify(body), {
|
|
expirationTtl: 3600 // 1 Stunde
|
|
});
|
|
|
|
return c.json({ success: true });
|
|
});
|
|
|
|
// R2 Storage
|
|
app.post('/upload', async (c) => {
|
|
const formData = await c.req.formData();
|
|
const file = formData.get('file') as File;
|
|
|
|
await c.env.BUCKET.put(file.name, file);
|
|
|
|
return c.json({ uploaded: file.name });
|
|
});
|
|
|
|
// Workers AI
|
|
app.post('/ai/chat', async (c) => {
|
|
const { message } = await c.req.json();
|
|
|
|
const response = await c.env.AI.run('@cf/meta/llama-2-7b-chat-int8', {
|
|
messages: [{ role: 'user', content: message }]
|
|
});
|
|
|
|
return c.json(response);
|
|
});
|
|
|
|
export default app;
|
|
```
|
|
|
|
---
|
|
|
|
## Streaming & SSE
|
|
|
|
```typescript
|
|
import { Hono } from 'hono';
|
|
import { streamSSE, streamText } from 'hono/streaming';
|
|
|
|
const app = new Hono();
|
|
|
|
// Server-Sent Events
|
|
app.get('/sse', (c) => {
|
|
return streamSSE(c, async (stream) => {
|
|
let id = 0;
|
|
|
|
while (true) {
|
|
await stream.writeSSE({
|
|
data: JSON.stringify({ time: new Date().toISOString() }),
|
|
event: 'tick',
|
|
id: String(id++)
|
|
});
|
|
|
|
await stream.sleep(1000);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Text Streaming (für AI)
|
|
app.get('/stream', (c) => {
|
|
return streamText(c, async (stream) => {
|
|
const words = 'Hello, this is a streaming response!'.split(' ');
|
|
|
|
for (const word of words) {
|
|
await stream.write(word + ' ');
|
|
await stream.sleep(100);
|
|
}
|
|
});
|
|
});
|
|
|
|
// AI Chat Streaming
|
|
app.post('/chat', async (c) => {
|
|
const { message } = await c.req.json();
|
|
|
|
return streamText(c, async (stream) => {
|
|
const response = await openai.chat.completions.create({
|
|
model: 'gpt-4',
|
|
messages: [{ role: 'user', content: message }],
|
|
stream: true
|
|
});
|
|
|
|
for await (const chunk of response) {
|
|
const content = chunk.choices[0]?.delta?.content;
|
|
if (content) {
|
|
await stream.write(content);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## HonoX (Meta-Framework)
|
|
|
|
```typescript
|
|
// HonoX: File-based Routing + Islands Architecture
|
|
|
|
// app/routes/index.tsx
|
|
export default function Home() {
|
|
return (
|
|
<html>
|
|
<body>
|
|
<h1>Welcome to HonoX</h1>
|
|
<Counter /> {/* Client Island */}
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|
|
|
|
// app/routes/api/users.ts
|
|
import { Hono } from 'hono';
|
|
|
|
const app = new Hono();
|
|
|
|
app.get('/', (c) => c.json({ users: [] }));
|
|
|
|
export default app;
|
|
```
|
|
|
|
---
|
|
|
|
## Fazit
|
|
|
|
Hono bietet:
|
|
|
|
1. **Ultra-Lightweight**: < 12KB, zero dependencies
|
|
2. **Multi-Runtime**: Gleicher Code überall
|
|
3. **Web Standards**: Request/Response API
|
|
4. **Type-Safe RPC**: End-to-End TypeScript
|
|
|
|
Das ideale Framework für Edge-First Entwicklung.
|
|
|
|
---
|
|
|
|
## Bildprompts
|
|
|
|
1. "Flame icon transforming into web API endpoints, Hono framework concept"
|
|
2. "Code running across multiple cloud platforms, multi-runtime visualization"
|
|
3. "Lightweight feather compared to heavy framework boxes, minimal bundle"
|
|
|
|
---
|
|
|
|
## Quellen
|
|
|
|
- [Hono Documentation](https://hono.dev/)
|
|
- [Hono GitHub](https://github.com/honojs/hono)
|
|
- [Cloudflare Hono Guide](https://developers.cloudflare.com/workers/framework-guides/web-apps/more-web-frameworks/hono/)
|
|
- [The Story of Hono](https://blog.cloudflare.com/the-story-of-web-framework-hono-from-the-creator-of-hono/)
|