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を導入することで豊富なエコシステムを 獲得