# Fumadocs (Framework Mode, Waku): createOpenAPI()
URL: /ja/docs/waku/integrations/openapi/server
Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/(framework)/[framework]/integrations/openapi/server.ja.mdx

OpenAPI のサーバーインスタンスです。

## OpenAPI Server [#openapi-server]

Fumadocs OpenAPI のメイン設定です。

> ブラウザ環境から参照しないでください。

### `input` [#input]

読み込む OpenAPI スキーマです。

* ファイルパス
* 外部 URL
* 関数 (後述) 

読み込めるのはここに指定したスキーマのみです。それ以外の ID を `getSchema()` に渡したり、ページでプリロードしたりするとエラーがスローされます。

<Callout type="warn">
信頼できないスキーマを `input` に渡さないでください。スキーマ内の `$ref` によって、ローカルファイルが読み取られたり、プライベートホストを含む任意の URL がフェッチされたりする可能性があります。
</Callout>

```ts tab="Basic"
import { createOpenAPI } from 'fumadocs-openapi/server';

export const openapi = createOpenAPI({
  input: ['./unkey.json'],
});
```

```ts tab="Functions"
import { createOpenAPI } from 'fumadocs-openapi/server';

export const openapi = createOpenAPI({
  input: {
    // [id]: ダウンロードした OpenAPI スキーマ
    my_schema: async () => {
      const res = await fetch('https://registry.scalar.com/@scalar/apis/galaxy/latest?format=json');
      return res.json();
    },
  },
});
```

## ドキュメントを一度だけ読み込む [#loading-documents-once]

ページは props でドキュメント全体を受け取るため、ページの読み込み、ナビゲーション、プリフェッチのたびにドキュメントが再送信されます。ドキュメントが大きい場合は、props に含めないようにしてください。

```ts title="lib/openapi.ts"
import { createOpenAPI } from 'fumadocs-openapi/server';

export const openapi = createOpenAPI({
  // ID は URL に含まれるため、URL セーフにしてください
  input: { museum: './museum.yaml' },
  includeDocument: false, // [!code ++]
});
```

`loadDocument` を渡すと、各ドキュメントの読み込みがブラウザセッションごとに1回で済みます。この関数はサーバーレンダリング時にも実行されます:

```tsx title="components/api-page.tsx"
'use client';
import { preload } from 'react-dom';
import { createOpenAPIPage } from 'fumadocs-openapi/ui';

export const OpenAPIPage = createOpenAPIPage({
  async loadDocument(id) {
    if (import.meta.env.SSR) {
      // ブラウザはページと同時にダウンロードを開始します
      preload(`/api/openapi/${id}`, { as: 'fetch', crossOrigin: 'anonymous' });
      const { openapi } = await import('@/lib/openapi');
      return (await openapi.getSchema(id)).bundled;
    }
    return (await fetch(`/api/openapi/${id}`)).json();
  },
});
```

ルートを用意して、ドキュメントをブラウザに配信します:

```ts title="src/pages/_api/api/openapi/[id].ts"
import type { ApiContext } from 'waku/router';
import { openapi } from '@/lib/openapi';

export async function GET(_: Request, { params }: ApiContext<'/api/openapi/[id]'>) {
  return Response.json((await openapi.getSchema(params.id)).bundled);
}
```

