Transform hub-guide from a single-skill Hub monorepo guide into a comprehensive programming best-practice plugin covering all situations. Skills (26): - Core: engineering-principles, clean-code, clean-architecture, design-patterns, testing, error-handling, security, api-design, git-workflow, documentation, logging-observability, performance - Languages: typescript, python, rust, go - Frameworks: react-frontend, elysiajs, hono-backend, drizzle-database, nextjs - Infrastructure: docker, ci-cd, monitoring - Monorepo: monorepo, hub-guide (existing) Hooks: - SessionStart: auto-detect project type and activate relevant skills - PreToolUse (Write|Edit): inject language-specific rules per file type Reference files for deep dives: - clean-architecture/references/solid.md (SOLID + component principles) - design-patterns/references/catalog.md (full GoF catalog with examples) - testing/references/mocks.md (test double taxonomy) Restructure plugin to modern skills/ directory format.
202 lines
5.5 KiB
Markdown
202 lines
5.5 KiB
Markdown
---
|
|
name: hono-backend
|
|
description: Hono best practices — middleware, RPC, validation (Zod), Drizzle integration, and project organization. Use when building Hono backend APIs, or whenever the user mentions "Hono," "Hono.js," "hono RPC," "hono middleware," "hono Zod OpenAPI," "hono validation," or "hono Drizzle."
|
|
---
|
|
|
|
# Hono Backend Best Practices
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
src/
|
|
├── modules/ # Feature modules
|
|
│ ├── users/
|
|
│ │ ├── routes.ts # Hono routes (thin — validation + delegation)
|
|
│ │ ├── service.ts # Business logic / use cases
|
|
│ │ └── repository.ts # Data access (Drizzle queries)
|
|
│ └── orders/
|
|
├── middleware/ # Custom middleware
|
|
├── lib/ # Shared utilities (env, JWT, hashing)
|
|
├── db/ # Drizzle schema, migrations
|
|
└── index.ts # App entry + composition root
|
|
```
|
|
|
|
## Route Definition
|
|
|
|
```typescript
|
|
import { Hono } from 'hono';
|
|
import { z } from 'zod';
|
|
import { zValidator } from '@hono/zod-validator';
|
|
import { userService } from './service';
|
|
import type { Env } from '../lib/env';
|
|
|
|
const users = new Hono<Env>()
|
|
.get('/', async (c) => {
|
|
const page = Number(c.req.query('page') || '1');
|
|
const limit = Number(c.req.query('limit') || '20');
|
|
const result = await userService.list({ page, limit });
|
|
return c.json(result);
|
|
})
|
|
.post('/', zValidator('json', CreateUserSchema), async (c) => {
|
|
const body = c.req.valid('json');
|
|
const user = await userService.create(body);
|
|
return c.json(user, 201);
|
|
})
|
|
.get('/:id', async (c) => {
|
|
const id = c.req.param('id');
|
|
const user = await userService.findById(id);
|
|
if (!user) return c.json({ error: 'Not found' }, 404);
|
|
return c.json(user);
|
|
});
|
|
|
|
export { users };
|
|
```
|
|
|
|
## Zod Validation
|
|
|
|
```typescript
|
|
import { z } from 'zod';
|
|
|
|
// Define at the boundary
|
|
export const CreateUserSchema = z.object({
|
|
email: z.string().email(),
|
|
name: z.string().min(1).max(100).optional(),
|
|
role: z.enum(['admin', 'user']).default('user'),
|
|
});
|
|
|
|
export const PaginationSchema = z.object({
|
|
page: z.coerce.number().int().positive().default(1),
|
|
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
});
|
|
|
|
// Infer types
|
|
export type CreateUserInput = z.infer<typeof CreateUserSchema>;
|
|
```
|
|
|
|
## Hono RPC (Type-Safe Client)
|
|
|
|
```typescript
|
|
// Server
|
|
import { hono } from 'hono';
|
|
import { routes } from './routes';
|
|
|
|
const app = new Hono().route('/api', routes);
|
|
export type App = typeof app;
|
|
|
|
// Client (no treaty needed — direct fetch wrapper)
|
|
import { hc } from 'hono/client';
|
|
import type { App } from '../server';
|
|
|
|
const client = hc<App>('http://localhost:3000');
|
|
const res = await client.api.users.$post({
|
|
json: { email: 'test@example.com', name: 'Alice' },
|
|
});
|
|
// res is fully typed — status codes, response body
|
|
```
|
|
|
|
## Middleware
|
|
|
|
```typescript
|
|
import { Hono } from 'hono';
|
|
|
|
const app = new Hono()
|
|
// Built-in
|
|
.use('*', cors())
|
|
.use('*', logger())
|
|
|
|
// Custom
|
|
.use('*', async (c, next) => {
|
|
const start = Date.now();
|
|
await next();
|
|
const ms = Date.now() - start;
|
|
c.header('X-Response-Time', `${ms}ms`);
|
|
})
|
|
|
|
// Auth middleware
|
|
.use('/api/*', async (c, next) => {
|
|
const auth = c.req.header('Authorization');
|
|
if (!auth?.startsWith('Bearer ')) return c.json({ error: 'Unauthorized' }, 401);
|
|
const user = await verifyToken(auth.slice(7));
|
|
if (!user) return c.json({ error: 'Invalid token' }, 401);
|
|
c.set('user', user);
|
|
await next();
|
|
});
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
```typescript
|
|
import { HTTPException } from 'hono/http-exception';
|
|
|
|
app.onError((err, c) => {
|
|
if (err instanceof HTTPException) {
|
|
return c.json({ error: err.message }, err.status);
|
|
}
|
|
console.error(err); // log unexpected errors
|
|
return c.json({ error: 'Internal server error' }, 500);
|
|
});
|
|
|
|
// In routes
|
|
app.post('/orders', async (c) => {
|
|
const result = await orderService.create(body);
|
|
if (!result.ok) {
|
|
throw new HTTPException(422, { message: result.error.message });
|
|
}
|
|
return c.json(result.value, 201);
|
|
});
|
|
```
|
|
|
|
## OpenAI / Swagger Integration
|
|
|
|
```typescript
|
|
import { OpenAPIHono } from '@hono/zod-openapi';
|
|
|
|
const app = new OpenAPIHono();
|
|
|
|
app.openapi(
|
|
createRoute({
|
|
method: 'post',
|
|
path: '/users',
|
|
request: { body: { content: { 'application/json': { schema: CreateUserSchema } } } },
|
|
responses: {
|
|
201: { description: 'User created', content: { 'application/json': { schema: UserSchema } } },
|
|
422: { description: 'Validation error' },
|
|
},
|
|
}),
|
|
async (c) => {
|
|
const body = c.req.valid('json');
|
|
const user = await userService.create(body);
|
|
return c.json(user, 201);
|
|
},
|
|
);
|
|
```
|
|
|
|
## Testing
|
|
|
|
```typescript
|
|
import { describe, expect, it } from 'bun:test';
|
|
import { app } from '../src/index';
|
|
|
|
describe('users', () => {
|
|
it('creates a user', async () => {
|
|
const res = await app.request('/users', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ email: 'test@example.com' }),
|
|
});
|
|
expect(res.status).toBe(201);
|
|
const body = await res.json();
|
|
expect(body.email).toBe('test@example.com');
|
|
});
|
|
});
|
|
```
|
|
|
|
## Anti-patterns
|
|
|
|
- ❌ Business logic in route handlers — routes validate + delegate
|
|
- ❌ No Zod validation on inputs — every route that accepts input must validate
|
|
- ❌ `c.req.raw` instead of Hono's `c.req.json/query/valid`
|
|
- ❌ Mixing Hono and Express middleware patterns
|
|
- ❌ Global error handled only at route level — use app-level `onError`
|
|
- ❌ Not using `c.set` for typed variables — declare `Env` type with bindings/variables
|