mcpField
S
server-routes

server-routes

github.com/tanstack開発15.3k195 回インストール4 日前に更新
リポジトリ

server-routes について

TanStack Routerでアプリのルートと並行してHTTPエンドポイントを作成するためのスキルです。ファイルベースの規約、リクエスト処理、ミドルウェア、安全面の注意点を扱い、明確なHTTP契約が必要な開発者向けです。

主な機能

  1. 01ファイルベースのサーバールート規約
  2. 02HTTPメソッドハンドラー
  3. 03リクエストとレスポンスの処理
  4. 04ルートおよびハンドラーのミドルウェア
  5. 05動的パラメーターとワイルドカード

活用シーン

  1. 01アプリのルートと並行してHTTP APIを公開
  2. 02JSONボディとステータスコードを処理
  3. 03認証や検証のミドルウェアを適用

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/tanstack/router --skill server-routes

インストール後、Claude Code で/server-routesを入力して読み込まれたか確認します。

詳細

サーバールート

サーバールートは、src/routes ディレクトリ内でアプリルートと並んで定義される API エンドポイントです。createFileRoute の server プロパティを使用し、生の HTTP リクエストを処理します。

呼び出し元に HTTP コントラクトが必要な場合は、サーバールートを使用してください。Start アプリケーション内だけで使用するデータには、サーバー関数を使用し、ローダーから直接呼び出してください。ルートローダーは SSR 中およびクライアントナビゲーション中に実行されるため、fetch('/api/...') はポータブルなローダーパターンではありません。

基本的なサーバールート

// src/routes/api/hello.ts
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/api/hello')({
  server: {
    handlers: {
      GET: async ({ request }) => {
        return new Response('Hello, World!')
      },
    },
  },
})

サーバールートとアプリルートの組み合わせ

同じファイルにサーバールートと UI ルートの両方を定義できます。

// src/routes/hello.tsx
import { createFileRoute } from '@tanstack/react-router'
import { useState } from 'react'

export const Route = createFileRoute('/hello')({
  server: {
    handlers: {
      POST: async ({ request }) => {
        const body = await request.json()
        return Response.json({ message: `Hello, ${body.name}!` })
      },
    },
  },
  component: HelloComponent,
})

function HelloComponent() {
  const [reply, setReply] = useState('')
  return (
    <button
      onClick={() => {
        fetch('/hello', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ name: 'Tanner' }),
        })
          .then((res) => res.json())
          .then((data) => setReply(data.message))
      }}
    >
      Say Hello {reply && `- ${reply}`}
    </button>
  )
}

上記の相対 fetch('/hello') は、ブラウザーのクリックハンドラー内でのみ実行されるため安全です。

Start ルートとのデータ共有

SSR ローダーから自身のサーバールートを呼び出さないでください。ビジネス処理をサーバー専用サービスに配置し、両方が必要な場合は両方の境界から公開してください。

// src/server/issues.server.ts
export function listIssues() {
  return db.issues.findMany()
}

// src/routes/api/issues.ts
export const Route = createFileRoute('/api/issues')({
  server: {
    handlers: {
      GET: async () => Response.json(await listIssues()),
    },
  },
})

// src/routes/issues.tsx
const getIssues = createServerFn({ method: 'GET' }).handler(() => {
  return listIssues()
})

export const Route = createFileRoute('/issues')({
  loader: () => getIssues(),
})

これにより、SSR が URL 解決に依存しなくなり、ビジネスロジックのソースを 1 つに保てます。

ファイルルートの規約

サーバールートは、TanStack Router のファイルベースルーティング規約に従います。

ファイルルート
routes/users.ts/users
routes/users/$id.ts/users/$id
routes/users/$id/posts.ts/users/$id/posts
routes/api/file/$.ts/api/file/$(スプラット)
routes/my-script[.]js.ts/my-script.js(エスケープされたドット)

一意のルートパス

各ルートに設定できるハンドラーファイルは 1 つだけです。次のファイルは競合します。

  • routes/users.ts
  • routes/users.index.ts
  • routes/users/index.ts

