32
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NRI OpenStandiaAdvent Calendar 2024

Day 9

【時代はHono🔥!?】今さらながらNext.js App RouterユーザがHonoを調べてみた

Last updated at Posted at 2024-12-08

目次

  1. はじめに
  2. 想定読者
  3. TL;DR
  4. Honoとは
  5. Trends
  6. Honoを使ってみた
  7. Next.jsとの比較
  8. ユースケース
  9. 所感
  10. まとめ

はじめに

こんにちは、「拳で」 と申します!
NRI OpenStandia Advent Calendar 2024の9日目は

【時代はHono🔥!?】今さらながらNext.js App RouterユーザがHonoを調べてみた

というタイトルでお送りいたします!

最近Honoについての記事や情報をよく見かけるようになり、全く調べたり触ったりしたことがなかったのでこの機会に調べてみました。

Next.js App Routerを使って、3案件ほどこなしたことがあるのでNext.jsユーザ目線で調べて思ったことなども書きました。

想定読者

この記事は以下のような方を想定して書いています。

  • Honoのこと、全然知らないけど概要を知りたい
  • Honoの2024年時点での立ち位置を知りたい
  • Honoを使っていて、複数エンドポイント(Grouping routes)・OpenAPI Docs対応・RPCモード対応したサンプルコードが見たい

TL;DR

  • Honoは2024年に特に伸びてきている
  • Honoは軽量・高速で開発体験が優れたフレームワークで、特にエッジやバックエンド開発に最適
    • Expressの代替になる
  • Honoの開発者体験は良い

Honoを使ってみたで紹介したコードは、以下リポジトリで公開しています💡

  • Next.jsと比較し、両者フルスタックフレームワークだがHonoはバックエンド(API)開発や軽量・ポータビリティに強み
    • 比較表

hono-nextjs.jpeg

  • Hono・Next.jsで両者補完する構成も良い
  • Expressの代替やエッジでのプロキシ用途、Next.jsとの併用などがユースケースとして向いてそう

Honoとは

Honoの概要

Readme Card

HonoはJavaScriptのWebフレームワークです。

Hono sample code for Node.js
import { serve } from '@hono/node-server'
import { Hono } from 'hono'

const app = new Hono()
app.get('/', (c) => c.text('Hello Node.js!'))

serve(app)

上記サンプルのようにExpressに似た書き味で、WebAPIの開発ができます。
v3.1.0以降はJSXもサポートして、フルスタックフレームワークになったようです!

比較のため、Expessのサンプルコードも載せておきます。

express sample code
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

Cloudflare内はもちろん、CyberAgent, Prismaなど様々な組織・団体で実利用されているようです。
以下は、Honoを利用していることを公表している組織・団体の情報です。

コラム:Honoの成り立ち

  • Yusuke Wadaさんが開発される、日本発のフレームワークです
  • 開発当初は、Cloudflare Workers(エッジ環境)に特化した「Webアプリを作るためのフレームワーク」をコンセプトに開発されています
  • Hono[炎]は、Cloudflareのflare(炎)とかけたネーミングが由来
  • 開発初期のYusuke Wadaさんの記事:Hono[炎]っていうイケてる名前のフレームワークを作っている

Honoの特徴

Honoは以下のような特徴があります。

  • 爆速:JavaScriptのWebフレームワーク内で最速
  • 軽量:Minify すれば 14KB 以下 (Express は 572KB) 1
  • ルーター:5種類のルーターを持ち、目的に応じて使い分けできます。特にRegExpRouterJavaScript界隈で最速のルーターです
    • Hono v4で登場した、HonoとViteを組み合わせたメタフレームワーク HonoXではFile-based Routingも提供されます
  • Web標準:HonoはWeb 標準のみを使用して実装されています。 Web 標準 API をサポートするあらゆるランタイムで動作
    • Cloudflare Workers, Fastly Compute, Deno, Bun, Vercel, Netlify, AWS Lambda , Lambda@Edge, Node.js などで動作
  • ミドルウェア & ヘルパー:シンプル・軽量なコアに、豊富なミドルウェアヘルパー適宜組み込むことで機能追加可能
  • 開発体験:TypeScriptによる型のサポートや、RPCモードによる型安全なAPIクライアントの生成が簡単にできます

開発体験を意識した機能が豊富なので好印象...!

