Skip to content

Hono React

Directory Structure

.
├── package.json
├── src
│   ├── app.tsx
│   ├── index.tsx
│   └── main.tsx
├── tsconfig.json
└── vite.config.ts

Install

sh
pnpm add react react-dom hono @hono/zod-openapi
sh
pnpm add -D @types/react @types/react-dom vite @hono/vite-dev-server @hono/swagger-ui

app.tsx

tsx
import { useState } from 'react'
import { client } from './'

const App = () => {
  const [message, setMessage] = useState('')

  const onSubmit = async () => {
    const res = await client.$get()
    const data = await res.json()
    setMessage(data.message)
  }

  return (
    <>
      <h1>Hono🔥 React</h1>
      <button type='button' onClick={onSubmit}>
        Get Message
      </button>
      <h1>{message}</h1>
    </>
  )
}

export default App

index.tsx

tsx
import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi'
import type { RouteHandler } from '@hono/zod-openapi'
import { hc } from 'hono/client'
import { swaggerUI } from '@hono/swagger-ui'
import { renderToString } from 'react-dom/server'

const app = new OpenAPIHono()

const get = createRoute({
  tags: ['Hono'],
  method: 'get',
  path: '/',
  description: 'Hono🔥 React',
  responses: {
    200: {
      description: 'Hono🔥',
      content: {
        'application/json': {
          schema: z.object({
            message: z.string(),
          }),
        },
      },
    },
  },
})

export const getHandler: RouteHandler<typeof get> = async (c) => {
  return c.json({ message: 'Hono🔥 React' })
}

export const api = app.basePath('/api').openapi(get, getHandler)

// Swagger
api
  .doc('/doc', {
    info: {
      title: 'Hono API',
      version: 'v1',
    },
    openapi: '3.1.0',
    tags: [
      {
        name: 'Hono',
        description: 'Hono API',
      },
    ],
  })
  .get('/ui', swaggerUI({ url: '/api/doc' }))

type AddType = typeof api

export const client = hc<AddType>('/').api

app.get('*', (c) => {
  return c.html(
    renderToString(
      <html lang='en'>
        <head>
          <meta charSet='utf-8' />
          <meta content='width=device-width, initial-scale=1' name='viewport' />
          <title>Hono🔥 React</title>
          <script type='module' src='/src/main.tsx' />
        </head>
        <body>
          <div id='root' />
        </body>
      </html>,
    ),
  )
})

export default app

main.tsx

tsx
import { createRoot } from 'react-dom/client'
import App from './app'

const rootElement = document.getElementById('root')
const root = rootElement ? createRoot(rootElement) : console.error('Root element not found')

if (root) {
  root.render(<App />)
}

vite.config.ts

ts
import { defineConfig } from 'vite'
import devServer from '@hono/vite-dev-server'

export default defineConfig({
  plugins: [
    devServer({
      entry: 'src/index.tsx',
    }),
  ],
})

Demo

demo