For AI agents: the complete documentation index is available at https://dotpress.dev/llms.txt, the full documentation bundle is available at https://dotpress.dev/llms-full.txt, and this page is available as Markdown at https://dotpress.dev/examples/hono.md.

Hono

The same notes API on Hono: one app, served by Node.js or by AWS Lambda.

  • native Hono middlewares (secureHeaders()) applied to the Dotpress routes, and a native Hono route (/health);
  • two entries: node.ts with @hono/node-server, lambda.ts with the main entry of the adapter, which has no Node.js server;
  • tests in memory with adapter.fetch() (Web Request and Response), over HTTP, and on simulated API Gateway events.

Source: examples/hono.

Run it

npx degit dotpressjs/dotpress/examples/hono my-api
cd my-api
npm install
npm run dev
curl -X POST localhost:3000/notes -H 'content-type: application/json' -d '{"text":"Hello"}'
curl localhost:3000/notes
curl localhost:3000/health

The routes

The same routes as the Fastify example, unchanged: they do not depend on the server.

import { createRouter, notFoundError } from '@dotpress/core'
import { z } from 'zod'
import type { NoteStore } from './notes.ts'

const Note = z.object({
  id: z.uuid(),
  text: z.string(),
  createdAt: z.iso.datetime(),
})

/** Dotpress routes: they know nothing about Hono or AWS Lambda. */
export const notesRouter = (store: NoteStore) => {
  const router = createRouter()

  router.defineRoute({
    path: '/notes',
    schema: (z) => ({ response: z.array(Note) }),
    handler: () => store.list(),
  })

  router.defineRoute({
    method: 'post',
    path: '/notes',
    status: 201,
    schema: (z) => ({
      body: z.object({ text: z.string().trim().min(1).max(1000) }),
      response: Note,
    }),
    handler: ({ req }) => store.create(req.body.text),
  })

  router.defineRoute({
    path: '/notes/:id',
    schema: () => ({ response: Note }),
    handler: async ({ req }) =>
      (await store.get(req.params.id)) ?? notFoundError('Note not found'),
  })

  return router
}

The app

The app takes its adapter as a parameter: the options of the adapter are shared, the adapter itself comes from the entry.

import type { HonoAdapterOptions } from '@dotpress/adapter-hono'
import { createApp, type HttpAdapter, type Logger } from '@dotpress/core'
import type { Hono } from 'hono'
import { secureHeaders } from 'hono/secure-headers'
import { createNoteStore } from './notes.ts'
import { notesRouter } from './routes.ts'

/** Options of the Hono adapter, the same on Node.js and AWS Lambda. */
export const honoOptions: HonoAdapterOptions<Hono> = {
  // Native Hono middlewares and routes, before the Dotpress routes.
  beforeRoutes: (app) => {
    app.use('*', secureHeaders())
    app.get('/health', (c) => c.json({ status: 'ok' }))
  },
}

/**
 * Creates the app on a Hono adapter: `honoAdapter()` of
 * `@dotpress/adapter-hono/node` in `node.ts`, of `@dotpress/adapter-hono`
 * in `lambda.ts` and the tests.
 */
export const createNotesApp = <TAdapter extends HttpAdapter>(
  adapter: TAdapter,
  options: { logger?: Logger } = {}
) =>
  createApp({
    adapter,
    ...(options.logger && { logger: options.logger }),
    routers: [notesRouter(createNoteStore())],
  })

Two entries

// The Node.js entry: @hono/node-server serves the app.
import { honoAdapter } from '@dotpress/adapter-hono/node'
import { createNotesApp, honoOptions } from './app.ts'

const port = Number(process.env.PORT ?? 3000)

const app = await createNotesApp(honoAdapter(honoOptions))
await app.listen(port)
console.log(`Notes API: http://localhost:${port}/notes`)

// Waits for pending requests, then runs the onClose hooks of the plugins.
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
  process.once(signal, () => void app.close())
}
// The AWS Lambda entry: API Gateway (REST and HTTP APIs), Function URLs and
// Application Load Balancers. The main entry of the adapter has no Node.js
// server: the bundle stays small.
import { honoAdapter } from '@dotpress/adapter-hono'
import { createLambdaHandler } from '@dotpress/adapter-hono/lambda'
import { createNotesApp, honoOptions } from './app.ts'

// The app is created at the first invocation (cold start), then reused by
// the next invocations of the execution environment.
export const handler = createLambdaHandler(() =>
  createNotesApp(honoAdapter(honoOptions))
)

npm run build:lambda bundles the handler into dist/lambda.mjs: about 115 kB gzipped, Hono and Zod included, the smallest of the adapters. See Choosing an adapter.

Learn more