203 lines
5.1 KiB
TypeScript
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
|