Guide/guides

ルーティングガイド

Guren は Hono の HTTP サーバー上に Laravel 風のルーティング DSL を提供します。推奨構成では routes/web.ts が registrar 関数を export し、アプリ起動時に app-local な Router へルートを登録します。

ルーティングガイド

Guren は Hono の HTTP サーバー上に Laravel 風のルーティング DSL を提供します。推奨構成では routes/web.ts が registrar 関数を export し、アプリ起動時に app-local な Router へルートを登録します。

基本の使い方

routes/web.ts を作成または編集し、Router と使用するコントローラーをインポートします。

import { Router } from '@guren/core'
import PostsController from '@/app/Http/Controllers/PostsController'

export function registerWebRoutes(router: Router): void {
  router.get('/', [PostsController, 'index'])
  router.post('/posts', [PostsController, 'store'])
}

各ルートはパスと以下のいずれかを受け取ります。

  • コントローラータプル [ControllerClass, 'method']
  • インラインハンドラー (ctx) => new Response('...')

利用できるメソッドは router.getrouter.postrouter.putrouter.patchrouter.delete、そして汎用の router.on(method, path, handler) です。

ルートグループ

router.group(prefix, callback) を使って、共通のパスプレフィックスとミドルウェアを適用できます。

router.group('/posts', (posts) => {
  posts.get('/', [PostsController, 'index'])
  posts.get('/:id', [PostsController, 'show'])
})

グループはネスト可能です。プレフィックスは自動的にトリミングされるため、/posts + /new/posts/new になります。

ミドルウェア

ルート単位のミドルウェア

ハンドラーに .middleware() をチェーンして適用できます。

import { Router, requireAuthenticated } from '@guren/core'
import { requireAdmin } from '@/app/Http/middleware/admin'

export function registerWebRoutes(router: Router): void {
  router.aliasMiddleware('auth', requireAuthenticated())
  router.aliasMiddleware('admin', requireAdmin())

  router.get('/admin', [AdminController, 'index']).middleware('auth', 'admin')
}

ミドルウェアエイリアス

ミドルウェア関数に短い名前を登録しておけば、ルート全体で文字列で参照できます。

import { Router, requireAuthenticated } from '@guren/core'
import { requireAdmin } from '@/app/Http/middleware/admin'

export function registerWebRoutes(router: Router): void {
  router.aliasMiddleware('auth', requireAuthenticated())
  router.aliasMiddleware('admin', requireAdmin())
}

エイリアスを登録すれば、ミドルウェアが受け入れられる場所ならどこでも文字列名で使えます。

router.get('/dashboard', [DashboardController, 'index']).middleware('auth')
router.get('/admin', [AdminController, 'index']).middleware('auth', 'admin')

ミドルウェアグループ

よく使うミドルウェアの組み合わせを一つの名前にまとめられます。

router.groupMiddleware('web', ['session', 'csrf'])
router.groupMiddleware('api', ['throttle:60'])

ミドルウェアグループをルートグループに適用します。

router.middleware('web').group((web) => {
  web.get('/', [HomeController, 'index'])
  web.get('/about', [PagesController, 'about'])
})

router.middleware('auth').group((auth) => {
  auth.get('/dashboard', [DashboardController, 'index'])
  auth.get('/settings', [SettingsController, 'index'])
})

ミドルウェアグループと個別のエイリアスは自由に組み合わせられます。

router.middleware('web', 'auth').group((group) => {
  group.get('/profile', [ProfileController, 'show'])
})

グローバル登録パターン、ビルトインヘルパー、セッションサポートの詳細は、専用のミドルウェアガイドをご覧ください。

ルートパラメータ

動的パラメータは Hono の構文に従います。

router.get('/posts/:id', [PostsController, 'show'])

コントローラー内では this.validateParams()this.ctx.req.param('id') でパラメータを読み取ります。

オプショナルセグメントには Hono のパターンサポート(router.get('/posts/:id?', handler))を使い、ワイルドカードには *(例: /:slug*)を使います。

ルートモデルバインディング

毎回 findOrFail() を書きたくない場合は、ルートパラメータにモデルをバインドできます。

import { PostResource } from '@/app/Http/Resources/PostResource'
import { pages } from '@/.guren/pages.gen'

router.bind('post', Post)
router.get('/posts/:post', [PostsController, 'show'])

async show() {
  const post = this.ctx.get('post') as PostRecord
  return this.inertia(pages.posts.Show, { post: new PostResource(post).toJSON() })
}

slug ベースの解決にしたい場合は、独自 resolver も渡せます。

router.bind('post', async (value) => Post.where('slug', value).firstOrFail())

ブートストラップ

src/app.ts で registrar を createApp() に渡します。

// src/app.ts
import { createApp } from '@guren/core'
import registerWebRoutes from '@/routes/web'

const app = createApp({
  routes: registerWebRoutes,
})

カスタムハンドラー

インラインハンドラーを使えば、コントローラーなしで Hono の Context を直接扱えます。

router.get('/health', (ctx) => ctx.json({ ok: true }))

