Hono example
The apps/example-hono directory contains a working Hono API that wires
every @rtorcato/api-* package together. The API contract is identical to
the Express example — same routes, same response shapes —
so you can compare the two implementations side by side.
What it demonstrates
| Package | Usage in the example |
|---|---|
api-config | loadEnv() validates PORT + LOG_LEVEL from .env at startup |
api-logger | createLogger() gives a pino logger, pretty-printed in dev |
api-auth | signToken() issues a JWT on POST /login |
api-auth-hono | authMiddleware() guards GET /me and exposes the user via c.get('user') |
api-errors | BadRequestError (failed validation) + NotFoundError (missing item) are thrown from handlers |
api-errors-hono | errorHandler() + notFoundHandler via app.onError / app.notFound |
api-health | createHealthRegistry() tracks the readiness checks |
api-health-hono | livenessHandler() / readinessHandler() back /healthz + /readyz |
api-rate-limit-hono | rateLimitMiddleware() — 100 req/min sliding window keyed on forwarded IP |
api-timeout-hono | timeoutMiddleware({ ms }) — a slow handler is failed with a 503 instead of hanging |
api-webhooks-hono | webhookMiddleware() verifies an HMAC signature on POST /webhooks (opt-in via webhookSecret) |
api-response | ok() wraps every success payload in { success: true, data } |
api-openapi-hono | configureOpenAPI() serves the schema-generated OpenAPI 3.1 doc at /doc and the Scalar reference UI at /reference |
api-graceful-shutdown | createShutdownController() drains the server on SIGTERM/SIGINT (in index.ts) |
Request bodies are validated by the @hono/zod-openapi route schemas — a failed
parse routes through the shared error envelope via the app's defaultHook, so
this example needs no separate validation package.
Routes
GET /items
POST /items { "name": string }
GET /items/:id
DELETE /items/:id
POST /login { "username": string } → { token }
GET /me Bearer token required
POST /webhooks HMAC-verified receiver (when webhookSecret is set)
GET /healthz liveness probe
GET /readyz readiness probe
GET /doc OpenAPI 3.1 document
GET /reference Scalar API reference
Items are stored in-memory — no database required.
Run locally
cd apps/example-hono
cp .env.example .env
pnpm dev
The server starts on http://localhost:3002 with pretty-printed logs.
- Scalar API reference →
http://localhost:3002/reference - OpenAPI 3.1 document →
http://localhost:3002/doc
Run with Docker
cd apps/example-hono
docker compose up
The image builds from the monorepo root, installs workspace packages, and
starts the server on port 3002.
Quick smoke test
# List items (empty)
curl http://localhost:3002/items
# Create
curl -X POST http://localhost:3002/items \
-H 'Content-Type: application/json' \
-d '{"name":"world"}'
# 404 for a bad ID
curl http://localhost:3002/items/bad-id
# 400 validation error — missing name
curl -X POST http://localhost:3002/items \
-H 'Content-Type: application/json' \
-d '{}'