Skip to content

HonoX

sh
yarn create hono

Directory Structure

.
├── app
│   ├── client.ts
│   ├── global.d.ts
│   ├── islands
│   │   └── counter.tsx
│   ├── routes
│   │   ├── _404.tsx
│   │   ├── _error.tsx
│   │   ├── index.tsx
│   │   └── _renderer.tsx
│   └── server.ts
├── package.json
├── public
│   └── favicon.ico
├── tsconfig.json
├── vite.config.ts
└── wrangler.toml

wrangler.toml

nameの値を修正して実行する。name = "-"これでは、以下のようなエラーになります。

[vite] Internal server error: Processing wrangler.toml configuration:
  - Expected "name" to be of type string, alphanumeric and lowercase with dashes only but got "-".
toml
name = "honox"
...

Start

sh
yarn dev

Accesse Browser

http://localhost:5173/

img

HonoX RPC

Directory Structure

.
├── app
│   ├── client.ts
│   ├── common
│   │   ├── types
│   │   │   └── index.ts
│   │   └── zod
│   │       └── index.ts
│   ├── global.d.ts
│   ├── islands
│   │   ├── index.tsx
│   │   └── Post
│   │       └── index.tsx
│   ├── routes
│   │   ├── _404.tsx
│   │   ├── api
│   │   │   └── index.ts
│   │   ├── _error.tsx
│   │   ├── index.tsx
│   │   └── _renderer.tsx
│   └── server.ts
├── package.json
├── public
│   └── favicon.ico
├── tsconfig.json
├── vite.config.ts
└── wrangler.toml

Hono API

ts
import { Hono } from 'hono'
import { validator } from 'hono/validator'
import { reqSchema } from '../../common/zod'
import { ReqType } from '../../common/types'

const app = new Hono()
  .get('/', async (c) => {
    return c.json({ message: 'HonoX🔥' })
  })
  .post(
    '/posts',
    validator('json', (value: ReqType, c) => {
      const parsed = reqSchema.safeParse(value)
      if (!parsed.success) {
        return c.json({ message: 'Bad Request', error: '1文字以上140文字以内で入力して下さい。' }, 400)
      }
      return parsed.data
    }),
    (c) => {
      const res = c.req.valid('json')

      return c.json(
        {
          message: 'Created',
          post: res.post,
        },
        201,
      )
    },
  )

export type AddType = typeof app
export default app

FrontEnd

tsx
import { createRoute } from 'honox/factory'
import Client from '../islands'
import Post from '../islands/Post'

export default createRoute((c) => {
  return c.render(
    <>
      <h1>HonoX</h1>
      <Client />
      <Post />
    </>,
  )
})

Get

tsx
import { hc } from 'hono/client'
import { useState } from 'hono/jsx'
import { AddType } from '../routes/api'

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

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

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

  return (
    <>
      <h1>Get</h1>
      <button onClick={onSubmit}>Get Message</button>
      <h1>{message}</h1>
    </>
  )
}

export default Client

Post

tsx
import { useState } from 'hono/jsx'
import { hc } from 'hono/client'
import { AddType } from '../../routes/api'
import { reqSchema } from '../../common/zod'
import { ReqType } from '../../common/types'

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

const Post = () => {
  const [value, setValue] = useState('')
  const [error, setError] = useState<string | null>(null)
  const [posts, setPosts] = useState<string[]>([])

  const handleChange = (e: Event) => setValue(e.target instanceof HTMLInputElement ? e.target.value : value)

  const valueValidate = (value: string): string | null => {
    const parseValue: ReqType = { post: value }
    const result = reqSchema.safeParse(parseValue)
    if (!result.success) {
      return '1文字以上140文字以内で入力して下さい。'
    }
    return null
  }

  const handleSubmit = async (e: Event) => {
    e.preventDefault()

    const valueValidateError = valueValidate(value)
    if (valueValidateError) {
      setError(valueValidateError)
      return
    }

    try {
      setError(null)
      const res = await client.posts
        .$post({
          json: { post: value },
        })
        .then((res) => res.json())

      const resMessage = res.post
      setPosts([...posts, resMessage])
      setValue('')
    } catch {
      setError('投稿に失敗しました')
    }
  }

  return (
    <>
      <h1>Post</h1>
      <form onSubmit={handleSubmit}>
        <input type='text' value={value} onChange={handleChange} />
        <button type='submit'>Submit</button>
      </form>
      {error && <h1>{error}</h1>}
      {posts.map((post, index) => (
        <p key={index}>{post}</p>
      ))}
    </>
  )
}

export default Post

Demo

demo