ヘルスチェックや Webhook のような軽量エンドポイントに便利です。

Tips

  • routes/web.ts は HTTP 定義に集中させましょう。ビジネスロジックはコントローラーやサービスに移してください。
  • 大規模なアプリでは、ルートを追加ファイル(例: routes/admin.ts)に分割し、src/app.ts で registrar を合成します。
  • 分かりやすいコントローラーメソッド名(indexshowstoreupdatedestroy)を使うと、フレームワーク全体の規約と揃います。
  • ミドルウェアエイリアスを活用すると、ルートファイルがすっきりし、あちこちでミドルウェア関数をインポートする必要がなくなります。

ルーティング DSL を使えば、複雑な HTTP 構造を表現しながら、エントリーポイントをクリーンで宣言的に保てます。

ルートコントラクト

第 2 引数にオプションオブジェクトを渡すと、Zod スキーマとメタデータをルートに付与できます。フレームワークはこれらのスキーマをリクエストバリデーション、コード生成、OpenAPI ドキュメント生成に使用します。

import { z } from 'zod'

const CreatePostSchema = z.object({
  title: z.string().min(1),
  body: z.string().min(1),
})

const PostIdParams = z.object({
  id: z.coerce.number().int().positive(),
})

router.post('/posts', {
  body: CreatePostSchema,
  name: 'posts.store',
}, [PostsController, 'store'])

router.get('/posts/:id', {
  params: PostIdParams,
  name: 'posts.show',
}, [PostsController, 'show'])

利用可能なコントラクトフィールド:

フィールド 用途
name URL 生成・コード生成用のルート名
params パスパラメータの Zod スキーマ
query クエリパラメータの Zod スキーマ
body リクエストボディの Zod スキーマ
output レスポンスボディの Zod スキーマ
bind ルートモデルバインディングマップ
middlewares ミドルウェアハンドラーの配列

Note

同じクエリキーが繰り返された場合、query スキーマには配列として渡されます(?tag=a&tag=b{ tag: ['a', 'b'] })。1 回だけ出現するキーは文字列のままです。詳細は配列形式のクエリパラメータを参照してください。

OpenAPI メタデータ

ルートコントラクトには軽量な OpenAPI アノテーションも指定できます。これらはルート定義に保存され、オプションの @guren/openapi プラグインで OpenAPI 3.1 ドキュメントを生成する際に使用されます。

router.post('/posts', {
  body: CreatePostSchema,
  output: PostResponseSchema,
  name: 'posts.store',
  summary: 'Create a post',
  description: 'Creates a new blog post.',
  tags: ['Posts'],
}, [PostsController, 'store'])

router.get('/posts/:id', {
  params: PostIdParams,
  name: 'posts.show',
  summary: 'Get a post',
  tags: ['Posts'],
  deprecated: false,
}, [PostsController, 'show'])

利用可能な OpenAPI フィールド:

フィールド 用途
summary string ドキュメント UI に表示される短い説明
description string エンドポイントの詳細な説明
tags string[] ドキュメント UI でエンドポイントをグループ化
operationId string 自動生成されるオペレーション ID を上書き
deprecated boolean エンドポイントを非推奨としてマーク

スペックドキュメントの生成については CLI リファレンスの OpenAPI セクションを参照してください。

OpenAPI ドキュメント生成

オプションの @guren/openapi パッケージをインストールして、ルート定義からスペックを生成します。

bun add @guren/openapi
bunx guren openapi:generate

ルートファイルを読み取り、ルートコントラクトから Zod スキーマと OpenAPI メタデータを抽出し、OpenAPI 3.1 JSON ドキュメントを .guren/openapi.gen.json に書き出します。

CLI オプション

# タイトルとバージョンを指定
bunx guren openapi:generate --title "Blog API" --version "1.0.0"

# 出力パスを変更
bunx guren openapi:generate --out docs/openapi.json

# サーバー URL を含める
bunx guren openapi:generate --server "https://api.example.com"

# 既存ファイルを上書き
bunx guren openapi:generate --force

ランタイムでのドキュメントマウント

OpenAPI スペックとインタラクティブなドキュメント UI をアプリケーションから直接配信することもできます。

import { createApp } from '@guren/core'
import { mountOpenApiDocs } from '@guren/openapi'

const app = createApp({ routes: registerWebRoutes })

mountOpenApiDocs(app, {
  title: 'Blog API',
  version: '1.0.0',
})

以下の 2 つのエンドポイントがマウントされます。

パス 説明
/openapi.json 生成された OpenAPI 3.1 JSON ドキュメント
/docs インタラクティブな API ドキュメント UI(Scalar)

パスは jsonPathdocsPath オプションでカスタマイズできます。

mountOpenApiDocs(app, {
  title: 'Blog API',
  version: '1.0.0',
  jsonPath: '/api/openapi.json',
  docsPath: '/api/docs',
})

Application インスタンスにマウントする場合、ルート定義はルーターから自動的に読み取られます。素の Hono インスタンスの場合は definitions を明示的に渡してください。