# Hono Stacks Hono makes easy things easy and hard things easy. It is suitable for not just only returning JSON. But it's also great for building the full-stack application including REST API servers and the client. ## RPC Hono's RPC feature allows you to share API specs with little change to your code. The client generated by `hc` will read the spec and access the endpoint type-safety. The following libraries make it possible. - Hono - API Server - [Zod](https://zod.dev) - Validator - [Zod Validator Middleware](https://github.com/honojs/middleware/tree/main/packages/zod-validator) - `hc` - HTTP Client We can call the set of these components the **Hono Stack**. Now let's create an API server and a client with it. ## Writing API First, write an endpoint that receives a GET request and returns JSON. ```ts twoslash import { Hono } from 'hono' const app = new Hono() app.get('/hello', (c) => { return c.json({ message: `Hello!`, }) }) ``` ## Validation with Zod Validate with Zod to receive the value of the query parameter. ![](/images/sc01.gif) ```ts import { zValidator } from '@hono/zod-validator' import * as 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}`, }) } ) ``` ## Sharing the Types To emit an endpoint specification, export its type. ::: warning For the RPC to infer routes correctly, all included methods must be chained, and the endpoint or app type must be inferred from a declared variable. For more, see [Best Practices for RPC](https://hono.dev/docs/guides/best-practices#if-you-want-to-use-rpc-features). ::: ```ts{1,17} 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 ``` ## Client Next. The client-side implementation. Create a client object by passing the `AppType` type to `hc` as generics. Then, magically, completion works and the endpoint path and request type are suggested. ![](/images/sc03.gif) ```ts import { AppType } from './server' import { hc } from 'hono/client' const client = hc('/api') const res = await client.hello.$get({ query: { name: 'Hono', }, }) ``` The `Response` is compatible with the fetch API, but the data that can be retrieved with `json()` has a type. ![](/images/sc04.gif) ```ts const data = await res.json() console.log(`${data.message}`) ``` Sharing API specifications means that you can be aware of server-side changes. ![](/images/ss03.png) ## With React You can create applications on Cloudflare Pages using React. The API server. ```ts // functions/api/[[route]].ts import { Hono } from 'hono' import { handle } from 'hono/cloudflare-pages' import * as 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 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') ``` The client with React and 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('/api') export default function App() { return ( ) } 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, Error, InferRequestType['form'] >({ mutationFn: async (todo) => { const res = await $post({ form: todo, }) return await res.json() }, onSuccess: async () => { queryClient.invalidateQueries({ queryKey: ['todos'] }) }, onError: (error) => { console.log(error) }, }) return (
    {query.data?.todos.map((todo) => (
  • {todo.title}
  • ))}
) } ```