Skip to content

Best Practices

Hono is very flexible. You can write your app as you like. However, there are best practices that are better to follow.

Don't make "Controllers" when possible

When possible, you should not create "Ruby on Rails-like Controllers".

ts
// 🙁
// A RoR-like Controller
const booksList = (c: Context) => {
  return c.json('list books')
}

app.get('/books', booksList)

The issue is related to types. For example, the path parameter cannot be inferred in the Controller without writing complex generics.

ts
// 🙁
// A RoR-like Controller
const bookPermalink = (c: Context) => {
  const id = c.req.param('id') // Can't infer the path param
  return c.json(`get ${id}`)
}

Therefore, you don't need to create RoR-like controllers and should write handlers directly after path definitions.

ts
// 😃
app.get('/books/:id', (c) => {
  const id = c.req.param('id') // Can infer the path param
  return c.json(`get ${id}`)
})

factory.createHandlers() in hono/factory

If you still want to create a RoR-like Controller, use factory.createHandlers() in hono/factory. If you use this, type inference will work correctly.

ts
import { createFactory } from 'hono/factory'
import { logger } from 'hono/logger'

// ...

// 😃
const factory = createFactory()

const middleware = factory.createMiddleware(async (c, next) => {
  c.set('foo', 'bar')
  await next()
})

const handlers = factory.createHandlers(logger(), middleware, (c) => {
  return c.json(c.var.foo)
})

app.get('/api', ...handlers)

Building a larger application

Use app.route() to build a larger application without creating "Ruby on Rails-like Controllers".

If your application has /authors and /books endpoints and you wish to separate files from index.ts, create authors.ts and books.ts.

ts
// authors.ts
import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => c.json('list authors'))
app.post('/', (c) => c.json('create an author', 201))
app.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))

export default app
ts
// books.ts
import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => c.json('list books'))
app.post('/', (c) => c.json('create a book', 201))
app.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))

export default app

Then, import them and mount on the paths /authors and /books with app.route().

ts
// index.ts
import { Hono } from 'hono'
import authors from './authors'
import books from './books'

const app = new Hono()

// 😃
app.route('/authors', authors)
app.route('/books', books)

export default app

If you want to use RPC features

The code above works well for normal use cases. However, if you want to use the RPC feature, you can get the correct type by chaining as follows.

ts
// authors.ts
import { Hono } from 'hono'

const app = new Hono()
  .get('/', (c) => c.json('list authors'))
  .post('/', (c) => c.json('create an author', 201))
  .get('/:id', (c) => c.json(`get ${c.req.param('id')}`))

export default app

If you pass the type of the app to hc, it will get the correct type.

ts
import app from './authors'
import { hc } from 'hono/client'

// 😃
const client = hc<typeof app>('http://localhost') // Typed correctly

For more detailed information, please see the RPC page.

Released under the MIT License.