ハンドラーコンテキスト

各ハンドラーは、次の情報を受け取ります。

  • request — 受信した Request オブジェクト
  • params — 動的なパスパラメーター
  • context — ミドルウェアからのコンテキスト
  • pathname — マッチしたパス名
  • next — SSR に処理を渡すための呼び出し(Response を返します)

動的パスパラメーター

// routes/users/$id.ts
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/users/$id')({
  server: {
    handlers: {
      GET: async ({ params }) => {
        return new Response(`User ID: ${params.id}`)
      },
    },
  },
})

スプラット/ワイルドカードパラメーター

// routes/file/$.ts
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/file/$')({
  server: {
    handlers: {
      GET: async ({ params }) => {
        return new Response(`File: ${params._splat}`)
      },
    },
  },
})

リクエストボディの処理

export const Route = createFileRoute('/api/users')({
  server: {
    handlers: {
      POST: async ({ request }) => {
        const body = await request.json()
        return Response.json({ created: body.name })
      },
    },
  },
})

その他のボディメソッド:request.text()、request.formData()。

JSON レスポンス

// Using Response.json helper
handlers: {
  GET: async () => {
    return Response.json({ message: 'Hello!' })
  },
}

ステータスコードとヘッダー

handlers: {
  GET: async ({ params }) => {
    const user = await findUser(params.id)
    if (!user) {
      return new Response('Not found', { status: 404 })
    }
    return Response.json(user)
  },
}
handlers: {
  GET: async () => {
    return new Response('Hello', {
      headers: { 'Content-Type': 'text/plain' },
    })
  },
}

サーバールートでのミドルウェア

すべてのハンドラー
export const Route = createFileRoute('/api/admin')({
  server: {
    middleware: [authMiddleware, loggerMiddleware],
    handlers: {
      GET: async ({ context }) => Response.json(context.user),
      POST: async ({ request, context }) => {
        /* ... */
      },
    },
  },
})
createHandlers を使用した特定のハンドラー
export const Route = createFileRoute('/api/data')({
  server: {
    handlers: ({ createHandlers }) =>
      createHandlers({
        GET: async () => Response.json({ public: true }),
        POST: {
          middleware: [authMiddleware],
          handler: async ({ context }) => {
            return Response.json({ user: context.session.user })
          },
        },
      }),
  },
})
ルートレベルとハンドラー固有の設定の組み合わせ
export const Route = createFileRoute('/api/posts')({
  server: {
    middleware: [authMiddleware], // runs first for all
    handlers: ({ createHandlers }) =>
      createHandlers({
        GET: async () => Response.json([]),
        POST: {
          middleware: [validationMiddleware], // runs after auth, POST only
          handler: async ({ request }) => {
            const body = await request.json()
            return Response.json({ created: true })
          },
        },
      }),
  },
})

よくある間違い

1. 重大:ページは保護しても、サーバールートを保護していない

プライベートデータを読み取る、または書き込むすべてのハンドラーで、ルートミドルウェア、ハンドラーミドルウェア、またはハンドラー内のチェックを通じて、リクエストを認証・認可する必要があります。ルーターの beforeLoad リダイレクトでは /api/... は保護されません。Cookie なしでハンドラーを直接テストし、プライベートなペイロードが返されないことを確認してください。

2. 中程度:ルートパスの重複
# WRONG — both resolve to /users, causes error
routes/users.ts
routes/users/index.ts

# CORRECT — pick one
routes/users.ts
3. 中程度:リクエストボディメソッドの await 忘れ
// WRONG — body is a Promise, not the actual data
const body = request.json()

// CORRECT — await the promise
const body = await request.json()
4. 高:TypeScript をレスポンススキーマの検証として信頼する

リクエスト入力を検証し、シリアライズされた Response の出力をテストしてください。型付けされたサービスでも、新しく追加されたフィールドが射影またはシリアライズされない場合があります。スキーマを変更した場合は、サーバールートの境界で await response.json() をアサートしてください。

関連項目

コメント