# アーキテクチャ

> 運用者とコントリビューター向けに、Nodaro の仕組みを説明します。エディター、Fastify API、BullMQ ワーカー、Redis、ストレージ、3 種類の認証方式、エディションを取り上げます。

Source: https://nodaro.ai/ja/docs/self-hosting/architecture

Nodaro は、**REST ファーストの AI ワークフローエンジン**です。ユーザーはブラウザーのエディターで、画像や動画の生成、動画の合成、テキストモデル、オーディオといった AI ノードをつないで、グラフを作ります。グラフは Supabase の Postgres に保存されます。グラフを実行すると、Fastify の API がグラフを並べ替え、各ノードの処理を Redis 上のキューのワーカーに渡します。生成はモデルプロバイダーが行い、結果は S3 互換ストレージに保存されます。

同じコードで Community、Business、Cloud の 3 つのエディションを提供し、3 種類のアクセストークンを受け付けます。このページは、Nodaro を運用する方と、Nodaro のコードを変更するコントリビューターに向けたものです。

## システムの全体像
Workflow: クライアントは API を呼び出します。API はデータを Supabase に保存し、実行を Redis のキューに入れます。オーケストレーターは各ワークフローのグラフをたどり、ノードのジョブをワーカーに渡します。ワーカーはモデルプロバイダーを呼び出し、結果をストレージに保存します。

- エディターとアプリ → API
- SDK、CLI、MCP → API
- API → Supabase
- API → Redis
- Redis → オーケストレーター
- オーケストレーター → ワーカー (ノードのジョブ)
- ワーカー → ストレージ

