Getting Started - Basic

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.

Project Structure

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()],
})

Server Entry File

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) => voidshowRoutes } 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): voidshowRoutes(const app: Hono<Env, BlankSchema, "/">app)

export default const app: Hono<Env, BlankSchema, "/">app

Routes

There are three ways to define routes.

1. 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.
@see{@link https://hono.dev/docs/api/context#render-setrenderer}@example```ts app.get('/', (c) => { return c.render('Hello!') }) ```
render
(
<JSX.IntrinsicElements.div: JSX.HTMLAttributesdiv> <JSX.IntrinsicElements.h1: JSX.HTMLAttributesh1>Hello!</JSX.IntrinsicElements.h1: JSX.HTMLAttributesh1> </JSX.IntrinsicElements.div: JSX.HTMLAttributesdiv> ) })

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: GetCookiegetCookie, const setCookie: (c: Context, name: string, value: string, opt?: CookieOptions) => voidsetCookie } 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: stringname } = 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`
@see{@link https://hono.dev/docs/api/request#parsebody}@example```ts app.post('/entry', async (c) => { const body = await c.req.parseBody() }) ```
parseBody
<{ name: stringname: string }>()
function setCookie(c: Context, name: string, value: string, opt?: CookieOptions): voidsetCookie(c: Context<Env, any, {}>c, 'name', const name: stringname) 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.
@see{@link https://hono.dev/docs/api/context#redirect}@example```ts app.get('/redirect', (c) => { return c.redirect('/') }) app.get('/redirect-permanently', (c) => { return c.redirect('/', 301) }) ```
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: stringname = 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.
@see{@link https://hono.dev/docs/api/context#render-setrenderer}@example```ts app.get('/', (c) => { return c.render('Hello!') }) ```
render
(
<JSX.IntrinsicElements.div: JSX.HTMLAttributesdiv> <JSX.IntrinsicElements.h1: JSX.HTMLAttributesh1>Hello, {const name: stringname}!</JSX.IntrinsicElements.h1: JSX.HTMLAttributesh1> <JSX.IntrinsicElements.form: FormHTMLAttributesform FormHTMLAttributes.method?: HTMLAttributeFormMethod | undefinedmethod='post'> <JSX.IntrinsicElements.input: InputHTMLAttributesinput InputHTMLAttributes.type?: HTMLInputTypeAttribute | undefinedtype='text' InputHTMLAttributes.name?: string | undefinedname='name' InputHTMLAttributes.placeholder?: string | undefinedplaceholder='name' /> <JSX.IntrinsicElements.input: InputHTMLAttributesinput InputHTMLAttributes.type?: HTMLInputTypeAttribute | undefinedtype='submit' /> </JSX.IntrinsicElements.form: FormHTMLAttributesform> </JSX.IntrinsicElements.div: JSX.HTMLAttributesdiv> ) })

2. Using a Hono instance

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.
@templateE - The environment type.@templateS - The schema type.@templateBasePath - The base path type.
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.
@paramoptions - Optional configuration options for the Hono instance.
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: stringname = 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.
@see{@link https://hono.dev/docs/api/routing#path-parameter}@example```ts const name = c.req.param('name') // or all parameters at once const { id, comment_id } = c.req.param() ```
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: stringname, }) }) export default const app: Hono<BlankEnv, BlankSchema, "/">app

3. Just return JSX

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.ElementHome(_c: Context<any, any, {}>_c: class Context<E extends Env = any, P extends string = any, I extends Input = {}>Context) {
  return <JSX.IntrinsicElements.h1: JSX.HTMLAttributesh1>Welcome!</JSX.IntrinsicElements.h1: JSX.HTMLAttributesh1>
}

