Hono Stacks
Hono 使簡單的事情變得容易,困難的事情也變得容易。它不僅適用於返回 JSON。它也非常適合建構包含 REST API 伺服器和客戶端的完整堆疊應用程式。
RPC
Hono 的 RPC 功能讓您可以在程式碼幾乎不變的情況下分享 API 規格。hc
產生的客戶端將讀取規格並以型別安全的方式存取端點。
以下程式庫使之成為可能。
- Hono - API 伺服器
- Zod - 驗證器
- Zod 驗證器中介層
hc
- HTTP 客戶端
我們可以將這些組件的組合稱為 Hono Stack。現在,讓我們用它來建立 API 伺服器和客戶端。
撰寫 API
首先,撰寫一個接收 GET 請求並返回 JSON 的端點。
ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/hello', (c) => {
return c.json({
message: `Hello!`,
})
})
使用 Zod 驗證
使用 Zod 驗證以接收查詢參數的值。
ts
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
app.get(
'/hello',
zValidator(
'query',
z.object({
name: z.string(),
})
),
(c) => {
const { name } = c.req.valid('query')
return c.json({
message: `Hello! ${name}`,
})
}
)
分享類型
為了發出端點規格,請匯出其類型。
ts
const route = app.get(
'/hello',
zValidator(
'query',
z.object({
name: z.string(),
})
),
(c) => {
const { name } = c.req.valid('query')
return c.json({
message: `Hello! ${name}`,
})
}
)
export type AppType = typeof route
客戶端
接下來是客戶端實作。將 AppType 類型作為泛型傳遞給 hc
以建立客戶端物件。然後,神奇地,補全功能開始運作,並且會提示端點路徑和請求類型。
ts
import { AppType } from './server'
import { hc } from 'hono/client'
const client = hc<AppType>('/api')
const res = await client.hello.$get({
query: {
name: 'Hono',
},
})
Response
與 fetch API 相容,但是可以使用 json()
擷取的資料具有類型。
ts
const data = await res.json()
console.log(`${data.message}`)
分享 API 規格意味著您可以知道伺服器端的變更。
使用 React
您可以使用 React 在 Cloudflare Pages 上建立應用程式。
API 伺服器。
ts
// functions/api/[[route]].ts
import { Hono } from 'hono'
import { handle } from 'hono/cloudflare-pages'
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'
const app = new Hono()
const schema = z.object({
id: z.string(),
title: z.string(),
})
type Todo = z.infer<typeof schema>
const todos: Todo[] = []
const route = app
.post('/todo', zValidator('form', schema), (c) => {
const todo = c.req.valid('form')
todos.push(todo)
return c.json({
message: 'created!',
})
})
.get((c) => {
return c.json({
todos,
})
})
export type AppType = typeof route
export const onRequest = handle(app, '/api')
使用 React 和 React Query 的客戶端。
tsx
// src/App.tsx
import {
useQuery,
useMutation,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'
import { AppType } from '../functions/api/[[route]]'
import { hc, InferResponseType, InferRequestType } from 'hono/client'
const queryClient = new QueryClient()
const client = hc<AppType>('/api')
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Todos />
</QueryClientProvider>
)
}
const Todos = () => {
const query = useQuery({
queryKey: ['todos'],
queryFn: async () => {
const res = await client.todo.$get()
return await res.json()
},
})
const $post = client.todo.$post
const mutation = useMutation<
InferResponseType<typeof $post>,
Error,
InferRequestType<typeof $post>['form']
>(
async (todo) => {
const res = await $post({
form: todo,
})
return await res.json()
},
{
onSuccess: async () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
},
onError: (error) => {
console.log(error)
},
}
)
return (
<div>
<button
onClick={() => {
mutation.mutate({
id: Date.now().toString(),
title: 'Write code',
})
}}
>
Add Todo
</button>
<ul>
{query.data?.todos.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
</div>
)
}