Trends

Express is the new JQuery

という界隈で一時話題になったポストや

I Stopped Using Express.js: Because Bun and Hono 🔥

と題して、Expressではなく、Honoを使いだしたという方も登場してきており

Honoの台頭とともに、脱Expressする人も増えてきている??気がしてます。

「拳で」の体感では 2024年になってよくHonoの記事や事例を見聞きするようになったのですが、トレンドはどうなってるか調べてみました。

今回各ツールのトレンドを比較するため

の調査を行いました。

比較対象は以下です。

プロダクト名 Stars
Express GitHub stars
Fastify GitHub stars
Elysia GitHub stars
Next.js GitHub stars

GitHub Star History

GitHub Star Historyで各GitHubリポジトリのStar数推移をチェックしてみました。

star-history-2024123.png

引用:GitHub Star History | Hono vs express vs fastify vs elysia vs Next.js

2022年に登場したHonoはかなりの勢いでStar数を増やしてますね!
特に2024年の加速は目覚ましいものがあり、よくHonoの記事や事例を目にするのも納得がいきました。

npm trends

node modulesとして配布されている、モノレポ管理ツールのダウンロード数をnpm trendsで比較しました。

image.png

expressやNext.jsを比較対象に加えると他のプロダクトのグラフが潰れてしまったので除外しています。

やはり、2024年から特にダウンロード数が増えてますね :eyes:

引用:npm trends | elysia-vs-fastify-vs-hono

State of JavaScript

State of JavaScript 2023調査結果から回答者が普段使っているBack-end Frameworksのランキングでは全体12位という結果でした。

image.png

回答者の1%が使っているという状況でしたが

  • 回答の選択肢にHonoがなかった
  • 「その他」の自由入力欄にHonoを記入した人が一定数いた

というのも少し影響している気がします。

State of JavaScript 2024のSurveyでは回答選択肢にHonoが追加されており、今年の躍進を見る限りランキングの大幅アップも期待できそうですね!

State of JavaScript 2024

JavaScript OpenSource Award

JavaScript OpenSource Award 2024のThe Most Exciting Use of Technology部門にノミネートされていました!

image.png

惜しくも受賞とはならなかったようですが、Honoは2024年のJavaScript界隈で注目を集めたプロダクトの1つですね!

Honoを使ってみた

前置きが長くなりましたが、早速Honoを使ってみましょう!

コードは以下リポジトリで公開しています💡

複数のエンドポイントを定義した、Grouping routes構成の上で

  • OpenAPI Docs対応
  • RPCモード対応

しているサンプルになるので、よかったらぜひご覧ください :eyes:
※Grouping routesで上記2つに対応しているサンプルコードをあまり見つけきらなかったので、お役に立てれば幸いです..

Geting Started

を参考にプロジェクトを作成します。

npm create hono@latest my-app

このコマンドを打つと、インタラクティブなCLIでプロジェクトが作成できます。
便利!

image.png

どのランタイムで動作させるか、テンプレートを選択できます。

server.ts
import { serve } from '@hono/node-server'
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => {
  return c.text('Hello Qiita AdventCalendar 2024!')
})
const port = 3000
console.log(`Server is running on http://localhost:${port}`)
serve({
  fetch: app.fetch,
  port
})

テンプレートから生成されたコードをそのまま実行して、用意されたエンドポイントにcurlしてみます。

curl -v localhost:3000
* Host localhost:3000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:3000...
* Connected to localhost (::1) port 3000
> GET / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.7.1
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 200 OK
< content-type: text/plain; charset=UTF-8
< Content-Length: 31
< Date: Wed, 04 Dec 2024 14:53:25 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< 
* Connection #0 to host localhost left intact
Hello Qiita AdventCalendar2024!

returnに設定されたテキストが返ってきますね!
記述はExpressライクな印象

RPCモード

次に個人的に、非常に注目しているPRCモードを試してみます。

RPCモードとは

RPCモードとは以下のような機能です。

  • Web APIの仕様、特にインプット・アウトプットをサーバーとクライアント間で共有するためのもの
  • OpenAPIやgRPCを使ってやりたかったことを叶えるかもしれない
  • サーバーとクライアントをどちらもTypeScriptで書くことが大前提である
  • 同種のものにtRPCがあるが、Honoの場合、普通のREST APIを書くだけで使える
  • クライアントはfetchのラッパーであり、スタンダードなResponseオブジェクトを扱う
  • いわゆる「型安全」を提供するものであり、エディタの補完がバチバチに効く

