Hono Integration

Build powerful APIs with Hono in Bini.js - file-based routing, middleware, and type safety.

File-Based API Routing

Hono is a fast, lightweight web framework that works everywhere. Bini.js integrates Hono with file-based API routing - your file structure defines your API routes.

Hono is the recommended approach for complex APIs in Bini.js. It provides routing, middleware, validation, and strong TypeScript support - all with zero-config file-based routing.

Your API route is determined by the file path inside src/app/api/. The file name becomes the route segment:

app
api
ƒhello.ts
ƒusers.ts
posts
ƒ[id].ts
ƒ[...catch].ts
/api/hello
/api/users
/api/posts/:id
/api/*
File PathAPI Route
src/app/api/hello.ts/api/hello
src/app/api/user.ts/api/user
src/app/api/posts.ts/api/posts
src/app/api/posts/[id].ts/api/posts/:id
src/app/api/[...catch].ts/api/*
There are no root / API routes. Every API file maps to a named route based on its filename. Write your Hono routes without the /api prefix - bini-router strips it in dev/preview and mounts the app under /api in production.

Basic Hono App

Create a Hono app in src/app/api/ and default export it:

src/app/api/hello.ts
// src/app/api/hello.ts -> /api/hello
import { Hono } from 'hono'

const app = new Hono()

app.all('/hello', (c) => {
  return c.json({
    message: 'Hello from Bini.js!',
    timestamp: new Date().toISOString(),
    method: c.req.method,
  })
})

export default app

Routing with Hono

Hono provides a powerful routing system with path parameters, query parameters, and more:

src/app/api/users.ts
// src/app/api/users.ts -> /api/users
import { Hono } from 'hono'

const app = new Hono()

app.get('/users', (c) => c.json({ users: ['alice', 'bob'] }))
app.get('/users/:id', (c) => c.json({ id: c.req.param('id') }))
app.post('/users', async (c) => c.json({ created: await c.req.json() }, 201))
app.put('/users/:id', async (c) =>
  c.json({ id: c.req.param('id'), ...(await c.req.json()) })
)
app.delete('/users/:id', (c) =>
  c.json({ message: `Deleted ${c.req.param('id')}` })
)

export default app
MethodRoute PatternFull URL
GET/users/api/users
GET/users/:id/api/users/123
POST/users/api/users
PUT/users/:id/api/users/123
DELETE/users/:id/api/users/123

Dynamic API Routes

Use [param] in filenames for dynamic segments:

src/app/api/posts/[id].ts
// src/app/api/posts/[id].ts -> /api/posts/:id
import { Hono } from 'hono'

const app = new Hono()
app.get('/posts/:id', (c) => c.json({ id: c.req.param('id') }))
export default app
src/app/api/[...catch].ts
// src/app/api/[...catch].ts -> /api/*
import { Hono } from 'hono'

const app = new Hono()
app.all('*', (c) => c.json({ path: c.req.path }))
export default app

Middleware

Hono has built-in middleware for common tasks:

src/app/api/secure.ts
// src/app/api/secure.ts -> /api/secure
import { Hono } from 'hono'
import { cors, logger, jwt, timeout } from 'hono/middleware'

const app = new Hono()

app.use('*', cors())
app.use('*', logger())
app.use('*', timeout(5000))

app.get('/secure', (c) => c.json({ message: 'Public' }))

export default app
MiddlewarePurpose
corsCross-Origin Resource Sharing
loggerRequest logging
jwtJWT authentication
timeoutRequest timeout
prettyJSONPretty JSON responses

Request Handling

Hono provides convenient methods for accessing request data:

src/app/api/echo.ts
// src/app/api/echo.ts -> /api/echo
import { Hono } from 'hono'

const app = new Hono()

app.all('/echo', async (c) => {
  const params = c.req.param()
  const query = c.req.query()
  const page = c.req.query('page')
  const userAgent = c.req.header('User-Agent')
  const body = await c.req.json().catch(() => null)

  return c.json({
    method: c.req.method,
    path: c.req.path,
    params,
    query: { page, ...query },
    headers: { userAgent },
    body,
  })
})

export default app

Response Handling

Hono provides flexible response methods:

src/app/api/responses.ts
// src/app/api/responses.ts -> /api/responses
import { Hono } from 'hono'

const app = new Hono()

app.get('/json', (c) => c.json({ message: 'Hello' }))
app.get('/text', (c) => c.text('Hello Text'))
app.get('/html', (c) => c.html('<h1>Hello</h1>'))
app.get('/redirect', (c) => c.redirect('https://example.com', 302))
app.post('/created', (c) => c.json({ message: 'Created' }, 201))
app.get('/error', (c) => c.json({ error: 'Error' }, 500))

export default app

Validation

Validate incoming requests with Zod:

src/app/api/posts.ts
// src/app/api/posts.ts -> /api/posts
import { Hono } from 'hono'
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'

const app = new Hono()

const postSchema = z.object({
  title: z.string().min(1).max(100),
  content: z.string().min(1),
})

app.post('/posts', zValidator('json', postSchema), async (c) => {
  const body = c.req.valid('json')
  return c.json({ post: { id: Date.now(), ...body } }, 201)
})

export default app
Install zod and @hono/zod-validator for request validation with TypeScript inference.

Environment Variables

Use getEnv() and requireEnv() from bini-env:

src/app/api/config.ts
// src/app/api/config.ts -> /api/config
import { Hono } from 'hono'
import { getEnv, requireEnv } from 'bini-env'

const app = new Hono()

app.get('/config', (c) => {
  const ctx = c as any
  const apiKey = requireEnv(ctx, 'MY_API_KEY')
  const appName = getEnv(ctx, 'APP_NAME') ?? 'Bini.js'

  return c.json({ appName, hasApiKey: !!apiKey })
})

export default app
Cast c once at the top of your handler with const ctx = c as any. Then use requireEnv(ctx, 'KEY') for required vars and getEnv(ctx, 'KEY') ?? 'default' for optional ones.

Error Handling

Handle errors gracefully with Hono's error handling:

src/app/api/robust.ts
// src/app/api/robust.ts -> /api/robust
import { Hono } from 'hono'
import { getEnv } from 'bini-env'

const app = new Hono()

app.onError((err, c) => {
  const isDev = getEnv(c as any, 'NODE_ENV') === 'development'
  return c.json(
    {
      error: 'Internal Server Error',
      ...(isDev && { details: err.message }),
    },
    500
  )
})

app.notFound((c) => c.json({ error: 'Not Found' }, 404))

app.get('/robust/users/:id', (c) => {
  const id = c.req.param('id')
  if (id === 'admin') {
    return c.json({ error: 'Access denied' }, 403)
  }
  return c.json({ id, name: 'John' })
})

export default app

Nested Routes

Organize complex APIs with nested sub-routers:

src/app/api/index.ts
// src/app/api/index.ts -> /api
import { Hono } from 'hono'

const app = new Hono()

const users = new Hono()
  .get('/users', (c) => c.json({ users: [] }))
  .get('/users/:id', (c) => c.json({ id: c.req.param('id') }))

const posts = new Hono()
  .get('/posts', (c) => c.json({ posts: [] }))
  .get('/posts/:id', (c) => c.json({ id: c.req.param('id') }))

app.route('/', users)
app.route('/', posts)

export default app

When to Use Hono

ScenarioRecommendation
Multiple endpoints in one fileHono - Recommended
Need middleware (CORS, auth, logging)Hono - Recommended
Complex routing patternsHono - Recommended
Production APIs with many routesHono - Recommended
Single endpoint with simple logicPlain handler
Quick prototypesPlain handler
Was this helpful?