Files
gh-jezweb-claude-skills-ski…/templates/rpc-pattern.ts
2025-11-30 08:24:59 +08:00

203 lines
5.1 KiB
TypeScript

/**
* Hono RPC Pattern
*
* Type-safe client/server communication using Hono's RPC feature.
*/
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
// ============================================================================
// SERVER-SIDE: Define Routes with Type Export
// ============================================================================
const app = new Hono()
// Define schemas
const createUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().int().min(18).optional(),
})
const updateUserSchema = createUserSchema.partial()
const userParamSchema = z.object({
id: z.string().uuid(),
})
// ============================================================================
// METHOD 1: Export Individual Routes
// ============================================================================
// Define route and assign to variable (REQUIRED for RPC type inference)
const getUsers = app.get('/users', (c) => {
return c.json({
users: [
{ id: '1', name: 'Alice', email: 'alice@example.com' },
{ id: '2', name: 'Bob', email: 'bob@example.com' },
],
})
})
const createUser = app.post('/users', zValidator('json', createUserSchema), (c) => {
const data = c.req.valid('json')
return c.json(
{
success: true,
user: {
id: crypto.randomUUID(),
...data,
},
},
201
)
})
const getUser = app.get('/users/:id', zValidator('param', userParamSchema), (c) => {
const { id } = c.req.valid('param')
return c.json({
id,
name: 'Alice',
email: 'alice@example.com',
})
})
const updateUser = app.patch(
'/users/:id',
zValidator('param', userParamSchema),
zValidator('json', updateUserSchema),
(c) => {
const { id } = c.req.valid('param')
const updates = c.req.valid('json')
return c.json({
success: true,
user: {
id,
...updates,
},
})
}
)
const deleteUser = app.delete('/users/:id', zValidator('param', userParamSchema), (c) => {
const { id } = c.req.valid('param')
return c.json({ success: true, deletedId: id })
})
// Export combined type for RPC client
export type AppType = typeof getUsers | typeof createUser | typeof getUser | typeof updateUser | typeof deleteUser
// ============================================================================
// METHOD 2: Export Entire App (Simpler but slower for large apps)
// ============================================================================
const simpleApp = new Hono()
simpleApp.get('/hello', (c) => {
return c.json({ message: 'Hello!' })
})
simpleApp.post('/echo', zValidator('json', z.object({ message: z.string() })), (c) => {
const { message } = c.req.valid('json')
return c.json({ echo: message })
})
export type SimpleAppType = typeof simpleApp
// ============================================================================
// METHOD 3: Group Routes by Domain
// ============================================================================
// Posts routes
const postsApp = new Hono()
const getPosts = postsApp.get('/', (c) => {
return c.json({ posts: [] })
})
const createPost = postsApp.post(
'/',
zValidator(
'json',
z.object({
title: z.string(),
content: z.string(),
})
),
(c) => {
const data = c.req.valid('json')
return c.json({ success: true, post: { id: '1', ...data } }, 201)
}
)
export type PostsType = typeof getPosts | typeof createPost
// Comments routes
const commentsApp = new Hono()
const getComments = commentsApp.get('/', (c) => {
return c.json({ comments: [] })
})
export type CommentsType = typeof getComments
// Mount to main app
app.route('/posts', postsApp)
app.route('/comments', commentsApp)
// ============================================================================
// MIDDLEWARE WITH RPC
// ============================================================================
// Middleware that returns early (short-circuits)
const authMiddleware = async (c: any, next: any) => {
const token = c.req.header('Authorization')
if (!token) {
return c.json({ error: 'Unauthorized' }, 401)
}
await next()
}
// Route with middleware
const protectedRoute = app.get('/protected', authMiddleware, (c) => {
return c.json({ data: 'Protected data' })
})
// Export type includes middleware responses
export type ProtectedType = typeof protectedRoute
// ============================================================================
// QUERY PARAMETER HANDLING
// ============================================================================
const searchQuerySchema = z.object({
q: z.string(),
page: z.string().transform((val) => parseInt(val, 10)),
})
const searchRoute = app.get('/search', zValidator('query', searchQuerySchema), (c) => {
const { q, page } = c.req.valid('query')
return c.json({
query: q,
page,
results: [],
})
})
export type SearchType = typeof searchRoute
// ============================================================================
// EXPORT MAIN APP
// ============================================================================
export default app