引用:見よ、これがHonoのRPCだ

ざっくりいうと

今までOpenAPI Specからopenapi-typescriptとかを使って作っていた、型安全なAPI ClientをHonoでAPI書くだけで簡単に実現できる機能です。

これがめっちゃ便利...!

server

ひとまず、普通にAPIを作成します。

メソッドチェーンで定義 すると、RPCモードが使えるようになります。
ついでに、zodによるpost valueのバリデーションチェックも加えます。

server.ts
import { serve } from '@hono/node-server'
import { zValidator } from '@hono/zod-validator'
import { Hono } from 'hono'
import { z } from 'zod'

const app = new Hono()

const schema = z.object({
	name: z.string().min(1),
	country: z.string().min(1),
})

const routes = app
	.get('/', (c) => {
		console.table(c)
		return c.text('Hello Qiita AdventCalendar 2024!')
	})
	.get('/api/users', (c) => {
		return c.json({
			user: 'foo-users',
		})
	})
	.post('/api/users', zValidator('json', schema), (c) => {
		const { name, country } = c.req.valid('json')
		return c.json({
			message: `Hello, ${name}. Your country is ${country}.`,
		})
	})

const port = 3000
console.log(`Server is running on http://localhost:${port}`)

serve({
	fetch: app.fetch,
	port,
})

// routesの型を取り、exportしておく
export type AppType = typeof routes
export default app

curlでデータをpostしてみます。

❯ curl -X POST localhost:3000/api/users -H 'Content-Type: application/json' -d '{"name":"jong","country":"japan"}'

{"message":"Hello, jong. Your country is japan."}

postしたデータが正しく受信できてそうです!
次にあえてバリデーションチェックエラーを起こしてみます。

❯ curl -v -X POST localhost:3000/api/users -H 'Content-Type: application/json' -d '{"name":"jong"}'
Note: Unnecessary use of -X or --request, POST is already inferred.
* Host localhost:3000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:3000...
* Connected to localhost (::1) port 3000
> POST /api/users HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.7.1
> Accept: */*
> Content-Type: application/json
> Content-Length: 15
>
* upload completely sent off: 15 bytes
< HTTP/1.1 400 Bad Request
< content-type: application/json; charset=UTF-8
< Content-Length: 163
< Date: Wed, 04 Dec 2024 15:23:34 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
<
* Connection #0 to host localhost left intact
{"success":false,"error":{"issues":[{"code":"invalid_type","expected":"string","received":"undefined","path":["country"],"message":"Required"}],"name":"ZodError"}}

HTTPステータス 400のレスポンス定義はしてませんが、よしなにステータスコードやエラーメッセージをレスポンスしてくれて便利😍

zodのschemaでmin(1)していすれば、requireに指定できますね。

client

server側で生成したAPIの型をhcに与えることで、型安全なAPI Clientを生成できます。

image.png

しっかり、エディタの型補完の恩恵を受けることができます!!
これは簡単・便利ですね!

client.mts
import { hc } from 'hono/client'
import type { AppType } from './server'

// TypeSafeなAPI Clientを生成
const client = hc<AppType>('http://localhost:3000/')

const res = await client.api.users.$post({
	json: {
		name: 'Qiita',
		country: 'japan',
	},
})

if (res.ok) {
	const data = await res.json()
	console.log(data)
}
実行結果
❯ npx tsx src/client.mts
{ message: 'Hello, Qiita. Your country is japan.' }

問題なく、使えます!
こんなサクサクとTypeSafeなAPI Clientが作れるのは素敵...

参考

Custom fetch method

hcで生成されるAPI Clientはfetchなので、必要に応じてAPI Clientをカスタマイズできます。
先程のコードにカスタムヘッダを追加してみます。

client.mts
import { hc } from 'hono/client'
import type { AppType } from './server'

// TypeSafeなAPI Clientを生成
const client = hc<AppType>('http://localhost:3000/', {
	fetch: (input: RequestInfo | URL, requestInit?: RequestInit) => {
		const headers = new Headers(requestInit?.headers) // 既存のヘッダを保持
		headers.set('x-qiita-custom-header', 'qiita adventcalendar 2024') // カスタムヘッダを追加

		return fetch(input, {
			...requestInit,
			headers, // 更新されたヘッダをセット
		})
	},
})

const res = await client.api.users.$post({
	json: {
		name: 'Qiita',
		country: 'japan',
	},
})

if (res.ok) {
	const data = await res.json()
	console.log(data)
}

サーバ側でカスタムヘッダを受信できてます!

> dev
> tsx watch src/server.ts

Server is running on http://localhost:3000
{
  accept: '*/*',
  'accept-encoding': 'gzip, deflate',
  'accept-language': '*',
  connection: 'keep-alive',
  'content-length': '34',
  'content-type': 'application/json',
  host: 'localhost:3000',
  'sec-fetch-mode': 'cors',
  'user-agent': 'node',
  'x-qiita-custom-header': 'qiita adventcalendar 2024'  //custom header
}

