Next.jsでもHonoを使ってOpenAPIの支援を受けられるようにする #honoconf
by
Azuki-bar
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
Next.jsでもHonoを使っ てOpenAPIの支援を受け られるようにする id:ymse / @azukibar_D 2025/10/18 Hono Conference 2025 1
Slide 2
Slide 2 text
お品書き - 話の前提となるアプリケーション構成 - Next.jsのルーティングとリクエストハンドラ - HonoとNext.jsを統合する - Hono OpenAPI 2
Slide 3
Slide 3 text
自己紹介 - あずきバー @azukibar_D - 株式会社はてな - Webアプリケーションエンジニア - 普段はTypeScriptを書いています 3
Slide 4
Slide 4 text
類似テーマのみなさま - 「Next.jsでもHonoを使ってOpenAPIの支援 を受けられるようにする」 - 会場に類似テーマの発表がいくつかある - Aditya Mathur氏 - @hono/mcpの話をされていた - Hono OpenAPIの作者 4
Slide 5
Slide 5 text
5
Slide 6
Slide 6 text
6
Slide 7
Slide 7 text
類似テーマの2名 - フロントエンド主体で進めるHono×zod-openapiのスキー マ駆動開発 - スキーマをもとにモックを作成 - スキーマ駆動開発における開発プロセス - スキーマ駆動で、Zod OpenAPI Honoによる、API開発す るために、Hono Takibiというライブラリを作っている - OpenAPIのスキーマからhono/zod-openapiを作るHono Takibiの話 - 話すテーマとは逆のアプローチ 7
Slide 8
Slide 8 text
8 前提
Slide 9
Slide 9 text
お品書き - 話の前提となるアプリケーション構成 - Next.jsのルーティングとリクエストハンドラ - HonoとNext.jsを統合する - Hono OpenAPI 9
Slide 10
Slide 10 text
toitta - インタビュー動画加工 - 書き起こし - 切片化 - 任意の設問に対し、 インタビューをもとに 回答を生成 10
Slide 11
Slide 11 text
おおまかな構成 11
Slide 12
Slide 12 text
おおまかな構成 12 ここの話をします
Slide 13
Slide 13 text
ユーザーから見えるのは Next.jsアプリのみ - Next.jsアプリがユーザーから見えるUIを提供 - App Router - 表示するデータの取得 - Server Functionsでのデータ更新 - ドメイン固有の処理はNext.jsアプリ内に定義 - DBへ保存するJSONカラムのスキーマ 13
Slide 14
Slide 14 text
すべてをNext.jsアプリで処理しない - Next.jsで実行すると都合が悪い処理もある - 生成AIへのコンテンツ生成リクエスト - 動画の読み込みや変換処理 - CPU/メモリを多く使うので水平スケーリングしやすく - インタビュー動画の書き起こし - GPUを使いたい 14
Slide 15
Slide 15 text
コンテンツ生成部分の特徴 - Pythonで実装されている - DBに繋がっていない - Python-Prismaは2025年4月にアーカイブされている - Next.jsアプリを経由してデータ取得 - Next.jsがデータ取得用のAPIを提供する必要がある 15
Slide 16
Slide 16 text
実装あるある - 新機能の追加に向けAPIを実装 - APIを使う側と提供する側で分かれて実装 - 結合したら400 Bad Request - 入出力値スキーマの変更追従漏れ - あとから値が不足していることに気がつく 16
Slide 17
Slide 17 text
APIの仕様を書いて共有 - 仕様を合意してから作る - 「/api/ _/interview に ?id=foo を付けてGETをすると データが取れます」 - 「保存するときのBodyはこれでいきます」 - @valibot/json-schema で吐いたJSON Schemaを貼る - 最初のほうはなんとかなる 17
Slide 18
Slide 18 text
崩壊 - 実装を進めると過不足に気がつく - 取得するリソースを絞りたい - バルクで取得したい - 直す - (ここでドキュメント・実装の追従が漏れる) - 突然壊れる - さっきまで使えていたエンドポイントが400を返す 18
Slide 19
Slide 19 text
崩壊を防ぐには - 崩壊を防ぐための知見はすでにある - OpenAPIやgRPCなどの仕組みが一例 - 今回はOpenAPIの力を借りる話をします 19
Slide 20
Slide 20 text
20 Next.jsのルーティング と リクエストハンドラ
Slide 21
Slide 21 text
お品書き - 話の前提となるアプリケーション構成 - Next.jsのルーティングとリクエストハンドラ - Next.jsのルーティングをおさらい - Route Handlerで実装するHTTP API - HonoとNext.jsを統合する - Hono OpenAPI 21
Slide 22
Slide 22 text
お品書き - 話の前提となるアプリケーション構成 - Next.jsのルーティングとリクエストハンドラ - Next.jsのルーティングをおさらい - Route Handlerで実装するHTTP API - HonoとNext.jsを統合する - Hono OpenAPI 22
Slide 23
Slide 23 text
Next.jsのルーティング - Next.jsはファイルベースでルーティング - ディレクトリ構成とURL構成が一致する - /app/foo/page.tsxでコンポーネントをエクスポートす ると /fooへのリクエストで表示される - 規約に沿ったディレクトリ名で特別扱いをする - URLの一部を動的に処理 23
Slide 24
Slide 24 text
Dynamic Route Segments - パスにidなどが含まれるURLを処理する仕組み - 例)GET /api/foo/:slug/list - ハンドラ内でURLの値を取得できる - 例) slug - []で囲うと動的になる - 例)api/foo/[slug]/list/route.ts で/api/foo/1/list な どの処理ができる 24
Slide 25
Slide 25 text
多階層の処理 - []で囲ったときは1階層 - 例)api/foo/[slug]/list/route.ts で/api/foo/1/list な どの処理ができる - /api/barと/api/bar/bazを同一ハンドラで処 理したいときもある - Catch-all Segmentsの登場 25
Slide 26
Slide 26 text
2つのCatch-all - Catch-all - /api/foo/[...slug]/route.tsで以下にマッチ - /api/foo/1, /api/foo/1/2/3 - Optional Catch-all - /api/foo/[[...slug]]/route.tsで以下にマッチ - /api/foo/1, /api/foo/1/2/3, /api/foo - あとで出てきます 26
Slide 27
Slide 27 text
2つのCatch-all - Catch-all - /api/foo/[...slug]/route.tsで以下にマッチ - /api/foo/1, /api/foo/1/2/3 - Optional Catch-all - /api/foo/[[...slug]]/route.tsで以下にマッチ - /api/foo/1, /api/foo/1/2/3, /api/foo ←にマッチするのが Optional Catch-all - あとで出てきます 27
Slide 28
Slide 28 text
Route Groups - Next.jsはファイルベースルーティング - URLとディレクトリ構造が一致 - とはいえURLとは別にグループを作りたくなる - 関心や機能で分ける - ディレクトリ名を()で囲むとURLには表れない グループができる - 例 /api/(admin)/ と /api/(guest)/ 28
Slide 29
Slide 29 text
Next.jsルーティングまとめ - 基本はファイルベース - []で囲うとハンドラで値が取れる - 例) /api/foo/[slug]/list - [...slug]のように3点始まりだとCatch-all - [[...slug]]のように[[]]で囲うとOptional Catch-all - ()で囲うとパスに表れないグループができる - Route Groups 29
Slide 30
Slide 30 text
お品書き - 話の前提となるアプリケーション構成 - Next.jsのルーティングとリクエストハンドラ - Next.jsのルーティングをおさらい - Route Handlerで実装するHTTP API - HonoとNext.jsを統合する - Hono OpenAPI 30
Slide 31
Slide 31 text
31 Next.jsでAPIを提供する - /api/foo/route.tsに以下のコードを置く - Route Handlerと呼ばれている - /api/fooへのGETで200 OKが返る export async function GET(request: Request) { return new NextResponse("ok", {status: 200}); }
Slide 32
Slide 32 text
入出力のバリデーション - Next.js組み込みの入出力値検査はない - 入力はawait req.json()で取得し自前でバリ デーションする - バリデーションはスキーマライブラリを使うの が良い 32
Slide 33
Slide 33 text
Zod, Valibotなどのスキーマライブラ リ - スキーマを定義し検査するライブラリ - Zod, Valibot, ArkType, … - TypeScriptだと値に型がついてお得 import * as v from 'valibot'; const inputSchema = v.object({ id: v.string() }) v.parse(inputSchema, await req.json()) 33
Slide 34
Slide 34 text
34 HonoとNext.js
Slide 35
Slide 35 text
お品書き - 話の前提となるアプリケーション構成 - Next.jsのルーティングとリクエストハンドラ - HonoとNext.jsを統合する - Hono OpenAPI 35
Slide 36
Slide 36 text
36 Next.js統合 - HonoはNext.jsと統合できる - ドキュメントにもある - https://hono.dev/docs/getting-started/nextjs - hono/vercelを使うといい感じにハンドルされ る
Slide 37
Slide 37 text
Honoのルータどこに置く - Next.jsはファイルベースルーティング - Honoはあるファイルがルータとして処理を分 配してほしい - Honoとはルーティングの考えかたが違う - Routerを置くHonoとRoutingをフレームワークがやる Next.js 37
Slide 38
Slide 38 text
Honoのハンドラどこに置く - (復習)[[...foo]]/route.tsで全てのリクエス トを扱える - Optional Catch-all Segments - Route Groupsで関心を分けたい - Next.jsで処理されるハンドラとHonoで処理されるハン ドラ 38
Slide 39
Slide 39 text
実装例 // app/api/(hono)/[[...slug]]/route.tsに以下のファイルを配置 import { Hono } from 'hono' import { handle } from 'hono/vercel' const app = new Hono().basePath('/api') app.get('/hello', (c) => { return c.json({ message: 'Hello Next.js!' }) }) export const GET = handle(app) export const POST = handle(app) // https://hono.dev/docs/getting-started/nextjs より引用し一部改変 39
Slide 40
Slide 40 text
実装例 // app/api/(hono)/[[...slug]]/route.tsに以下のファイルを配置 import { Hono } from 'hono' import { handle } from 'hono/vercel' const app = new Hono().basePath('/api') app.get('/hello', (c) => { return c.json({ message: 'Hello Next.js!' }) }) export const GET = handle(app) export const POST = handle(app) // https://hono.dev/docs/getting-started/nextjs より引用し一部改変 40 アダプタとして hono/vercelをimport
Slide 41
Slide 41 text
実装例 // app/api/(hono)/[[...slug]]/route.tsに以下のファイルを配置 import { Hono } from 'hono' import { handle } from 'hono/vercel' const app = new Hono().basePath('/api') app.get('/hello', (c) => { return c.json({ message: 'Hello Next.js!' }) }) export const GET = handle(app) export const POST = handle(app) // https://hono.dev/docs/getting-started/nextjs より引用し一部改変 41 アダプタとして hono/vercelをimport Next.jsは特定名でexport された関数がリクエストを ハンドルする hono/vercelのhandleで 囲って関数を実装
Slide 42
Slide 42 text
既存の実装と同居 - 過渡期はRoute Handlerで実装されたハンドラ とHonoで実装されたハンドラが同居 - 意味的にも違うのでRoute Groupsで分ける - Honoで実装されたハンドラは(hono)ディレク トリ以下に置く 42
Slide 43
Slide 43 text
めでたしめでたし - Next.jsのAPIをHonoで実装することに成功 - ElysiaJSでも同じ手法が紹介されている - https://elysiajs.com/integrations/nextjs.html - 新規に実装するだけだったらこれで良いが…… - 我々にはすでに実装した既存のリソースがある 43
Slide 44
Slide 44 text
既存のリソースとどう同居する? - Next.jsの仕組みで実装したミドルウェア - ロギング - 認証 - トレース - Route Handlerで実装した既存エンドポイント 44
Slide 45
Slide 45 text
既存のリソースとどう同居する? - Next.jsの仕組みで実装したミドルウェア - ロギング - 認証 - トレース - Route Handlerで実装した既存エンドポイント 45
Slide 46
Slide 46 text
既存のミドルウェア - 今回の手法はあくまでNext.jsのルーティング をHonoに委譲するだけ - ミドルウェアの処理がされてからHonoに渡っ てくる - Next.jsミドルウェア, Next.jsルータ, Honoのルータの順 で処理 - なので考慮不要。そのままで良いはず 46
Slide 47
Slide 47 text
既存のリソースとどう同居する? - Next.jsの仕組みで実装したミドルウェア - ロギング - 認証 - トレース - Route Handlerで実装した既存エンドポイント 47
Slide 48
Slide 48 text
Optional Catch-all segments の処理順 . └── api ├── (hono) │ └── [[...slugs]] │ └── route.ts (1) └── foo └── route.ts (2) 48 /api/fooへの処理 (2)のRoute Handlerが 処理する (1)のroute.tsに書いた /api/fooのハンドラは 実行されない
Slide 49
Slide 49 text
Optional Catch-all segments の処理順 . └── api ├── (hono) │ └── [[...slugs]] │ └── route.ts (1) └── foo └── route.ts (2) 49 左のような構造のコー ドを想定 (1)のroute.tsに書いた /api/fooのハンドラは 実行されない Optional Catch-all segmentsの 処理優先順位が低い
Slide 50
Slide 50 text
Optional Catch-all segments - Optional Catch-all segmentsの処理優先順位 が低い - つまりRoute Handlerの実装をそのままに Honoの実装を増やせる - 段階的な移行が可能 50
Slide 51
Slide 51 text
既存のリソースとどう同居する? まとめ - Next.jsの仕組みで実装したミドルウェア - -> 手を加えず、そのままで良い - Route Handlerで実装した既存エンドポイント - -> Hono側の実装が後に評価されるのでそのままで良い 51
Slide 52
Slide 52 text
Next.js x Honoまとめ - Route Handlerで作ったハンドラそのままに Honoのハンドラを横付け - 段階的にHonoの強力なエコシステムを獲得 52
Slide 53
Slide 53 text
53 Hono OpenAPI
Slide 54
Slide 54 text
お品書き - 話の前提となるアプリケーション構成 - Next.jsのルーティングとリクエストハンドラ - HonoとNext.jsを統合する - Hono OpenAPI 54
Slide 55
Slide 55 text
Hono OpenAPI - Honoのミドルウェアとしてふるまう - 入力値のバリデーションやドキュメント生成を 行なう - 先日v1が出た - 🎉standard schema対応 55
Slide 56
Slide 56 text
スキーマ出力 - Hono OpenAPIはopenapi.jsonを出力できる - https://honohub.dev/docs/openapi/persisting - generateSpecsにHonoのappを渡すと出力される - 出力スクリプトをCIで都度実行、差分があった ら落ちるように - 常に正しいopenapi.jsonがリポジトリにある状態 56
Slide 57
Slide 57 text
OpenAPIの恩恵 - API呼び出し側はopenapi.jsonを元に実装可能 - 例えばopenapi-generator-cliでコード生成 - Swaggerを用いれば人間が読みやすいドキュメ ントも入手できる 57
Slide 58
Slide 58 text
課題解決 - API仕様の変更追従漏れ課題があった - 仕様を自動生成することで解決 - 人間が都度変更する必要がなくなる - CIなどで仕様変更と同時に壊れたことを検知できる 58
Slide 59
Slide 59 text
59 Next.jsと Hono+Hono OpenAPI
Slide 60
Slide 60 text
60 ここまでの話をまとめる - HonoとNext.jsを連携できる - HonoとHono OpenAPIを繋げるとHonoを OpenAPI対応できる
Slide 61
Slide 61 text
61 ここまでの話をまとめる - HonoとNext.jsを連携できる - HonoとHono OpenAPIを繋げるとHonoを OpenAPI対応できる Next.jsでもHonoを使って OpenAPIの支援を受けられるようになる
Slide 62
Slide 62 text
62 ここまでの話をまとめる - HonoとNext.jsを連携できる - Honoとhono-openapiを繋げるとHonoを OpenAPI対応できる Next.jsでもHonoを使って OpenAPIの支援を受けられるようになる
Slide 63
Slide 63 text
63 まとめ
Slide 64
Slide 64 text
64 まとめ - Next.jsで提供するHTTP APIをHonoによって 提供 - Hono OpenAPIでOpenAPIの力を手にいれる - Next.jsでもコードファーストでAPI仕様を生成 する - Honoを導入することで豊富なエコシステムを 獲得