* `loadDocument` は生成された MDX ファイルでも使用できます。
* [Fumadocs CLI でインストールした UI](/docs/waku/integrations/openapi/api-page#full-ui) では、`loadDocument` を `createOpenAPIRenderer()` に渡してください。
* ブラウザは各ドキュメントをリロードまで保持します。`disableCache` を使用する場合は、`includeDocument` を有効にしたままにしてください。

## プロキシの作成 [#creating-proxy]

プロキシサーバーは、ブラウザの CORS 制限を受けずに HTTP リクエストを送信します。API プレイグラウンドでは、次の用途でプロキシを使用します。

* CORS で自分のオリジンが許可されていない API エンドポイントへのリクエスト。
* コンフィデンシャルクライアント (クライアントシークレットを使用) の OAuth リクエストおよびデバイスフロー。

<Callout type="warn">
プロキシは認証なしで公開されます。`Authorization` を含め、リクエストのヘッダーとボディはそのまま転送されます。[`allowedOrigins`](#allowed-origins) と `filterRequest` でアクセスを制限してください。
</Callout>

### セットアップ [#setup]

プロキシ用のルートハンドラーを作成します。

```ts title="src/pages/_api/api/proxy.ts"
import { openapi } from '@/lib/openapi';

export const { GET, HEAD, PUT, POST, PATCH, DELETE } = openapi.createProxy();
```

次に、`createOpenAPI()` でこのルートの URL を設定します。

```ts title="lib/openapi.ts"
import { createOpenAPI } from 'fumadocs-openapi/server';

export const openapi = createOpenAPI({
  proxyUrl: '/api/proxy', // [!code ++]
});
```

### 許可するオリジン [#allowed-origins]

デフォルトでは、プロキシはあらゆるパブリックなオリジンを受け付けますが、プライベートネットワーク上のホスト (例: `localhost`) は拒否し、サイトの Cookie も転送しません。
プロキシの対象を制限したい場合や、プライベートな API をプロキシしたい場合は、プロキシのルートで許可するオリジンを指定してください。

```ts
openapi.createProxy({
  // 文字列または正規表現
  allowedOrigins: ['https://api.example.com', 'http://localhost:8080'],
});
```

リダイレクト先も同じルールでチェックされます。

<Callout type="warn">
プライベートネットワークの拒否には Node.js または Deno が必要です。Bun などの他のランタイムでは、
`allowedOrigins` を指定してください。
</Callout>

リクエストを拒否するには `filterRequest` を使用します。たとえば、読み取りのみを許可するには次のようにします。

```ts
openapi.createProxy({
  // false を返したリクエストはプロキシされずに拒否されます
  filterRequest: (request) => request.method === 'GET',
});
```

## OAuth リダイレクト [#oauth-redirect]

OAuth プロバイダーは登録済みのリダイレクト URI しか受け付けませんが、API プレイグラウンドはデフォルトで現在のページにリダイレクトします。
そこで、全ページ共通のリダイレクト URI となるルートハンドラーを作成してください。このハンドラーは、フローを開始したページへユーザーを戻します。

```ts title="src/pages/_api/api/oauth.ts"
import { openapi } from '@/lib/openapi';

export const GET = openapi.createOAuthHandler();
```

このルートの URL を `createOpenAPIPage()` に渡し、完全な URL(例: `https://docs.example.com/api/oauth`)を OAuth プロバイダーに登録してください。

```tsx title="components/api-page.tsx"
export const OpenAPIPage = createOpenAPIPage({
  oauthRedirectUrl: '/api/oauth', // [!code ++]
});
```

<Callout title="Content Security Policy">
ハンドラーは、自身のルートから配信されるスクリプトを使ってユーザーを元のページに戻します。このルートに Content Security Policy が適用される場合は、`'self'` のスクリプトを許可する必要があります。`'strict-dynamic'` を含むポリシーでは `'self'` が無視されるため、代わりにこのルートをポリシーの対象から除外してください。
</Callout>

### 静的サイト [#static-sites]

静的サイトでは、ルートはファイルとしてプリレンダリングされます。`inline: true` を渡してスクリプトをページ内に埋め込み、静的ホストがファイルを HTML として配信できるよう、パスの末尾を `.html` にしてください。

```ts title="src/pages/_api/api/oauth.html.ts"
import { openapi } from '@/lib/openapi';

export const GET = openapi.createOAuthHandler({ inline: true });

export async function getConfig() {
  return { render: 'static' } as const;
}
```

`oauthRedirectUrl: '/api/oauth.html'` のように、`.html` の URL をリダイレクト URI として指定してください。スクリプトがページ内に埋め込まれるため、このルートに適用される Content Security Policy でインラインスクリプトを許可する必要があります。