RPCモードの利用について
tRPC vs ts-rest vs Hono RPC
の記事にもあるとおり、モノレポや同一プロジェクトでない場合RPCモードを使うのは工夫が必要です。

※Honoを使ってAPIを構築するプロジェクトが別リポジトリの場合、package化してroutesの型を別リポジトリにimportしたりが必要

OpenAPI

HonoはOpenAPI形式でスキーマを定義することで、APIドキュメントの生成も簡単にできます。

こちらの記事が大変参考になりました :bow:

以下のようにOpenAPI形式でrouteを定義すると

route.ts
import {
  ErrorSchema,
  UserGetResSchema,
  UserPostResSchema,
  UserReqSchema,
} from '@/schema/web/userSchema'
import { createRoute } from '@hono/zod-openapi'

export const userGetRoute = createRoute({
  method: 'get',
  path: '/users',
  responses: {
    200: {
      content: {
        'application/json': {
          schema: UserGetResSchema,
        },
      },
      description: 'Returns a sample user name.',
    },
  },
})

export const userPostRoute = createRoute({
  method: 'post',
  path: '/users',
  request: {
    body: {
      content: {
        'application/json': {
          schema: UserReqSchema,
        },
      },
    },
  },
  responses: {
    200: {
      content: {
        'application/json': {
          schema: UserPostResSchema,
        },
      },
      description: 'Returns a posted user name, country.',
    },
    400: {
      content: {
        'application/json': {
          schema: ErrorSchema,
        },
      },
      description: 'Bad Request',
    },
  },
})
server.ts
import { webRouter } from '@/routes/web'
import { serve } from '@hono/node-server'
import { swaggerUI } from '@hono/swagger-ui'
import { OpenAPIHono } from '@hono/zod-openapi'
import { logger } from 'hono/logger'
import { prettyJSON } from 'hono/pretty-json'

const baseApp = new OpenAPIHono().basePath('/api')

baseApp.use('*', prettyJSON())
baseApp.use('*', logger())

// OpenAPIドキュメントの生成
baseApp
  .doc31('/doc', {
    openapi: '3.1.0',
    info: {
      title: 'API',
      version: '1.0.0',
    },
  })
  .get(
    '/ui',
    swaggerUI({
      url: '/api/doc',
    }),
  )

const app = baseApp.route('/web', webRouter)

const port = 3000
console.log(`Server is running on http://localhost:${port}`)

serve({
  fetch: app.fetch,
  port,
})

export type AppType = typeof app

簡単にSwagger UIのAPIドキュメントを生成できます!!
便利すぎる...

image.png

もちろん、RPCモードも使えます。

Next.jsとの比較

image.png

HonoとNext.jsはどういった関係性なのか比較してみました。

成り立ち

Yusuke Wadaさんが以下のようにポストしてます。

image.png
引用:Hono v4より

個人的に以下のような印象を受けます。

  • Hono:Expressの代替や、エッジ環境でのAPI開発目的が出発点
    • バックエンド側の機能が充実。エッジ環境やマルチランタイム対応に強み。シンプルで軽量なフレームワーク
  • Next.js:Reactのメタフレームワークとして、画面開発目的が出発点
    • フロントエンド側の機能が充実。バックエンドの機能も含め、様々な機能を有する重厚なフレームワーク

HonoとNext.jsの機能比較

