Let's create a basic HonoX application using hono/jsx as a renderer. This application has no client JavaScript and renders JSX on the server side.
Below is a typical project structure for a HonoX application.
.
├── app
│ ├── global.d.ts // global type definitions
│ ├── routes
│ │ ├── _404.tsx // not found page
│ │ ├── _error.tsx // error page
│ │ ├── _renderer.tsx // renderer definition
│ │ ├── about
│ │ │ └── [name].tsx // matches `/about/:name`
│ │ └── index.tsx // matches `/`
│ └── server.ts // server entry file
├── package.json
├── tsconfig.json
└── vite.config.ts
vite.config.ts
The minimum Vite setup for development is as follows:
import { function defineConfig(config: UserConfig): UserConfig (+3 overloads)
Type helper to make it easier to use vite.config.ts
accepts a direct
{@link
UserConfig
}
object, or a function that returns it.
The function receives a
{@link
ConfigEnv
}
object.defineConfig } from 'vite'
import function honox(options?: Options): PluginOption[]
honox from 'honox/vite'
export default function defineConfig(config: UserConfig): UserConfig (+3 overloads)
Type helper to make it easier to use vite.config.ts
accepts a direct
{@link
UserConfig
}
object, or a function that returns it.
The function receives a
{@link
ConfigEnv
}
object.defineConfig({
UserConfig.plugins?: PluginOption[] | undefined
Array of vite plugins to use.plugins: [function honox(options?: Options): PluginOption[]
honox()],
})
A server entry file is required. The file should be placed at app/server.ts
. This file is first called by the Vite during the development or build phase.
In the entry file, simply initialize your app using the createApp()
function. app
will be an instance of Hono, so you can use Hono's middleware and the showRoutes()
in hono/dev
.
// @filename: app/server.ts
import { const createApp: <E extends Env>(options?: ServerOptions<E>) => Hono<E, BlankSchema, "/">
createApp } from 'honox/server'
import { const showRoutes: <E extends Env>(hono: Hono<E>, opts?: ShowRoutesOptions) => void
showRoutes } from 'hono/dev'
const const app: Hono<Env, BlankSchema, "/">
app = createApp<Env>(options?: Partial<BaseServerOptions<Env>> | undefined): Hono<Env, BlankSchema, "/">
createApp()
showRoutes<Env>(hono: Hono<Env, BlankSchema, "/">, opts?: ShowRoutesOptions): void
showRoutes(const app: Hono<Env, BlankSchema, "/">
app)
export default const app: Hono<Env, BlankSchema, "/">
app
There are three ways to define routes.
createRoute()
Each route should return an array of Handler | MiddlewareHandler
. createRoute()
is a helper function to return it. You can write a route for a GET request with default export
.
// `createRoute()` helps you create handlers
import { const createRoute: CreateHandlersInterface<Env, any>
createRoute } from 'honox/factory'
export default createRoute<{}, Response | Promise<Response>, Env>(handler1: H<Env, any, {}, Response | Promise<Response>>): [...] (+9 overloads)
createRoute((c: Context<Env, any, {}>
c) => {
return c: Context<Env, any, {}>
c.Context<Env, any, {}>.render: DefaultRenderer
(content: string | Promise<string>) => Response | Promise<Response>
`.render()` can create a response within a layout.render(
<JSX.IntrinsicElements.div: JSX.HTMLAttributes
div>
<JSX.IntrinsicElements.h1: JSX.HTMLAttributes
h1>Hello!</JSX.IntrinsicElements.h1: JSX.HTMLAttributes
h1>
</JSX.IntrinsicElements.div: JSX.HTMLAttributes
div>
)
})
You can also handle methods other than GET by export
POST
, PUT
, and DELETE
.
import { const createRoute: CreateHandlersInterface<Env, any>
createRoute } from 'honox/factory'
import { const getCookie: GetCookie
getCookie, const setCookie: (c: Context, name: string, value: string, opt?: CookieOptions) => void
setCookie } from 'hono/cookie'
export const const POST: [H<Env, any, {}, Promise<Response & TypedResponse<undefined, 302, "redirect">>>]
POST = createRoute<{}, Promise<Response & TypedResponse<undefined, 302, "redirect">>, Env>(handler1: H<Env, any, {}, Promise<Response & TypedResponse<undefined, 302, "redirect">>>): [...] (+9 overloads)
createRoute(async (c: Context<Env, any, {}>
c) => {
const { const name: string
name } = await c: Context<Env, any, {}>
c.Context<Env, any, {}>.req: HonoRequest<any, unknown>
`.req` is the instance of
{@link
HonoRequest
}
.req.HonoRequest<any, unknown>.parseBody<{
name: string;
}>(options?: Partial<ParseBodyOptions>): Promise<{
name: string;
}> (+1 overload)
`.parseBody()` can parse Request body of type `multipart/form-data` or `application/x-www-form-urlencoded`parseBody<{ name: string
name: string }>()
function setCookie(c: Context, name: string, value: string, opt?: CookieOptions): void
setCookie(c: Context<Env, any, {}>
c, 'name', const name: string
name)
return c: Context<Env, any, {}>
c.Context<Env, any, {}>.redirect: <302>(location: string | URL, status?: 302 | undefined) => Response & TypedResponse<undefined, 302, "redirect">
`.redirect()` can Redirect, default status code is 302.redirect('/')
})
export default createRoute<{}, Response | Promise<Response>, Env>(handler1: H<Env, any, {}, Response | Promise<Response>>): [...] (+9 overloads)
createRoute((c: Context<Env, any, {}>
c) => {
const const name: string
name = function getCookie(c: Context, key: string): string | undefined (+2 overloads)
getCookie(c: Context<Env, any, {}>
c, 'name') ?? 'no name'
return c: Context<Env, any, {}>
c.Context<Env, any, {}>.render: DefaultRenderer
(content: string | Promise<string>) => Response | Promise<Response>
`.render()` can create a response within a layout.render(
<JSX.IntrinsicElements.div: JSX.HTMLAttributes
div>
<JSX.IntrinsicElements.h1: JSX.HTMLAttributes
h1>Hello, {const name: string
name}!</JSX.IntrinsicElements.h1: JSX.HTMLAttributes
h1>
<JSX.IntrinsicElements.form: FormHTMLAttributes
form FormHTMLAttributes.method?: HTMLAttributeFormMethod | undefined
method='post'>
<JSX.IntrinsicElements.input: InputHTMLAttributes
input InputHTMLAttributes.type?: HTMLInputTypeAttribute | undefined
type='text' InputHTMLAttributes.name?: string | undefined
name='name' InputHTMLAttributes.placeholder?: string | undefined
placeholder='name' />
<JSX.IntrinsicElements.input: InputHTMLAttributes
input InputHTMLAttributes.type?: HTMLInputTypeAttribute | undefined
type='submit' />
</JSX.IntrinsicElements.form: FormHTMLAttributes
form>
</JSX.IntrinsicElements.div: JSX.HTMLAttributes
div>
)
})
You can create API endpoints by exporting an instance of the Hono object.
// @filename: app/routes/about/index.ts
import { class Hono<E extends Env = BlankEnv, S extends Schema = BlankSchema, BasePath extends string = "/">
The Hono class extends the functionality of the HonoBase class.
It sets up routing and allows for custom options to be passed.Hono } from 'hono'
const const app: Hono<BlankEnv, BlankSchema, "/">
app = new new Hono<BlankEnv, BlankSchema, "/">(options?: HonoOptions<BlankEnv> | undefined): Hono<BlankEnv, BlankSchema, "/">
Creates an instance of the Hono class.Hono()
// matches `/about/:name`
const app: Hono<BlankEnv, BlankSchema, "/">
app.Hono<BlankEnv, BlankSchema, "/">.get: HandlerInterface
<"/:name", "/:name", JSONRespondReturn<{
'your name is': string;
}, ContentfulStatusCode>, BlankInput, BlankEnv>(path: "/:name", handler: H<...>) => Hono<...> (+22 overloads)
get('/:name', (c: Context<BlankEnv, "/:name", BlankInput>
c) => {
const const name: string
name = c: Context<BlankEnv, "/:name", BlankInput>
c.Context<BlankEnv, "/:name", BlankInput>.req: HonoRequest<"/:name", unknown>
`.req` is the instance of
{@link
HonoRequest
}
.req.HonoRequest<"/:name", unknown>.param<"name">(key: "name"): string (+3 overloads)
`.req.param()` gets the path parameters.param('name')
return c: Context<BlankEnv, "/:name", BlankInput>
c.Context<BlankEnv, "/:name", BlankInput>.json: JSONRespond
<{
'your name is': string;
}, ContentfulStatusCode>(object: {
'your name is': string;
}, status?: ContentfulStatusCode | undefined, headers?: HeaderRecord) => JSONRespondReturn<...> (+1 overload)
json({
'your name is': const name: string
name,
})
})
export default const app: Hono<BlankEnv, BlankSchema, "/">
app
Or simply, you can just return JSX.
// @filename: app/routes/index.tsx
/** @jsx jsx */
/** @jsxImportSource hono/jsx */
import { class Context<E extends Env = any, P extends string = any, I extends Input = {}>
Context } from 'hono'
export default function function Home(_c: Context): JSX.Element
Home(_c: Context<any, any, {}>
_c: class Context<E extends Env = any, P extends string = any, I extends Input = {}>
Context) {
return <JSX.IntrinsicElements.h1: JSX.HTMLAttributes
h1>Welcome!</JSX.IntrinsicElements.h1: JSX.HTMLAttributes
h1>
}
Define your renderer - the middleware that does c.setRender()
- by writing it in _renderer.tsx
.
Before writing _renderer.tsx
, write the Renderer type definition in global.d.ts
.
// @filename: app/global.d.ts
import type {} from 'hono'
type type Head = {
title?: string;
}
Head = {
title?: string | undefined
title?: string
}
declare module 'hono' {
interface ContextRenderer {
(content: string | Promise<string>
content: string | interface Promise<T>
Represents the completion of an asynchronous operationPromise<string>, head: Head | undefined
head?: type Head = {
title?: string;
}
Head): Response | interface Promise<T>
Represents the completion of an asynchronous operationPromise<Response>
}
}
The JSX Renderer middleware allows you to create a Renderer as follows:
import { const jsxRenderer: (component?: ComponentWithChildren, options?: RendererOptions) => MiddlewareHandler
JSX Renderer Middleware for hono.jsxRenderer } from 'hono/jsx-renderer'
export default function jsxRenderer(component?: ComponentWithChildren, options?: RendererOptions): MiddlewareHandler
JSX Renderer Middleware for hono.jsxRenderer(({ children: Child
children, title: string
title }) => {
return (
<JSX.IntrinsicElements.html: HtmlHTMLAttributes
html JSX.HTMLAttributes.lang?: string | undefined
lang='en'>
<JSX.IntrinsicElements.head: JSX.HTMLAttributes
head>
<JSX.IntrinsicElements.meta: MetaHTMLAttributes
meta MetaHTMLAttributes.charset?: StringLiteralUnion<"utf-8"> | undefined
charset='UTF-8' />
<JSX.IntrinsicElements.meta: MetaHTMLAttributes
meta MetaHTMLAttributes.name?: StringLiteralUnion<MetaName> | undefined
name='viewport' MetaHTMLAttributes.content?: string | undefined
content='width=device-width, initial-scale=1.0' />
{title: string
title ? <JSX.IntrinsicElements.title: JSX.HTMLAttributes
title>{title: string
title}</JSX.IntrinsicElements.title: JSX.HTMLAttributes
title> : <></>}
</JSX.IntrinsicElements.head: JSX.HTMLAttributes
head>
<JSX.IntrinsicElements.body: JSX.HTMLAttributes
body>{children: Child
children}</JSX.IntrinsicElements.body: JSX.HTMLAttributes
body>
</JSX.IntrinsicElements.html: HtmlHTMLAttributes
html>
)
})
The _renderer.tsx
is applied under each directory, and the app/routes/posts/_renderer.tsx
is applied in app/routes/posts/*
.
You can write a custom Not Found page in _404.tsx
.
import { type NotFoundHandler<E extends Env = any> = (c: Context<E>) => Response | Promise<Response>
NotFoundHandler } from 'hono'
const const handler: NotFoundHandler
handler: type NotFoundHandler<E extends Env = any> = (c: Context<E>) => Response | Promise<Response>
NotFoundHandler = (c: Context<any, any, {}>
c) => {
return c: Context<any, any, {}>
c.Context<any, any, {}>.render: DefaultRenderer
(content: string | Promise<string>) => Response | Promise<Response>
`.render()` can create a response within a layout.render(<JSX.IntrinsicElements.h1: JSX.HTMLAttributes
h1>Sorry, Not Found...</JSX.IntrinsicElements.h1: JSX.HTMLAttributes
h1>)
}
export default const handler: NotFoundHandler
handler
You can write a custom Error page in _error.tsx
.
import { type ErrorHandler<E extends Env = any> = (err: Error | HTTPResponseError, c: Context<E>) => Response | Promise<Response>
ErrorHandler } from 'hono'
const const handler: ErrorHandler
handler: type ErrorHandler<E extends Env = any> = (err: Error | HTTPResponseError, c: Context<E>) => Response | Promise<Response>
ErrorHandler = (e: Error | HTTPResponseError
e, c: Context<any, any, {}>
c) => {
return c: Context<any, any, {}>
c.Context<any, any, {}>.render: DefaultRenderer
(content: string | Promise<string>) => Response | Promise<Response>
`.render()` can create a response within a layout.render(<JSX.IntrinsicElements.h1: JSX.HTMLAttributes
h1>Error! {e: Error | HTTPResponseError
e.Error.message: string
message}</JSX.IntrinsicElements.h1: JSX.HTMLAttributes
h1>)
}
export default const handler: ErrorHandler
handler