Renderer

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 | undefinedtitle?: string } declare module 'hono' { interface ContextRenderer { (content: string | Promise<string>content: string | interface Promise<T>
Represents the completion of an asynchronous operation
Promise
<string>, head: Head | undefinedhead?:
type Head = {
    title?: string;
}
Head
): Response | interface Promise<T>
Represents the completion of an asynchronous operation
Promise
<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.
@see{@link https://hono.dev/docs/middleware/builtin/jsx-renderer}@paramcomponent - The component to render, which can accept children and props.@paramoptions - The options for the JSX renderer middleware.@paramoptions.docType - The DOCTYPE to be added at the beginning of the HTML. If set to false, no DOCTYPE will be added.@paramoptions.stream - If set to true, enables streaming response with default headers. If a record is provided, custom headers will be used.@returnsThe middleware handler function.@example```ts const app = new Hono() app.get( '/page/*', jsxRenderer(({ children }) => { return ( <html> <body> <header>Menu</header> <div>{children}</div> </body> </html> ) }) ) app.get('/page/about', (c) => { return c.render(<h1>About me!</h1>) }) ```
jsxRenderer
} from 'hono/jsx-renderer'
export default function jsxRenderer(component?: ComponentWithChildren, options?: RendererOptions): MiddlewareHandler
JSX Renderer Middleware for hono.
@see{@link https://hono.dev/docs/middleware/builtin/jsx-renderer}@paramcomponent - The component to render, which can accept children and props.@paramoptions - The options for the JSX renderer middleware.@paramoptions.docType - The DOCTYPE to be added at the beginning of the HTML. If set to false, no DOCTYPE will be added.@paramoptions.stream - If set to true, enables streaming response with default headers. If a record is provided, custom headers will be used.@returnsThe middleware handler function.@example```ts const app = new Hono() app.get( '/page/*', jsxRenderer(({ children }) => { return ( <html> <body> <header>Menu</header> <div>{children}</div> </body> </html> ) }) ) app.get('/page/about', (c) => { return c.render(<h1>About me!</h1>) }) ```
jsxRenderer
(({ children: Childchildren, title: stringtitle }) => {
return ( <JSX.IntrinsicElements.html: HtmlHTMLAttributeshtml JSX.HTMLAttributes.lang?: string | undefinedlang='en'> <JSX.IntrinsicElements.head: JSX.HTMLAttributeshead> <JSX.IntrinsicElements.meta: MetaHTMLAttributesmeta MetaHTMLAttributes.charset?: StringLiteralUnion<"utf-8"> | undefinedcharset='UTF-8' /> <JSX.IntrinsicElements.meta: MetaHTMLAttributesmeta MetaHTMLAttributes.name?: StringLiteralUnion<MetaName> | undefinedname='viewport' MetaHTMLAttributes.content?: string | undefinedcontent='width=device-width, initial-scale=1.0' /> {title: stringtitle ? <JSX.IntrinsicElements.title: JSX.HTMLAttributestitle>{title: stringtitle}</JSX.IntrinsicElements.title: JSX.HTMLAttributestitle> : <></>} </JSX.IntrinsicElements.head: JSX.HTMLAttributeshead> <JSX.IntrinsicElements.body: JSX.HTMLAttributesbody>{children: Childchildren}</JSX.IntrinsicElements.body: JSX.HTMLAttributesbody> </JSX.IntrinsicElements.html: HtmlHTMLAttributeshtml> ) })

The _renderer.tsx is applied under each directory, and the app/routes/posts/_renderer.tsx is applied in app/routes/posts/*.

Not Found page

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: NotFoundHandlerhandler: 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.
@see{@link https://hono.dev/docs/api/context#render-setrenderer}@example```ts app.get('/', (c) => { return c.render('Hello!') }) ```
render
(<JSX.IntrinsicElements.h1: JSX.HTMLAttributesh1>Sorry, Not Found...</JSX.IntrinsicElements.h1: JSX.HTMLAttributesh1>)
} export default const handler: NotFoundHandlerhandler

Error Page

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: ErrorHandlerhandler: type ErrorHandler<E extends Env = any> = (err: Error | HTTPResponseError, c: Context<E>) => Response | Promise<Response>ErrorHandler = (e: Error | HTTPResponseErrore, 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.
@see{@link https://hono.dev/docs/api/context#render-setrenderer}@example```ts app.get('/', (c) => { return c.render('Hello!') }) ```
render
(<JSX.IntrinsicElements.h1: JSX.HTMLAttributesh1>Error! {e: Error | HTTPResponseErrore.Error.message: stringmessage}</JSX.IntrinsicElements.h1: JSX.HTMLAttributesh1>)
} export default const handler: ErrorHandlerhandler