ざっくりですが、HonoとNext.jsの機能や特徴を比較してみました。
※誤りがあったら申し訳ございません :bow:

hono-nextjs.jpeg

こう並べてみると、Honoもフルスタックフレームワークに位置付けられますね。

  • Honoは柔軟なミドルウェアを駆使したAPI開発に強み
  • Next.jsは画面系の開発に強み

といった印象を受けます。
双方のフレームワークの出自のとおりですね。

HonoとNext.jsの関係性

今のところ、両者がカチ合って直接の競合になるイメージは湧きません。

  • API開発やエッジでの処理が主体のアプリケーション:Hono
  • フロント系の開発が主体のアプリケーション:Next.js

という棲み分けです。

両者は補完関係にもあるので、美味しいとこ取りする構成は魅力的です。

参考

ユースケース

「拳で」がHonoについて調べながら、このユースケースが向いてるなと個人的に感じたものを並べます。

1. Expressの代替

まず、ぱっと思い浮かんだのはこちらです。
既存のExpressプロジェクトのリプレースは大変だと思うので、新規案件でAPIをTypeScriptで書く場合は選択肢に入れても良さそう。

2. Lambdalith

AWS Lambda・Lambda@Edgeを使うケースでLambdalith構成を選択する場合、マルチランタイム対応しているHonoは選択肢に入ると思います。

AWS管理ランタイムを気にしなくて良くなる恩恵はあるかなと。

参考

3. エッジでのプロキシ

Yusuke Wadaさんの資料で紹介されていますが、エッジ上で

  • レスポンスヘッダーのハンドリング
  • CORS対応
  • キャッシュ

など様々な用途で、Honoは活躍すると思います。

詳細は、資料参照

4. Next.jsのRoute Handler内での利用

Next.jsでRoute Handlerを使う構成を考えている場合、Route HandlerでHonoを採用する のはかなりアリな構成だなと思いました。

RPCモードで型安全なAPI Clientを生成し、Next.jsの画面側のコードでそれを使う とアプリケーションの品質を高めることができそうです。

Honoを採用しておくと
Route Handler内でCPU boundな処理によって、プロセスがハングしてNext.jsの画面応答もしなくなる ような性能問題に直面したとき、Route Handler(= API)部分だけ後々分離したりできます。
※そもそもNodeで、CPU boundな処理するなって話はありますが...😅

Node.jsでCPU boundな処理をすると...の件は、PLAIDがNode.jsを採用し、5年間で12万行書いてわかったことが参考になります。

参考

5. BFF

image.png
引用:流行りのBFFアーキテクチャとは?|Offers Tech Blogより

BFFは一般的に広く公開するのではなく、特定のアプリケーションのために構築するので個人的にOpenAPI specをわざわざ書くのはだるいなーと思ってました。

HonoならRPCモードで活用ができて、簡単にOpenAPIドキュメントが作れるので心理的ハードルは低いと感じました。

6. エッジでAPI + 簡単な画面を返したいとき

JSX対応やHonoXでファイルベースルーティングにも対応し、フルスタックWebアプリケーションフレームワークとしての活用もできそうです。

エッジ側でも簡単な処理や画面のレスポンスなら行けるので、軽量フレームワークとして...

所感

Honoを調査してみて、以下を感じました。

  • シンプル・軽量で開発者体験も非常に良い
    • 特に、OpenAPI・RPCモードは非常に良い
  • マルチランタイム対応しており、ポータビリティ性が高いのはありがたい
    • Next.jsのRoute Handlerに組み込んだりとか使える範囲が広い
  • 公式ドキュメントがよりリッチになると嬉しい
    • middlewareのオプションなどは網羅的に書かれてるわけではない
    • middlewareのリポジトリ側では、情報が提供されているので大きな問題ではないが
  • RPCモードは便利だが、モノレポ・同一プロジェクトでないと使いづらいので大規模プロジェクトでどう活用するかは工夫が必要??

実案件で使ってみたいと思いました。
ひとまず、趣味プロジェクトやハッカソンとかで使ってみるかな..🤩

まとめ

Honoについて色々と調査した内容をまとめさせていただきました。
シンプルながら奥深く、様々なユースケースが挙げられるのでこれから更に注目していきたいと思います :eyes:

参考資料

  1. https://hono.dev/docs/#lightweight より

32
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?