Skip to main content

HTTP Method Handlers

Hono provides method-specific handlers for defining routes. Each handler corresponds to an HTTP method:
const app = new Hono()

app.get('/hello', (c) => c.text('GET request'))
app.post('/hello', (c) => c.text('POST request'))
app.put('/hello', (c) => c.text('PUT request'))
app.delete('/hello', (c) => c.text('DELETE request'))
app.patch('/hello', (c) => c.text('PATCH request'))
app.options('/hello', (c) => c.text('OPTIONS request'))
The available method handlers are defined in src/router.ts:17:
export const METHODS = ['get', 'post', 'put', 'delete', 'options', 'patch'] as const

Method Chaining

You can chain multiple handlers on the same path:
app.post('/books').get((c) => {
  return c.text('Get books')
})

// This creates a GET handler for /books

Handlers Without a Path

You can define handlers without specifying a path. The path from the previous call will be used:
const app = new Hono()

app.get((c) => {
  return c.text('Hello from root')
})
// This handles GET requests to '/'

All Methods Handler

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

Custom Methods with on()

For custom HTTP methods or multiple methods at once, use on():
// Single method
app.on('PURGE', '/cache', (c) => {
  return c.text('Cache purged')
})

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

// Multiple paths
app.on('GET', ['/hello', '/hi'], (c) => {
  return c.text('Hello')
})
The implementation in src/hono-base.ts:144 shows how on() works:
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 Order and Matching

Routes are matched in the order they are defined. However, the specific router implementation determines the exact matching behavior:
The default router uses a smart matching algorithm that prioritizes more specific routes over wildcards.
app.get('/posts/featured', (c) => c.text('Featured posts'))
app.get('/posts/:id', (c) => c.text(`Post ${c.req.param('id')}`))

// GET /posts/featured -> "Featured posts"
// GET /posts/123 -> "Post 123"

Wildcard Routes

Use * to match any path segment:
// Match any path under /api
app.get('/api/*', (c) => {
  return c.text('API endpoint')
})

// Match everything
app.get('*', (c) => {
  return c.text('Catch all')
})
From src/router/reg-exp-router/router.ts:149, wildcards are handled specially:
if (path === '/*') {
  path = '*'
}

Pattern Matching

Hono supports various path patterns:
// Static paths
app.get('/about', (c) => c.text('About'))

// With parameters
app.get('/users/:id', (c) => c.text(`User ${c.req.param('id')}`))

// Optional segments (see next page for details)
app.get('/posts/:id/comments?', (c) => c.text('Post with optional comments'))

// Regex patterns
app.get('/date/:date{[0-9]+}', (c) => {
  return c.text(`Date: ${c.req.param('date')}`)
})

Multiple Handlers

You can register multiple handlers for the same route by calling the method multiple times:
// First handler (middleware-like)
app.get('/hello', async (c, next) => {
  console.log('Before handler')
  await next()
})

// Second handler (final response)
app.get('/hello', (c) => {
  return c.text('Hello!')
})
Alternatively, pass multiple handlers in one call:
app.get('/hello', 
  async (c, next) => {
    console.log('Middleware')
    await next()
  },
  (c) => c.text('Hello!')
)
The implementation in src/hono-base.ts:130 shows this pattern:
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
}

Strict Mode

By default, Hono uses strict mode which distinguishes between paths with and without trailing slashes:
// With strict mode (default)
const app = new Hono({ strict: true })
app.get('/hello', (c) => c.text('Hello'))

// GET /hello -> 200
// GET /hello/ -> 404

// Without strict mode
const app2 = new Hono({ strict: false })
app2.get('/hello', (c) => c.text('Hello'))

// GET /hello -> 200
// GET /hello/ -> 200
From src/hono-base.ts:172, the strict option affects the path matching:
this.getPath = (strict ?? true) ? (options.getPath ?? getPath) : getPathNoStrict

Build docs developers (and LLMs) love