Hono — небольшой веб-фреймворк на TypeScript для Cloudflare Workers, Deno, Bun, Node.js и других сред с Web API. Он даёт роутер, middleware и удобный контекст запроса, оставаясь близким к стандартным Request и Response.
Разберём маршруты, валидацию, bindings на edge, тесты и деплой. Примеры рассчитаны на Hono 4.x и Node.js 20+.
Почему Hono подходит для edge
Edge-платформы запускают код близко к пользователю и ограничивают окружение: нет файловой системы и долгоживущих процессов, доступны только Web API и привязки платформы. Hono не тянет Node-специфичные зависимости и собирается в компактный бандл.
- Роутер с параметрами, wildcard и регулярными ограничениями.
- Готовые middleware: CORS, логирование, JWT, кеш, сжатие.
- Типизированный клиент RPC для фронтенда.
- Адаптеры для разных рантаймов без смены кода приложения.
Состояние между запросами не хранится в памяти, а процессорное время и размер бандла лимитированы тарифом. Для данных используйте KV, D1, R2 или внешнюю базу.
Создание проекта
Генератор предложит шаблон и рантайм: для Cloudflare выбирайте Workers, для VPS — Node.js.
npm create hono@latest my-api
cd my-api
npm install
npm run dev
В шаблоне есть точка входа и пример маршрута. Для Node.js используется @hono/node-server, для Workers — wrangler dev. Включите строгий TypeScript: он ловит ошибки в параметрах и bindings.
npm install hono @hono/node-server
npm run build
npx wrangler dev
npx wrangler deploy
Маршруты и контекст
Маршрут получает контекст c: из него читают параметры, заголовки и тело, в него пишут ответ. Параметры пути объявляются двоеточием, ограничения — регулярным выражением в фигурных скобках.
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('ok'))
app.get('/users/:id{[0-9]+}', (c) => {
const id = c.req.param('id')
return c.json({ id: Number(id) })
})
app.get('/search', (c) => c.json({ q: c.req.query('q') ?? '' }))
Группы маршрутов подключаются через app.route('/api', api), базовый путь задаётся app.basePath('/v1'). Вложенные приложения собираются из отдельных экземпляров Hono.
Тело запроса читается один раз: получите объект через await c.req.json() и передайте дальше. Ошибку разбора JSON обрабатывайте явно, иначе клиент получит 500 вместо 400.
Middleware и обработка ошибок
Middleware — функция, которая получает контекст и next. Она может изменить запрос, добавить заголовки или прервать цепочку. Порядок регистрации важен: он определяет порядок выполнения.
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
import { secureHeaders } from 'hono/secure-headers'
app.use('*', logger())
app.use('/api/*', cors({ origin: 'https://example.com' }))
app.use('*', secureHeaders())
app.onError((err, c) => {
console.error(err)
return c.json({ error: 'internal' }, 500)
})
Для авторизации есть basicAuth, bearerAuth и jwt. JWT-middleware проверяет подпись и кладёт payload в переменные контекста.
import { jwt } from 'hono/jwt'
app.use('/admin/*', jwt({ secret: 'CHANGE_ME' }))
app.get('/admin/me', (c) => c.json(c.get('jwtPayload')))
Секрет в примере — заглушка. В продакшене берите значение из c.env или хранилища секретов и не логируйте токены.
Валидация и типизированный клиент
Схемы удобно описывать через Zod и подключать @hono/zod-validator. Валидатор возвращает 400 с деталями и передаёт проверенные данные в обработчик.
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const schema = z.object({ name: z.string().min(1), age: z.number().int() })
app.post('/users', zValidator('json', schema), (c) => {
const body = c.req.valid('json')
return c.json(body, 201)
})
Типы можно прокинуть на клиент через RPC. Экспортируйте тип приложения и используйте hc: клиент будет знать пути и формы ответов.
export type AppType = typeof app
const client = hc<AppType>('https://api.example.com')
const res = await client.users.$post({ json: { name: 'Ann', age: 30 } })
const data = await res.json()
RPC не заменяет контракт для внешних потребителей: для публичного API нужна OpenAPI-схема. Зато в монорепозитории он убирает рассинхрон фронтенда и бэкенда.
Bindings и работа с данными на edge
На Cloudflare окружение приходит в c.env. Типизируйте его через generic Hono<{ Bindings: Env }>, чтобы редактор подсказывал имена привязок.
type Env = {
Bindings: { KV: KVNamespace; DB: D1Database }
}
const app = new Hono<Env>()
app.get('/cache/:key', async (c) => {
const value = await c.env.KV.get(c.req.param('key'))
return value ? c.text(value) : c.notFound()
})
KV подходит для чтения редкозаписываемых значений, D1 — для SQL, R2 — для файлов. Помните о согласованности: KV распространяется с задержкой, поэтому счётчики и блокировки на нём строить нельзя. Для транзакций используйте D1 или внешнюю PostgreSQL.
Фоновые задачи запускаются через c.executionCtx.waitUntil(promise); платформа дождётся завершения в пределах лимита.
Тесты и деплой
Приложение можно вызвать без сети: app.request() создаёт запрос и возвращает ответ — быстрый способ проверить маршруты в тестах.
import { describe, it, expect } from 'vitest'
describe('api', () => {
it('returns ok', async () => {
const res = await app.request('/')
expect(res.status).toBe(200)
})
})
Для VPS используйте адаптер Node.js и systemd: приложение слушает localhost, nginx принимает TLS.
import { serve } from '@hono/node-server'
serve({ fetch: app.fetch, port: 3000, hostname: '127.0.0.1' })
Юнит systemd:
[Unit]
Description=my-api
After=network.target
[Service]
WorkingDirectory=/srv/my-api
ExecStart=/usr/bin/node dist/index.js
Restart=always
User=www-data
[Install]
WantedBy=multi-user.target
После systemctl enable --now my-api проверьте curl и journalctl -u my-api -f. Для Cloudflare смотрите wrangler tail.
Чек-лист перед продакшеном
- Секреты вынесены в bindings или переменные окружения.
- Включены CORS только для нужных origin и security-заголовки.
- Ограничен размер тела запроса и настроен таймаут.
- Ошибки логируются без персональных данных и токенов.
- Есть тесты на ключевые маршруты и валидацию.
- Понятны лимиты процессорного времени, памяти и времени выполнения платформы.
Итог
Hono даёт минимальный, но достаточный набор для API на edge: роутер, middleware, валидацию и типизированный клиент. Он не скрывает Request и Response, поэтому код легко переносится между рантаймами. Начните с одного приложения и теста через app.request, затем добавьте bindings и деплой. Если проект требует файловой системы, фоновых демонов и тяжёлых зависимостей, проще взять Node.js на VPS — Hono там тоже работает.