Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
ROUTERCENTRISM: ルーティングに還元されるすべてについて
Search
Taku Amano
October 11, 2026
Technology
11
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
ROUTERCENTRISM: ルーティングに還元されるすべてについて
Taku Amano
October 11, 2026
More Decks by Taku Amano
See All by Taku Amano
The Journey of the Node.js Adapter through Performance and Portability
usualoma
0
210
TypeScript100%で作るMovable Typeプラグイン
usualoma
3
760
We can develop a framework
usualoma
1
430
Honoの3+1のルーターとそこにつながるPRがプロジェクトにもたらしたもの
usualoma
3
4k
JSのウェブフレームワークで高速なルーターを実装する方法
usualoma
4
3.7k
Other Decks in Technology
See All in Technology
子育てエンジニアの可処分時間が減っても成長を諦めない戦い方
sudoakiy
4
3.5k
[2026-09-30]ロックンロールは鳴り止まないっ - 信頼性かまってちゃん - 「データ駆動を投げ捨ててまで。」追いかける信頼性改善に向けた取り組みの話
tosite
0
210
AI時代だからこそ推したい!Gitコマンド再入門
munakata
0
150
ai_cording_with_k8s_knowledge.pdf
mochizuki875
1
310
プロダクト価値を、 チームが使える判断軸に変える
vivion
0
130
Snapshot Testing in Practice: Predictable and Reliable SwiftUI Views
fespinoza
0
140
AI 時代の Azure エンジニアリング ~ 私たちは何を磨き、何を任せるのか ~
chack411
1
530
事業活動を AI Ready にする攻めと守りのデータエンジニアリング / data-engineering-for-ai-ready-business
pei0804
5
1.9k
しろおびから始める!ログ活用の第一歩
shumei_ito
0
530
ファミコンでPHPを動かす / PHP on the Famicom side b
tomzoh
0
140
ミイダス株式会社 テックチームのご紹介 / MIIDAS Tech Team
miidas
0
160
今こそ知りたいAmplifyGen2
mkdev10
2
160
Featured
See All Featured
Building a A Zero-Code AI SEO Workflow
portentint
PRO
0
760
Done Done
chrislema
187
17k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
2.2k
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Automating Front-end Workflow
addyosmani
1369
210k
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
260
KATA
mclloyd
PRO
35
16k
Efficient Content Optimization with Google Search Console & Apps Script
katarinadahlin
PRO
1
910
ラッコキーワード サービス紹介資料
rakko
1
5.2M
Skip the Path - Find Your Career Trail
mkilby
1
240
Building Experiences: Design Systems, User Experience, and Full Site Editing
marktimemedia
1
620
Technical Leadership for Architectural Decision Making
baasie
3
590
Transcript
ルーティングに還元されるすべてについて Hono Conference 2026 © 2026 Taku Amano
2
3
Thank you! 4
偽陽性の 同じ AI モデルから同じ Advisory が無限に湧く 5
open な issue には AI slop の PR が無限に湧く 6
Consider sponsoring Hono (and me) 7
今日のテーマ 8
それPla ? 「それはルーターに還元できる問題では?」 新機能の実装にあたって、ルーター中心の視点から見るというアプローチをとってみる 9
以下の 2 つの PR についての話 https://github.com/honojs/hono/pull/5201 https://github.com/honojs/hono/pull/5541 Merged ルーターの実装に関する四方山話 これは
個人の見解 で、プロジェクトの総意ではありません 10
Hono のルーター達
8888 8888 Eight Routers 88 88 88 令和8年8月8日 12
Eight Routers 1. TrieRouter 基準 2. RegExpRouter 速い 3. StaticRouter
吸収された 4. PatternRouter 小さい 13 5. LinearRouter 1shot が速い 6. SmartRouter 組み合わせ可 7. PreparedRegExpRouter hono/tiny 8. TransformRouter New! hono/quick 準備したら速い
ルーターの interface export interface Router<T> { name: string add(method: string,
path: string, handler: T): void match(method: string, path: string): Result<T> } 14
ルーターの interface : add() export interface Router<T> { name: string
add(method: string, path: string, handler: T): void match(method: string, path: string): Result<T> } add('ALL', '*', logger) 登録 add('GET', '/users/:id', handler) 15 Router
ルーターの interface : match() export interface Router<T> { name: string
add(method: string, path: string, handler: T): void match(method: string, path: string): Result<T> } GET /users/1 16 match('GET', '/users/1') Router 束 logger → handler
ルーターの役割 match() に method と path を与えた時に、handler の束( Handler[] )を返すのが
ルーターの役割 path の一致で柔軟にルーティングできる 17
意味でルーティングする JevRouter 18
パスの代わりに、handler にマッチさせる「条件」を登録する import { JevRouter } from 'hono-jev-router' const app
= new Hono({ router: new JevRouter({ apiKey }) }) const md = { 'Content-Type': 'text/markdown' } app.on('jev', 'a request from an AI agent', (c) => c.text('# Documentation', 200, md)) app.on('jev', 'a request from a human browser', (c) => c.html('<h1>Documentation</h1>')) app.on('jev', 'suspicious automated traffic', (c) => c.text('Forbidden', 403)) README の例。Jev へ「リクエストの情報」と「条件」を使って問い合わせて、handler を選択する 19
ルーターは判定器 ルーターは、ある種の判定器 ふつうは決定的。ただし Hono は、決定的であることを必ずしも求めない add() で指定するパスは criteria match() は、リクエストを
criteria で判定して、適切な handler の束を返す JevRouter は、criteria が文章で、判定が確率的という形になっている 20
TransformRouter honojs/hono#5201
middleware と handler の単位でトレースしたい honojs/hono#4842 。 TracingChannel をサポートしたいという要望 リクエスト全体ではなく、middleware や
handler ごとの単位でトレースしたい 22
それはルーターに還元できる問題では? 23
method と path を与えた時に handler の束を返すのが ルーターの役割 24
method と path を与えた時に トレース可能な状態にラップされた handler の束を返すのが TransformRouter の役割 25
使い方 import { RegExpRouter } from 'hono/router/reg-exp-router' import { TransformRouter
} from 'hono/router/transform-router' const app = new Hono({ router: new TransformRouter({ delegateRouter: new RegExpRouter(), transform: ({ handler, method, path }) => async (c, next) => { console.time(`${method} ${path}`) try { return await handler(c, next) } finally { console.timeEnd(`${method} ${path}`) } }, }), }) 26
実装 class TransformRouter implements Router<[H, RouterRoute]> { add(method: string, path:
string, entry: [H, RouterRoute]) { const [handler, route] = entry this.#delegateRouter.add(method, path, [ this.#transform({ ...route, handler }), route, ]) } match(method: string, path: string) { return this.#delegateRouter.match(method, path) } } 27
add の中で transform する app.get() add TransformRouter transform(route) 28 RegExpRouter
match wrapped handler
例1: diagnostics_channel でトレースする import { tracingChannel } from 'node:diagnostics_channel' const
tc = tracingChannel('hono:request') tc.subscribe({ start: (m) => console.log('start', m.route.path), asyncEnd: (m) => console.log('end', m.route.path), }) const router = new TransformRouter({ delegateRouter: new RegExpRouter(), transform: (route) => (c, next) => tc.tracePromise(() => route.handler(c, next), { route, c }), }) 29
例2: どの handler が投げたかを onError の前に知る const router = new
TransformRouter({ delegateRouter: new RegExpRouter(), transform: ({ handler, method, path }) => async (c, next) => { try { return await handler(c, next) } catch (err) { console.error(`${method} ${path} threw`, err) throw err } }, }) app.onError((err, c) => c.text(err.message, 500)) 30
本体の実装に影響を与えることなく実現できる new Hono() の router オプションに、TransformRouter を渡すだけ TransformRouter を使わない既存のアプリには影響がない 変換は登録時に
1 回だけ行われる リクエスト時のオーバーヘッドがない app.routes には元の handler が保持される transform された結果は TransformRouter 内で使用されるだけ showRoutes() の結果にも影響がない 31
四方山話 RegExpRouter は事前計算をしている middleware の適用の事前計算
登録時に、個別のルートに必要な束は決められる const app = new Hono() app.all('*', logger) app.all('/api/*', cors)
app.get('/api/users/:id', handler) app.all('/api/*', fallback) * logger /api/* logger → cors /api/users/:id logger → cors → handler → fallback 33
ルーター毎の違い 起動 リクエストが来る TrieRouter・他 登録 RegExpRouter 登録 PreparedRegExpRouter 畳む 登録
取り出す 畳む 束を作る 実行 束を作る 束を 取り出す 実行 束を作る 束を 取り出す 実行 束 = middleware と handler の配列。Prepared- は「畳む」だけを起動前にツールで準備しておく。 34
四方山話 - もう少し 事前計算にまつわる苦労話
RegExpRouter の事前計算で扱えないパスもある 正規表現に対して「束」が曖昧になるパスは扱えない 原理的には、展開すれば扱えるはずだが、パターンが増えすぎて解析のコストも掛かる そのときは UnsupportedPathError を投げる (SmartRouter 経由で、TrieRouter に切り替わる)
36
UnsupportedPathError になる代表的なケース 登録済み 追加すると投げる 理由 /reg-exp/router /reg-exp/:id 同じ階層に、文字列とキャプチャ r o
p p u Uns 37 r o r r E h t a P d e t
v5 で修正されるパスの途中の * の扱い honojs/hono#5243 38
修正前は、途中の * の扱いがルーターごとに違った ルーター /x*/y ← /xz/y /x*/y ← /x/y
/a/b*/c ← /a/bz/c RegExp match — match Trie — — — Linear match match match Pattern — — — 39
8888 8888 修正後 ルーター /x*/y ← /xz/y /x*/y ← /x/y
/a/b*/c ← /a/bz/c RegExp match match match Trie match match match Linear match match match Pattern match match match 40 88 88 88
事前計算のルールも見直す必要が出た パス use('/x/*') get('/x*/y') /x/y match match /xz/y — match
/x/z/y match — r o p p u Uns 41 r o r r E h t a P d e t
notFound と onError honojs/hono#5541
notFound/onError にも middleware を… 404 ページやエラーページをレンダリングする際に、middleware を適用したい 例: languageDetector const
app = new Hono() app.notFound( languageDetector(), (c) => { return c.html(render404Page(c.get('language'))) } ) 43
それはルーターに還元できる問題では? 44
v4 ではルーティングの対象ではない 設定できるのはアプリに対して 1 つで、パスによる絞り込みはない middleware を利用できない onError() は subApp
に対応している subApp の handler で発生したエラーは、subApp の onError() で処理できる notFound() は subApp には未対応 const app = new Hono() app.notFound((c) => c.text('Custom 404 Message', 404)) app.onError((err, c) => c.text('Custom Error Message', 500)) 45
v5 ではルーティングの対象になる アプリに対して複数設定でき、パスによる絞り込みもできる middleware を利用できる app.notFound( languageDetector(), (c) => c.html(render404Page(c.get('language')))
) app.onError( languageDetector(), (c) => c.html(renderErrorPage(c.get('language'), c.error), 500) ) 46
予約メソッドにルートを登録する実装 notFound() は @NOT_FOUND 、 onError() は @ERROR @ 始まりは予約扱いにして、
inspectRoutes / showRoutes などでは除外 const METHOD_NAME_NOT_FOUND = '@NOT_FOUND' const METHOD_NAME_ERROR = '@ERROR' this.onError = (...handlers: (string | H)[]) => this.#addRoutes(METHOD_NAME_ERROR, handlers) as any this.notFound = (...handlers: (string | H)[]) => this.#addRoutes(METHOD_NAME_NOT_FOUND, handlers) as any 47
onError の処理のフロー handler でエラーが発生する router.match("@ERROR", c.req.path) compose(handlers) 48
何が嬉しいか? 49
middleware を利用できる 50
ルーターのユーティリティを利用できる 51
例: subApp と app の両方に notFound を登録した場合 const app =
new Hono() .get('/', home) .get('/health', healthCheck) const subApp = new Hono() .get('/users', listUsers) .get('/users/:id', getUser) .post('/users', createUser) .onError(handleApiError) .notFound(handleApiNotFound) app.route('/api', subApp) .onError(handleAppError) .notFound(handleAppNotFound) 52
showRoutes で、エラーと 404 の handler も一覧に出せる showRoutes(app, { includeInternal: true,
verbose: true }) GET GET GET GET POST @ERROR @NOT_FOUND @ERROR @NOT_FOUND 53 / /health /api/users /api/users/:id /api/users /api/* /api/* /* /* home healthCheck listUsers getUser createUser handleApiError handleApiNotFound handleAppError handleAppNotFound
各ルーターの機能を利用できる 54
例: /api 以下のエラー用 handler に処理を追加する const router = new TransformRouter({
delegateRouter: new RegExpRouter(), transform: ({ handler, method, path }) => { if (method === '@ERROR' && path.startsWith('/api/')) { return async (c, next) => { await report(path, c.error) return handler(c, next) } } return handler }, }) app.onError('/api/*', (c) => c.text('api error', 500)) 55
悩みどころもある 56
通常のルーティングのままの場合 登録順だと、親の onError() で登録した handler でレスポンスが返ってしまう subApp の onError() は呼ばれない
// '/*' app.onError(async (c) => c.text(`app: ${c.error?.message}`, 500)) // '/sub/*' subApp.onError(async (c, next) => c.text(`subApp: ${c.error?.message}`, 500)) app.route('/sub', subApp) 57
期待通りの順序になるようにルールを調整 各ルートは route() で追加された回数を depth として持つ マッチした handler は depth
の深い順に並べる // '/*', depth: 0 app.onError(async (c) => c.text(`app: ${c.error?.message}`, 500)) // '/sub/*', depth: 1 subApp.onError(async (c, next) => c.text(`subApp: ${c.error?.message}`, 500)) app.route('/sub', subApp) 58
depth の深い順に並べる handler でエラーが発生する router.match("@ERROR", c.req.path) handlers.sort(byDepthDesc) depth の深い順に並べる compose(handlers)
59
まとめ
ルーター中心で捉えるメリット 使う側 新機能も通常のルートと同じ意味論で捉えられて、シグネチャも同じままで使える 実装側 少ない変更で大きな効果が得られる Hono らしさ ルーターがたくさんあることを活かせる 61
埋める必要がある違いもある 順序 通常のルートは単純な登録順だが、 notFound() / onError() では深さ順が優先される 登録順だと、親の onError() の応答で完結してしまい、subApp
に届かない 62
ご期待ください honojs/hono#5201 TransformRouter honojs/hono#5541 notFound / onError 63 Merged
ありがとうございました github.com/sponsors/usualoma