Skip to Content
getting-startedBasic Concepts

Last Updated: 3/9/2026


Basic Concepts

Understand the core concepts that power Hono applications: requests, responses, middleware, and the application lifecycle.

Prerequisites

Before reading this, complete Hello World to understand basic routing.

The Request/Response Cycle

Every Hono application follows this flow:

Request → Middleware → Handler → Response
  1. Request arrives from the client
  2. Middleware processes the request (optional)
  3. Handler generates the response
  4. Response returns to the client

Handlers vs. Middleware

Understanding the difference is crucial:

Handler

A handler returns a Response object. Only one handler executes per request.

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

Middleware

A middleware calls await next() to pass control to the next middleware or handler.

app.use(async (c, next) => { console.log('Before handler') await next() // Continue to next middleware/handler console.log('After handler') })

Middleware can also return a Response to short-circuit the chain:

app.use('/admin/*', async (c, next) => { const token = c.req.header('Authorization') if (!token) { return c.text('Unauthorized', 401) // Stop here } await next() // Continue if authorized })

The Context Object

The Context object (c) is your interface to everything:

Request Access

app.get('/example', (c) => { // Headers const userAgent = c.req.header('User-Agent') // Path parameters const id = c.req.param('id') // Query parameters const page = c.req.query('page') // Request body (async) const body = await c.req.json() return c.text('OK') })

Response Generation

app.get('/example', (c) => { // Set status code c.status(201) // Set headers c.header('X-Custom', 'value') // Return response return c.json({ ok: true }) })

Variables

Store and retrieve values within a request:

app.use(async (c, next) => { c.set('startTime', Date.now()) await next() }) app.get('/', (c) => { const startTime = c.get('startTime') const duration = Date.now() - startTime return c.text(`Request took ${duration}ms`) })

Learn more in Context API.

Middleware Execution Order

Middleware executes in registration order:

app.use(async (c, next) => { console.log('1: start') await next() console.log('1: end') }) app.use(async (c, next) => { console.log('2: start') await next() console.log('2: end') }) app.get('/', (c) => { console.log('handler') return c.text('Hello') })

Output:

1: start 2: start handler 2: end 1: end

This “onion” pattern lets you wrap handlers with pre/post processing.

Using Built-in Middleware

Hono includes useful middleware out of the box:

import { Hono } from 'hono' import { logger } from 'hono/logger' import { cors } from 'hono/cors' import { basicAuth } from 'hono/basic-auth' const app = new Hono() // Apply to all routes app.use(logger()) app.use(cors()) // Apply to specific paths app.use('/admin/*', basicAuth({ username: 'admin', password: 'secret', })) app.get('/admin/dashboard', (c) => { return c.text('Admin Dashboard') })

See all built-in middleware in the Reference.

Route Matching

Routes are matched in registration order:

app.get('/posts/new', (c) => c.text('New post form')) app.get('/posts/:id', (c) => c.text('Post detail'))

Visit /posts/new → “New post form” (first match wins)

Wildcards

app.get('/posts/*', (c) => c.text('Any post path'))

Matches /posts/, /posts/123, /posts/123/comments, etc.

Method Matching

// Match any HTTP method app.all('/api/*', (c) => { c.header('X-API-Version', '1.0') return c.text('API') }) // Match specific methods app.on(['PUT', 'DELETE'], '/posts/:id', (c) => { return c.text('Update or delete') })

Grouping Routes

Organize related routes:

const api = new Hono() api.get('/users', (c) => c.json({ users: [] })) api.post('/users', (c) => c.json({ created: true }, 201)) const app = new Hono() app.route('/api', api) // Mount at /api

Now /api/users is available.

Learn more in Building Larger Applications.

Error Handling

Handle errors globally:

app.onError((err, c) => { console.error(`Error: ${err.message}`) return c.text('Internal Server Error', 500) }) app.get('/error', (c) => { throw new Error('Something went wrong!') })

Not Found Handler

Customize 404 responses:

app.notFound((c) => { return c.text('Custom 404 Not Found', 404) })

Testing Your App

Test without starting a server:

// In your test file const res = await app.request('/hello') expect(res.status).toBe(200) expect(await res.text()).toBe('Hello!')

Learn more in the Testing Guide.

What’s Next

Understand middleware: Read the Middleware Guide to learn about built-in and custom middleware.

Learn routing: Check out the Routing Guide for advanced patterns.

Add validation: See the Validation Guide to validate requests with Zod.

Deploy your app: Explore Deployment guides for your platform.