Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Route groups and separate files

As routes multiply, src/index.ts turns into a long list. In Hono a group of routes is written as a separate small app, new Hono(), in its own file, and the main app mounts it with app.route() under a common address prefix.

A file with post routes

Create a src/routes folder with a posts.ts file in it:

// src/routes/posts.ts
import { Hono } from 'hono'
 
const posts = new Hono()
 
posts.get('/', (c) => c.text('List of posts'))
posts.get('/:id', (c) => c.text(`Post ${c.req.param('id')}`))
posts.post('/', (c) => c.text('Post created', 201))
 
export default posts

Addresses here are relative to the future prefix: / is the list itself and /:id is one post.

Mounting

In src/index.ts import the group and mount it:

// src/index.ts
import { serve } from '@hono/node-server'
import { Hono } from 'hono'
import posts from './routes/posts.js'
 
const app = new Hono()
 
app.route('/posts', posts)
 
serve({ fetch: app.fetch, port: 3000 })

Now GET /posts returns "List of posts" and GET /posts/7 returns "Post 7". The .js extension in the import is required by the NodeNext module setting, even though the file is named posts.ts.

Nested resources

Comments belong to a post, so their address starts with the post number. Create src/routes/comments.ts:

// src/routes/comments.ts
import { Hono } from 'hono'
 
const comments = new Hono()
 
comments.get('/:postId/comments', (c) => {
  return c.text(`Comments on post ${c.req.param('postId')}`)
})
 
export default comments

And mount it under the same prefix:

// src/index.ts
import comments from './routes/comments.js'
 
app.route('/posts', comments)

The address /posts/3/comments returns "Comments on post 3". Several groups can share one prefix.

A common API prefix

If every API address should start with /api, set it once with basePath():

// src/index.ts
const app = new Hono().basePath('/api')
 
app.route('/posts', posts)
app.route('/posts', comments)

Now the post list lives at /api/posts and the comments at /api/posts/3/comments.

Project structure

src/
├── index.ts          the app, mounting groups, starting the server
└── routes/
    ├── posts.ts
    └── comments.ts

From here on, post routes are written in src/routes/posts.ts.

Common mistake

Repeating the prefix inside the group file:

// src/routes/posts.ts
posts.get('/posts/:id', (c) => c.text('Post'))

After app.route('/posts', posts) this route ends up at /posts/posts/5, and /posts/5 returns 404. Inside a group, write addresses without the prefix.

Thanks for reading!

Donate to the site — we work hard for you.