Skip to main content

Overview

Routing in Hono maps HTTP requests to handler functions based on the request method and URL path. Hono provides a simple and intuitive API for defining routes with full type safety.

Defining Routes

Routes are defined using HTTP method helpers on the Hono application instance. Each method corresponds to a standard HTTP verb.

Basic Route Definition

import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => {
  return c.text('Hello Hono!')
})

app.post('/posts', (c) => {
  return c.json({ message: 'Post created' })
})

HTTP Method Handlers

Hono supports all standard HTTP methods through dedicated handler functions:
app.get('/users', (c) => c.text('GET /users'))
app.post('/users', (c) => c.text('POST /users'))
app.put('/users/:id', (c) => c.text('PUT /users/:id'))
app.delete('/users/:id', (c) => c.text('DELETE /users/:id'))
app.patch('/users/:id', (c) => c.text('PATCH /users/:id'))
app.options('/users', (c) => c.text('OPTIONS /users'))

Available Methods

From src/hono-base.ts:104-110:
get!: HandlerInterface<E, 'get', S, BasePath, CurrentPath>
post!: HandlerInterface<E, 'post', S, BasePath, CurrentPath>
put!: HandlerInterface<E, 'put', S, BasePath, CurrentPath>
delete!: HandlerInterface<E, 'delete', S, BasePath, CurrentPath>
options!: HandlerInterface<E, 'options', S, BasePath, CurrentPath>
patch!: HandlerInterface<E, 'patch', S, BasePath, CurrentPath>
all!: HandlerInterface<E, 'all', S, BasePath, CurrentPath>

The all Method

The all method matches any HTTP method:
app.all('/api/*', (c) => {
  return c.text('Matches all methods')
})

Path Patterns

Hono supports various path patterns for flexible route matching.

Static Paths

Exact path matching:
app.get('/about', (c) => c.text('About page'))
app.get('/contact', (c) => c.text('Contact page'))

Path Parameters

Capture dynamic segments using :param syntax:
app.get('/users/:id', (c) => {
  const id = c.req.param('id')
  return c.json({ userId: id })
})

app.get('/posts/:postId/comments/:commentId', (c) => {
  const { postId, commentId } = c.req.param()
  return c.json({ postId, commentId })
})

Wildcard Paths

Match multiple path segments:
app.get('/files/*', (c) => {
  return c.text('Matches /files/any/path/here')
})

The on Method

For custom methods or matching multiple methods, use the on method:
// Single method
app.on('CUSTOM', '/webhook', (c) => {
  return c.text('Custom method handler')
})

// Multiple methods
app.on(['GET', 'POST'], '/dual', (c) => {
  return c.text(`Handled ${c.req.method}`)
})

// Multiple paths
app.on('GET', ['/path1', '/path2'], (c) => {
  return c.text('Multiple paths')
})
From src/hono-base.ts:144-154:
this.on = (method: string | string[], path: string | string[], ...handlers: H[]) => {
  for (const p of [path].flat()) {
    this.#path = p
    for (const m of [method].flat()) {
      handlers.map((handler) => {
        this.#addRoute(m.toUpperCase(), this.#path, handler)
      })
    }
  }
  return this as any
}

Route Grouping

Group routes with a common base path using route:
const api = new Hono()
api.get('/users', (c) => c.json({ users: [] }))
api.get('/posts', (c) => c.json({ posts: [] }))

const app = new Hono()
app.route('/api', api)
// Now accessible at /api/users and /api/posts
From src/hono-base.ts:208-232:
route<
  SubPath extends string,
  SubEnv extends Env,
  SubSchema extends Schema,
  SubBasePath extends string,
  SubCurrentPath extends string,
>(
  path: SubPath,
  app: Hono<SubEnv, SubSchema, SubBasePath, SubCurrentPath>
): Hono<E, MergeSchemaPath<SubSchema, MergePath<BasePath, SubPath>> | S, BasePath, CurrentPath>

Base Paths

Define a base path for all routes in an instance:
const api = new Hono().basePath('/api/v1')

api.get('/users', (c) => c.text('GET /api/v1/users'))
api.get('/posts', (c) => c.text('GET /api/v1/posts'))
From src/hono-base.ts:247-253:
basePath<SubPath extends string>(
  path: SubPath
): Hono<E, S, MergePath<BasePath, SubPath>, MergePath<BasePath, SubPath>> {
  const subApp = this.#clone()
  subApp._basePath = mergePath(this._basePath, path)
  return subApp
}

Route Matching

Routes are matched in the order they are defined. The first matching route handles the request.

Strict Mode

By default, Hono uses strict mode which distinguishes /path from /path/:
const app = new Hono({ strict: true }) // default

app.get('/users', handler) // Matches /users only
app.get('/posts/', handler) // Matches /posts/ only
Disable strict mode to treat both the same:
const app = new Hono({ strict: false })

app.get('/users', handler) // Matches both /users and /users/
From src/hono-base.ts:46-87:
type HonoOptions<E extends Env> = {
  /**
   * `strict` option specifies whether to distinguish whether the last path is a directory or not.
   *
   * @default true
   */
  strict?: boolean
  /**
   * `router` option specifies which router to use.
   */
  router?: Router<[H, RouterRoute]>
  /**
   * `getPath` can handle the host header value.
   */
  getPath?: GetPath<E>
}

Chaining Routes

Multiple handlers can be chained on the same path:
app
  .get('/users', (c) => c.json({ message: 'Getting users' }))
  .post('/users', (c) => c.json({ message: 'Creating user' }))
  .put('/users/:id', (c) => c.json({ message: 'Updating user' }))
From src/hono-base.ts:127-141, routes return the app instance for chaining:
allMethods.forEach((method) => {
  this[method] = (args1: string | H, ...args: H[]) => {
    if (typeof args1 === 'string') {
      this.#path = args1
    } else {
      this.#addRoute(method, this.#path, args1)
    }
    args.forEach((handler) => {
      this.#addRoute(method, this.#path, handler)
    })
    return this as any
  }
})

Route Information

Access registered routes through the routes property:
const app = new Hono()
app.get('/users', handler)
app.post('/posts', handler)

console.log(app.routes)
// Returns RouterRoute[] with route metadata
From src/types.ts:57-62:
interface RouterRoute {
  basePath: string
  path: string
  method: string
  handler: H
}

Best Practices

  • Define specific routes before wildcard routes
  • Use route grouping for better organization
  • Leverage TypeScript for path parameter type safety
  • Use basePath for API versioning
Wildcard routes (*) should be defined last, as they will match any path that hasn’t been matched by previous routes.

Build docs developers (and LLMs) love