🔥

Hono[炎]っていうイケてる名前のフレームワークを作っている

2022/01/27に公開約4,500字2件のコメント

Cloudflare Workersは「CDNのエッジで動く」という特徴だけでなく「サーバーレス」の環境としても非常に優秀です。プロジェクトの作成からデプロイまで「4ステップ」で出来ます。

$ yarn init -y
$ wrangler init
$ touch index.js // Write code
$ wrangler publish

自動的に {project-name}.{user-name}.workers.dev といったURLを発行してくれて、すぐさま公開されます。この手軽さとスピード感はヤバい。スクリプトのサイズが1MB以内、使えるAPIが極端に限られているなど制約がありますが、それはそれで単一機能のシンプルなアプリケーションを作る気にさせてくれます。

さて、このCloudflare WokersでWebアプリを作っていたのですが、だんだんとCloudflare Workersに特化した「Webアプリを作るためのフレームワーク」を作りたくなってきました。手段の目的化です。ということで作り始めました。「Hono[炎]」というイケてる名前のフレームワークです。

https://github.com/yusukebe/hono

Cloudflare WokersはService Workersを踏襲したAPIでアプリを作ります。それが非常に限られています。メインで使えそうなものは

  • Fetch
  • FetchEvent
  • Request
  • Response
  • URL

くらいしかありません。もちろん外部モジュールを使うことが出来ますが、node.js依存のモジュールは動きません。というかモジュール、だいぶ動きません。そこで、上記のみのAPIを使って、依存ゼロの軽いフレームワークがあったらいいなと作りました。作ってから気づいたのですが、フレームワークというかルーターというかCloudflare Workersに特化したものが他にも

  • itty router
  • Sunder
  • Worktop

と出来のいいのが見つかってアイデンティティを探す旅をしています。

Hono[炎]

Hono[炎] と名付けました。Cloud「flare」とかかりつつ、外人うけしたら面白いなと思います。このHono、実際のところ、

  • ルーター
  • Request/Responseを扱うためのContext
  • ミドルウェア

というちょっとした構成になっています。

さきほど、似たようなCloudflare Workers向けのルーター・フレームワークがあると申しましたが、Honoの場合、ルーターをtrie木で実装した関係上非常に速いです。ゆえに「Ultrafast web framework」を謳っています。アイデンティティisそれです。ちなみにルーターのみのベンチ結果はこちら。

hono x 708,671 ops/sec ±2.58% (58 runs sampled)
itty-router x 159,610 ops/sec ±2.86% (87 runs sampled)
sunder x 322,846 ops/sec ±2.24% (86 runs sampled)
worktop x 223,625 ops/sec ±2.01% (95 runs sampled)
Fastest is hono
✨  Done in 57.83s.

速い!!ウルトラファスト!!

使うのは簡単です。まぁこのGif動画を見てもらうとイメージが湧くと思います。

コード例はこれです。


import { Hono, Middleware } from 'hono'

const app = new Hono()

app.use('*', Middleware.logger())

app.get('/', (c) => c.text('Hello Hono!'))
app.get('/entry/:id', (c) => {
  return c.json({ 'your id' : c.req.param('id') })
})

app.fire()

最後、ディスパッチするのに app.fire() とするのがオシャレですね!

使い方はREADMEを読んでもらえればお分かりですが、せっかくなので紹介します。

Routing

「これだけできればいいだろ」っていう柔軟なルーティングが出来ます。

app.get('/', (c) => c.text('GET /'))
app.post('/', (c) => c.text('POST /'))

といったベーシックなものから、

// Wildcard
app.get('/wild/*/card', (c) => {
  return c.text('GET /wild/*/card')
})

// Named param
app.get('/user/:name', (c) => {
  const name = c.req.param('name')
  ...
})

// Regexp
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
  const date = c.req.param('date')
  const title = c.req.param('title')

とまぁ、正規表現のキャプチャも出来ます。面白いのは、一番最後、フォールバックした場合に404を書いておけば、オリジナルの404を書けます。

app.all('*', (c) => {
  return c.text('Custom 404 Error', 404)
})

Context

Contextはとにかく便利にしてて、APIがやっと定まってきました。c.req.xxx がRequestオブジェクトをいじるためのショートカット、c.xxx がResponseへのショートカットになっています。

Requestが、

// Shortcut to get a header value
app.get('/shortcut', (c) => {
  const userAgent = c.req.header('User-Agent')
  ...
})

// Query params
app.get('/search', (c) => {
  const query = c.req.query('q')
  ...
})

// Captured params
app.get('/entry/:id', (c) => {
  const id = c.req.param('id')
  ...
})

Responseが

app.get('/welcome', (c) => {
  c.header('X-Message', 'Hello!')
  c.header('Content-Type', 'text/plain')
  c.status(201)
  c.statusText('201 Content Created')

  return c.body('Thank you for comming')
})

こうです。

Middleware

app.use を使えば、自分でいくらでもMiddlewareを作れます。例えば、カスタムヘッダを送信するMiddlewareは

// Add a custom header
app.use('/message/*', async (c, next) => {
  await next()
  c.res.headers.add('x-message', 'This is middleware!')
})

と書けます。それに加えて、BuiltinのMiddlewareを充実させようと思っています。今のところ、

  • powered-by
  • basic-auth
  • logger
  • cookie
  • body-parse
  • cors

があります。これからも要件によって追加していこう考え中。使い方は簡単、

app.use(
  '/auth/*',
  Middleware.basicAuth({
    username: 'hono',
    password: 'acoolproject',
  })
)

とするだけでBasic認証かけられます。

どんなことができるか

できることはたくさんあると思いますが、examplesにどんどん足していきたいです。今のところこんな感じです。

  • basic
  • blog
  • compute-at-edge
  • durable-objects
  • jsx-ssr
  • serve-static

SS

Fastly Compute@Edge

これ、Service WorkersのAPIを使っている同士としてFastly Compute@Edgeでも動きます。Fastlyは僕も使っていて、設定をVCLで書いてるのですが、CDNのルーティングの設定をHonoで書くってのも面白そうです。


ということで Hono[炎] いいでしょ!!

使ってください!

ということで名前がイケてるフレームワーク Hono[炎] を紹介しました。Cloudflare Workersを使ったことがある方もない方も使ってみてください!!むしろ使ったことがない人はその魅力に気づいて楽しいことでしょう!

これ作るのめちゃくちゃ楽しくて、1ヶ月前からほぼ毎日少しずつ書いてます。一緒に作ってくれる人もいて嬉しいのですが、もっといたらもっと嬉しいです!よろしく!

https://github.com/yusukebe/hono

Discussion

最初のサンプルc.rep.param('id') → c.req.param('id')ですね。ちょうどリクエストハンドリング見て「うぉ」っと思ったところだったので、ズキューンと来ました。

「うぉ」。ですね!ありがとうございます!

ログインするとコメントできます