## リポジトリ
公開リポジトリ [github.com/nodaroai/app.nodaro.ai](https://github.com/nodaroai/app.nodaro.ai) は、npm workspaces を使った 1 つのモノレポで、次のフォルダーで構成されています。

| フォルダー | 内容 |
| --- | --- |
| `backend/` | Fastify の API、BullMQ のワーカー、オーケストレーターです。Node.js 22、TypeScript で書かれています。 |
| `frontend/` | ブラウザーで動くアプリです。エディター、アプリランナー、管理パネルが含まれます。React 19、React Router 7、React Flow を使っています。 |
| `packages/` | 共有コード、公開している npm パッケージ `@nodaro/shared`、`@nodaro/sdk`、`@nodaro/cli`、そして Remotion の動画コンポジションです。 |
| `supabase/` | データベースのスキーマです。前方向にだけ適用する SQL マイグレーションとして管理しています。 |
| `docs/` | 製品のドキュメントです。設計メモも含まれます。 |
| `tools/` | 運用者向けのスクリプトです。キーの生成、コントラクトプローブ、バックアップとリストアがあります。 |
| `examples/` | サンプルです。たとえば、SSH 経由でサーバーを更新する GitHub Actions のワークフローがあります。 |

## フロントエンド
フロントエンドは、Vite で作られたシングルページアプリです。Docker イメージでは、Web サーバーの Caddy がこれを静的ファイルとして配信します。1 つのバンドルに、次の 4 つのプロダクトが入っています。

- **エディター**：React Flow のキャンバス、ノードの系統ごとの設定パネル、そして編集中にブラウザーでワークフローを実行するグラフ実行エンジンです。
- **アプリランナー**：公開したワークフローを、入力と結果を並べたシンプルなフォームとして表示します。
- **OAuth 同意画面**：ユーザーがサードパーティのアプリや MCP クライアントを承認する画面で、`/oauth/authorize` にあります。
- **管理パネル**：Business エディションと Cloud エディションにだけあります。

サーバーの状態は React Query、UI の状態は Zustand、キャンバスの状態は React Flow 自身のストアで管理します。

## API
API は、Node.js 22 上で動く Fastify サーバーで、TypeScript で書かれています。ルートは Fastify のプラグインで、すべてのエンドポイントがリクエストとレスポンスのスキーマを宣言します。同じスキーマで各リクエストを検証し、`/v1/openapi.json` の OpenAPI 3.1 ドキュメントも生成します。`GET /v1/nodes` は、その環境で実行できるノードタイプの一覧を返します。

すべてのルートの前に、1 つの認証フックが実行されます。[認証](#authentication)を参照してください。

## プロセスとキュー
バックエンドは、1 つのイメージに含まれる 5 つの Node.js プロセスとして提供されます。

| プロセス | 役割 |
| --- | --- |
| **API サーバー** | HTTP API です。 |
| **メディアワーカー** | キューからノードごとのジョブを受け取り、モデルプロバイダーを呼び出して、結果をストレージにアップロードします。画像や動画の生成、ffmpeg による処理、オーディオなど、40 種類以上のジョブを扱います。処理の大半がネットワーク待ちなので、同時実行数のデフォルトは `50` です。 |
| **レンダーワーカー** | コンポジション、モーショングラフィックス、Lottie オーバーレイ、3D タイトル、コンポジットを描画する Remotion のレンダラーです。ヘッドレス Chrome を動かすため、CPU が処理のボトルネックになります。同時実行数のデフォルトは `2` です。 |
| **オーケストレーター** | ワークフロー全体を実行します。実行を読み込み、そのグラフを並べ替えて、レベルごとに実行します。同時実行数のデフォルトは `20` です。 |
| **パイプラインワーカー** | **ストーリー → 動画**（Story → Video）のパイプラインです。すべてのエディションに含まれますが、動作するのは Nodaro Cloud だけで、それ以外ではすぐに終了します。 |

Docker では、1 つの起動スクリプトがすべてのプロセスを立ち上げ、API の前段には Caddy が立ちます。スケールアウトした環境では、それぞれのプロセスを別々のコンテナで動かせます。プロセス同士の連携は、Redis 上の BullMQ のキューと、Postgres 上の実行レコードだけを通じて行われます。[スケーリング](https://nodaro.ai/docs/self-hosting/scaling)を参照してください。

## ワークフローの実行の流れ
ユーザーがエディターで**実行**をクリックすると、実行の経路は 2 つに分かれます。

1. **ブラウザー**：編集中は、エディター自身がグラフを実行し、ノードごとに API のエンドポイントを 1 回呼び出します。各ノードが終わるとすぐに、その結果が表示されます。
2. **サーバー**：Webhook、スケジュール、API からのトリガーによる実行、アプリの実行、明示的なサーバー実行は、オーケストレーターに送られます。オーケストレーターは進行状況を実行のレコードに保存するので、どのクライアントからでも進行状況を追えます。

オーケストレーターは、次のように動きます。

1. ワークフローのノードと接続を読み込みます。
2. グラフを並べ替えて、レベルに分けます。1 つのレベルには、入力がすべて揃ったノードが入ります。
3. 各レベルのノードを並列に実行します。並列数の上限は、ユーザー自身の並列数と、サーバーの上限 `MAX_CONCURRENT_NODES_PER_EXECUTION` です。
4. 各ノードを、次の 3 つの方法のいずれかで実行します。

| 方法 | 実行場所 | 例 | 理由 |
| --- | --- | --- | --- |
| **キュー経由** | メディアワーカーまたはレンダーワーカーのジョブ | **画像生成**（Generate Image）、**動画生成**（Generate Video）、**テキストから音声**（Text to Speech）、**動画を結合**（Combine Videos）、**動画レンダリング**（Render Video） | 時間のかかる処理や外部への呼び出しです。バックプレッシャーのため、オーケストレーターから切り離します |
| **直接 HTTP** | API ルートへの内部呼び出し | **プロンプト**（Prompt）、**モーショングラフィックス**（Motion Graphics）、プロンプトヘルパー、SNS への投稿ノード | すぐに終わるテキストモデルの呼び出しで、キューのオーバーヘッドがかかりません |
| **インライン** | オーケストレーターの中 | **テキストを結合**（Combine Text）、**テキストを分割**（Split Text）、**コンポジット**（Composite） | 外部への呼び出しがない、純粋なロジックです |

5. ノードが終わるたびに、その出力を依存するノードに渡し、ノードごとに 1 回のデータベース書き込みで実行の進行状況を保存します。

実行が止まる形は 2 つあります。**キャンセル済み**になった実行は、すぐに止まります。実行中のノードは破棄され、すでに請求されたクレジットは返還されません。**停止処理中**の実行は、現在のレベルを最後まで実行し、次のレベルに進む前に止まります。

**上限**：各ノードは最長 90 分、実行全体は最長 120 分かけられます。独自の持ち時間を宣言するノードには、代わりにその持ち時間が与えられ、実行全体の上限も同じだけ延びます。現在これに当たるのは [**EDL 適用**（Apply EDL）](https://nodaro.ai/docs/nodes/video/apply-edl)で、持ち時間はレンダリングする編集の規模から決まります。実行中にオーケストレーターが再起動した場合、持ち時間を持つノードは、そのワーカーがまだ動いていれば、2 回実行されずに再開されます。[**サブワークフロー**（Sub-Workflow）](https://nodaro.ai/docs/nodes/automate/sub-workflow)は、循環を検出しながら、最大 5 階層まで再帰的に実行されます。

## ストレージとデータベース
- **メディア**：生成されたすべての画像、動画、オーディオファイルは S3 互換ストレージに保存され、キーで参照されます。Nodaro が、保存したメディアを自分から削除することはありません。
- **データベース**：Supabase の Postgres には、プロフィール、プロジェクト、ワークフロー、実行とその進行状況、ジョブ、メディアのレコード、クレジットの取引、OAuth アプリとそのトークン、個人用 API トークン、暗号化された SNS の接続情報が保存されます。すべてのテーブルで行レベルセキュリティが有効なので、各ユーザーが自分の行だけを扱えるよう、データベース自体が制限します。
- **マイグレーション**：スキーマは `supabase/migrations/` にあり、ファイル名の順に、前方向にだけ適用されます。
- **Redis**：キューと、小さな共有キャッシュを保持します。Redis のジョブの状態は一時的なものです。[バックアップとリストア](https://nodaro.ai/docs/self-hosting/backups)を参照してください。

## 認証
API は 3 種類のベアラートークンを受け付け、その形式で見分けます。

| トークン | 取得元 | 誰として動作するか |
| --- | --- | --- |
| `eyJ…`（JWT） | エディターでの Supabase のログインセッション | ログイン中のユーザー |
| `ndr_app_<64 hex>` | ユーザーが承認した OAuth アプリ | アプリを承認したユーザー。付与されたスコープの範囲に限られます |
| `ndr_<64 hex>` | 個人用 API トークン | トークンの所有者 |

1 つの環境の中のプロセス同士も、`INTERNAL_ORCHESTRATOR_SECRET` で認証し合います。この値は定数時間で比較されます。Web サーバーは、外部からのリクエストに含まれるこのシークレットのヘッダーを削除します。

API は、リクエストごとに次の順でチェックします。

1. **公開ルート**（`/v1/openapi.json` や Webhook の受信など）は、認証をスキップします。
2. **内部シークレット**：プロセス間の呼び出しに使います。
3. **OAuth アプリのトークン**：取り消されておらず、期限切れでもないことが必要です。
4. **Supabase のセッショントークン**：ユーザーのロールをプロフィールから読み取り、5 分間キャッシュします。
5. **それ以外**は `401` を返します。

個人用 API トークンは、そのトークンを受け付けるルートがチェックします。これらのルートは、レート制限も適用し、トークンで扱えるワークフローを、そのトークンに設定されたものに限定します。[認証](https://nodaro.ai/docs/developers/api/authentication)と [OAuth アプリ](https://nodaro.ai/docs/developers/oauth)を参照してください。

## コード上のエディション
1 つのコードベースで、3 つのエディションすべてを提供します。

- **Community**（`EDITION=community`、デフォルト）：セルフホスティングで運用し、管理パネル、クレジットの台帳、請求はありません。新規登録したユーザーは、全員が一般ユーザーになります。
- **Business**（`EDITION=business`）：管理パネルとユーザー管理が加わります。セルフホスティングで運用し、請求がない点は同じです。
- **Cloud**（`EDITION=cloud`）：クレジット、請求、管理者が設定するクレジット価格が加わります。app.nodaro.ai を動かしているエディションで、セルフホスティング向けではありません。

ルートは、エディションの名前ではなく、エディションが持つ機能をチェックします。管理用のルートは管理パネルのあるエディションでだけ動作し、クレジットのコードはクレジットのあるエディションでだけ動作します。このチェックは、ルートのほかのどの処理よりも先に行われます。ブラウザーのアプリは、ビルド時に `VITE_EDITION` からエディションを読み取ります。[エディションとサーフェスプロファイル](https://nodaro.ai/docs/self-hosting/editions-and-profiles)を参照してください。

## ステータスとストリーミング
ジョブと実行の**ステータス**は、2〜5 秒ごとのポーリングで取得します。ステータスは秒単位で変わるものであり、ポーリングはデプロイが簡単だからです。**ストリーミング出力**（テキストモデルからのテキスト、ワークフローの実行、パイプライン）には、Server-Sent Events を使います。そのため、Nodaro の前段に置くリバースプロキシでは、レスポンスをバッファリングしてはいけません。

## 技術選定の理由
- **Fastify**：TypeScript の型を第一級でサポートし、スキーマに基づく検証を備えています。プラグインによって、各ルートを独立したスコープに保てます。
- **BullMQ**：Redis 上で動く成熟したキューで、リトライ、バックオフ、同時実行数の制御を備えています。同じ Node.js のスタックで動きます。
- **REST**：ルートごとに OAuth のスコープを設定しやすく、OpenAPI ドキュメントを生成でき、サードパーティのクライアントにとってもシンプルなモデルです。
- **Supabase**：Postgres、ログイン、リアルタイム機能、行レベルセキュリティを 1 つのサービスで提供します。行レベルセキュリティのおかげで、独自の認可コードの多くが不要になります。
- **React Flow**：ノードグラフ向けの実績あるキャンバスで、Nodaro はこれを大幅に拡張して使っています。
- **Remotion**：宣言的な React のコンポジションで、サーバーでもエディターのプレビューと同じ結果にレンダリングされます。
- **単一のプロバイダーレイヤー**：モデルの呼び出しはすべて 1 つの抽象化レイヤーを通るため、機能のコードを変えずに、モデルを別のプロバイダーに移せます。

## Frequently asked questions

### Nodaro は何で作られていますか？

フロントエンドは、React Flow のキャンバスを備えた Vite と React 19 のシングルページアプリです。API は Node.js 22 上の Fastify で、TypeScript で書かれています。キューには Redis 上の BullMQ、Postgres とログインには Supabase、メディアには S3 互換ストレージ、動画の合成には Remotion を使います。

### サーバーでは、ワークフローはどのように実行されますか？

オーケストレーターがワークフローを読み込み、そのグラフを並べ替えてレベルに分け、各レベルのノードを同時実行数の上限まで並列に実行します。時間のかかる処理や外部への呼び出しは、Redis を通じてメディアワーカーとレンダーワーカーに渡します。すぐに終わるテキストの呼び出しと純粋なロジックは、直接実行します。

### Nodaro API はどのトークンを受け付けますか？

エディターからの Supabase のセッショントークン、開発者アプリからの OAuth アクセストークン（ndr_app_ で始まります）、個人用 API トークン（ndr_ で始まります）の 3 種類です。どのトークンも 1 人のユーザーに対応し、OAuth トークンはスコープも持ちます。

### 1 回の実行には、どのくらいの時間をかけられますか？

各ノードは最長 90 分、実行全体は最長 120 分です。EDL 適用（Apply EDL）のように独自の持ち時間を宣言するノードには、代わりにその持ち時間が与えられ、実行全体の上限も同じだけ延びます。

### 環境の OpenAPI ドキュメントはどこにありますか？

お使いの環境の /v1/openapi.json にあります。すべてのエンドポイントはスキーマで入力を検証し、同じスキーマから OpenAPI 3.1 ドキュメントが生成されます。GET /v1/nodes は、その環境で実行できるノードタイプの一覧を返します。
