Hono.jsを試してみた

はじめに

Hono.js という日本人が作った JavaScript/TypeScript のフレームワークがあります。
とにかく軽くて速いらしい、ということで触ってみました。

Hono.jsとは?

Hono.jsの公式ドキュメントでは、以下のように説明されています。

Hono - means flame🔥 in Japanese - is a small, simple, and ultrafast web framework built on Web Standards. It works on any JavaScript runtime: Cloudflare Workers, Fastly Compute, Deno, Bun, Vercel, Netlify, AWS Lambda, Lambda@Edge, and Node.js.

(訳:Hono.jsは、日本語で「炎」を意味する小さくシンプルで超高速なWebフレームワークです。Web標準に基づいて構築されており、Cloudflare Workers、Fastly Compute、Deno、Bun、Vercel、AWS Lambda、Lambda@Edge、Node.jsなど、さまざまなJavaScriptランタイム上で動作します。)

正規表現ベースのRegExpRouter を採用しており、ルーティングが速いのが特徴だそうです。
また、Hono.js の最小構成バージョンであるhono/tinyプリセットは14kB未満で軽量です。この状態では用途は限られますが、依存関係がなくWeb標準APIのみとすることで軽量さを実現しているようです。

Hono.js のリクエスト処理能力

同じ言語のフレームワークである Express 等との比較はよく見かけるので、社内の開発で使うことの多い PHP の Laravel とそのリクエスト処理能力を比較してみました。

どちらも、{ message: "Hello, world!" }を返すだけの /hello というエンドポイントを用意しました。
ベンチマークツールには、wrk を用いています。

Hono.js vs Laravel(JSONレスポンス)

いくつか項目を抜粋して比較します。

項目 Hono.js Laravel
Requests/sec (リクエスト処理数) 20,949.58 83.68
Latency (平均遅延) 2.57ms 191.17ms
Transfer/sec (データ転送量) 3.64MB/sec 96.10KB/sec
  • Hono.js は Laravel の約250倍のリクエスト処理能力
  • 応答速度も Hono.js の方が74倍以上速い
  • 転送量も Hono.js の方が約38倍多い

この差が生じる理由には以下が挙げられます。

  1. PHP + Laravel は基本的に同期処理
    • Laravel はリクエストごとにPHPプロセスを起動するためオーバーヘッドが大きい
    • Hono.js(Node.js)は 非同期で並行処理 できる
  2. Laravel は JSON を返すのに内部で json_encode() している
  3. Laravel はミドルウェア処理が多い
    • デフォルトでセッション、認証、ログ記録などのミドルウェアが動く

今回は、Laravel を高速化するための工夫を行っていない状態で数値を取っています。
オーバーヘッドを減らすためのツールを用いたり、余分なミドルウェアを回避したり、キャッシュを活用するなど、Laravel の処理をもっと速くすることは可能です。

とはいえ、素の状態では、公式の言うとおり Hono.js がとにかく速いことが分かりました。

環境構築

Windows上のNode.js で動作させるように、ローカル環境を構築したので手順を載せます。

Hono.js の特徴を活かすべく、APIサーバーとして活用する想定で以下のような構成にしてみました。

hono-app/
├── frontend/      # フロントエンド
└── backend/       # バックエンド (Hono.js APIサーバー)
    ├── src/
    │   └──  index.ts
    ├── package.json
    ├── tsconfig.json
    └── nodemon.json
  1. プロジェクトのセットアップ
cd hono-app
mkdir backend && cd backend
npm init -y
  1. 必要なパッケージのインストール
npm install hono dotenv
npm install --save-dev typescript ts-node nodemon @types/node
npm install @hono/node-server
  1. TypeScriptの設定(backend/tsconfig.json)
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "CommonJS",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true
  }
}
  1. 開発用設定ファイル (backend/nodemon.json)
{
  "watch": ["src"],
  "ext": "ts",
  "exec": "ts-node src/index.ts"
}
  1. APIサーバーの作成(backend/src/index.ts)
import { Hono } from 'hono'
import { serve } from '@hono/node-server'
import dotenv from 'dotenv'

dotenv.config()

const app = new Hono()

app.get('/', (c) => c.text('Hello, Hono!'))

const port = 3000
serve({ fetch: app.fetch, port: Number(port) })

console.log(`Server is running at http://localhost:${port}`)
  1. 開発用スクリプトの追加 (backend/package.json)
"scripts": {
  "dev": "nodemon",
  "build": "tsc",
  "start": "node dist/index.js"
}
  1. 開発サーバーの起動
npm run dev
  1. 起動確認

ローカル環境でサーバーを起動し、 curl http://localhost:3000/ を実行すると、
Hello Hono!のテキストが返ってきます。

エンドポイントを追加してみる

ルートマッピングはbackend/src/index.tsのような形式で追加できますが、設定方法には他にも種類があったのでそのうちのひとつを紹介します。

まずは、編集内容を載せます。

  1. APIサーバー(backend/src/index.ts)
import { Hono } from 'hono'
import { serve } from '@hono/node-server'
import dotenv from 'dotenv'
import { userRoutes } from './routes/users'
import { productRoutes } from './routes/products'

dotenv.config()

const app = new Hono()

app.get('/', (c) => c.text('Hello, Hono!'))

app.route('/users', userRoutes)      // 追加
app.route('/products', productRoutes)   // 追加

const port = process.env.PORT || 3000
serve({ fetch: app.fetch, port: Number(port) })

console.log(`Server is running at http://localhost:${port}`)
  1. ルートファイルの作成
  • backend/src/routes/users.ts
import { Hono } from 'hono'

export const userRoutes = new Hono()

userRoutes.get('/', (c) => {
  return c.json({ message: 'User list' })
})
  • backend/src/routes/products.ts
import { Hono } from 'hono'

export const productRoutes = new Hono()

productRoutes.get('/', (c) => {
  return c.json({ message: 'Product list' })
})

エンドポイントとレスポンス

  • http://localhost:3000/users{"message": "User list"}
  • http://localhost:3000/products{"message": "Product list"}

上記のように、/users、/products のルーティングを userRoutes、productRoutes オブジェクトに委譲することができます。
ルーティングをグループ化して別ファイルに分割できるので、機能単位でルートを管理できます。

ほかにも、正規表現やパスパラメータなどを柔軟にエンドポイントを管理できるようになっています。

まとめ

今回、Hono.js というフレームワークを紹介しました。
リクエストの処理に関しては、確かに高い性能であることが分かりました。
また、ローカル環境構築もすることが少なくて試しやすいと思いました。

それと、Hono.js と比較すると Laravel は遅いということにはなりますが、データベースや認証、バリデーションなどの機能はより充実しています。

どちらが優れている(劣っている)ということではなく、Hono.js は軽量でAPI 向き、Laravel は機能が豊富でフルスタック向きというように、フレームワークの特徴を知り、長所を活かして使い分けるのがよさそうです。