Next.jsでもHonoを使ってOpenAPIの支援を受けられるようにする #honoconf
by
Azuki-bar
×
Copy
Open
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
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を導入することで豊富なエコシステムを 獲得