コンテンツにスキップ

Vendure開発ガイド

このドキュメントは、Vendure公式ドキュメントを基に、プロジェクト内で開発に必要な主要情報をまとめた包括的な開発ガイドです。

より深い技術詳細が必要な場合: Vendureのアーキテクチャ、コアコンセプト、高度な実装パターンについては Vendure開発ハンドブック を参照してください。

目次

Vendureは2つのGraphQL APIエンドポイントを提供します:

  • Shop API (/shop-api): ストアフロント(顧客向け)用のAPI
  • Admin API (/admin-api): 管理画面(管理者向け)用のAPI

両方のAPIは同じVendureサーバーで動作し、異なる認証・認可ルールを適用します。

プラグインシステム

Vendureはプラグインアーキテクチャを採用しており、すべての機能はプラグインとして実装されます。

@VendurePlugin({
  imports: [PluginCommonModule],
  providers: [MyService],
  shopApiExtensions: {
    resolvers: [MyResolver],
    schema: myGraphqlSchema,
  },
})
export class MyPlugin {}

プラグイン開発の実装手順: 詳細な実装ガイドはプラグイン開発の基礎を参照してください。

データモデル

Vendureの主要なエンティティ:

  • Product: 商品情報(複数のVariantを持つ)
  • ProductVariant: 商品のバリエーション(SKU、価格、在庫)
  • Customer: 顧客情報
  • Order: 注文情報
  • Channel: チャネル(マルチテナント対応)
  • Collection: 商品コレクション(カテゴリ)

コレクション / ファセット / custom field の運用ルール(標準)

  • 役割分担
  • Collection: ストアフロントのカテゴリ / ナビゲーション階層 / 一覧ページ。Brand page、brand 配下 menu、campaign 導線をここで表現する。
  • Facet: 属性・絞り込み・内部ルールの基礎。Brand は brand Facet の FacetValue として保持する。
  • custom field: 数値・複雑条件・計算専用値。Facet に載せにくい詳細業務条件に使う。
  • 標準パターン
  • brand / product-type / customer-visibility / shipping-mode などの Facet を定義する。
  • Product に FacetValue を付与する(Brand は通常 Product 単位)。
  • Brand page は facet-value-filterbrand=<facet value> を条件にした Collection として作る。
  • Brand 配下 menu は親 filter 継承に追加 Facet 条件を組み合わせて表現する。
  • この案件での基本配置
  • Facet: brand, product-type, customer-visibility, shipping-mode、必要に応じて consent-required
  • Collection: brands 親 collection、各ブランド Collection、ブランド配下の professional / retail / set / promotion などの menu Collection、campaigns 配下の導線。
  • custom field: minOrderQty, 請求書要否、送料ルールキー、価格計算用の詳細条件など。
  • 命名規約
  • Facet code / Facet value code / custom field key / collection slug は ASCII に統一する。
  • 画面表示名は日本語でよい。
  • Dashboard 表示
  • Vendure Dashboard 上で表示する custom field のうち、SMILE が SSoT で Vendure 上では編集禁止 (readonly) または編集非推奨 (インポートのみ運用) の項目には、 日本語ラベルの先頭に (U+263A) を付ける。
  • は運用者向けの「この値は SMILE 側で管理しており Vendure では触らない」目印で あり、保存値・field key・GraphQL schema 名には含めない。helper (markSmileSourcedLabel / markSmileSourcedCustomFieldLabel) 経由での付与を 推奨する。
  • Vendure 上で通常通り編集する想定の項目(SMILE を参照していても運用上 Vendure 側で書き換える前提のもの)には付けない。
  • 自作 extension / plugin 由来の項目は marker を別軸で付与する ( と両立する場合は ☺◇ ラベル の順)。
  • 運用ポイント
  • Brand のデータ真実は brand FacetValue とし、Collection はそのページ/導線表現として扱う。
  • facet-value-filter は Brand Collection とその子 Collection を構成する標準手段として使う。
  • 顧客別の表示制御は、Collection 対象と customer 系 Facet / custom field を plugin / policy 側で組み合わせて判定する。
  • Product.customFields.brandCode は互換維持や内部連携の補助値であり、Brand の正本にはしない。
  • 予約商品は facet ではなく ProductVariant.customFields を正本とする。予約種別、送料方針、出荷予定日、releaseGroupKey は reservation custom field で管理し、受注分離は seller strategy で解決する。
  • 大量インポート時は setApplyAllFiltersOnProductUpdates(false) で一時停止→インポート→triggerApplyFiltersJob で再適用すると高速。
  • 詳細な境界線は docs/specifications/2026-03-collection-facet-boundary.md を正本とする。

チャネルとマルチテナント

Vendureはチャネルベースのマルチテナントアーキテクチャをサポートしています。各チャネルは独立した商品カタログ、価格設定、在庫管理を持てます。

詳細情報: チャネルとゾーンの技術的な詳細については Vendureコアコンセプト詳細解説 を参照してください。

認証・認可システム

  • 認証: CookieまたはBearer Tokenによる認証
  • 認可: Role-Based Access Control (RBAC)による権限制御
  • Shop API: 顧客認証(Customer)
  • Admin API: 管理者認証(Administrator)

開発環境セットアップ

注意: 本リポジトリでは原則として .env / .env.local を使用しません。最新のローカル開発手順は apps/vendure-server/local-development.md を正として参照してください。

前提条件

  • Node.js / pnpm: mise.tomlpackage.json#engines を正とする
  • 依存サービス: just dev-services(PostgreSQL/Redis/Mailpit 等)

ローカル開発環境の構築

1. 依存関係のインストール

# プロジェクトルートから
pnpm install

2. 環境変数の考え方

  • 機密情報: just 経由で AWS Secrets Managerjust dev は内部で with-env.sh を呼び出し、最新のシークレットを自動注入します。
  • 非機密(例: ポート): 起動コマンドで PORT=3022 ... のように一時的に上書き。
  • portless による名前解決: ローカル開発では named.localhost(例: vendure.localhost:<worktree-proxy-port>)を使用します。ポート番号を意識せずにサービス間通信ができるよう、DATABASE_URLVENDURE_BASE_URLjust dev 起動時にSSOT(Single Source of Truth)として構成されます。

ストアフロントの画像ホスト許可設定は VENDURE_BASE_URL(推奨)または VITE_PUBLIC_ASSET_HOSTS を利用します。詳細は apps/storefront/AGENTS.md の「Storefront 画像配信設定」を参照してください。

3. 開発サーバーの起動

# Vendureサーバーの起動(SSM経由、ホットリロード対応)

機密情報をロードして起動するために **just 経由**で AWS Secrets Manager を使用します。

```bash
# Vendure 開発用の設定を使う
just dev-vendure
```

```bash
# 依存サービスだけ先に上げたい場合に使う
just dev-services

既定の dev_vendure / local-vendure では、ローカル PostgreSQL (127.0.0.1:5433) が未起動なら just dev-services 相当を自動実行します。staging_vendure など別 config を使う場合は自動起動しません。

dev_vendure では SUPERADMIN_USERNAME=ec-admin / SUPERADMIN_PASSWORD=devPassword!123 を dev/test 用の既定値として許容します。本番相当環境では引き続き fail-fast のままです。

React Dashboard を別ターミナルで起動する場合は just dev-dashboard を使い、公開 URL は just ports で確認します。Dashboard 側の /admin-api proxy は VENDURE_BASE_URL を正本にするため、固定 localhost と portless を切り替えた場合は、この値が Vendure の実 URL と一致していることを確認してください。

Docker依存サービスも含めて起動

just dev-full

補足: just dev-vendure / just local-vendure / just dev-full / just local-full 実行時は packages/pluginstsc -w を自動起動するため、プラグイン変更が即時反映されます。

本番相当のビルドで確認

pnpm run build pnpm run start

##### 5. Dashboardのビルド(初回のみ)

```bash
cd apps/vendure-server
pnpm exec nx run ritsubi-vendure-server:dashboard:build

注意: pnpm + monorepo環境では、vite.config.mtstempCompilationDir を指定する必要があります。詳細は apps/vendure-server/AGENTS.md の Vite 設定セクションを参照してください。

アクセスURL

既定の CLI 開発導線では portless を使用します。<worktree-proxy-port> の実値は just ports で確認してください。

  • Shop API: http://vendure.localhost:<worktree-proxy-port>/shop-api
  • Admin API: http://vendure.localhost:<worktree-proxy-port>/admin-api
  • React Dashboard: http://dashboard.localhost:<worktree-proxy-port>/
  • GraphiQL: http://vendure.localhost:<worktree-proxy-port>/admin-api (ブラウザで開くとGraphiQLが表示)

固定 localhost ポートで確認したい場合のみ、PORTLESS=0 just dev-vendure / PORTLESS=0 just dev-dashboard または個別の dev コマンドを使います。

プラグインの構造

packages/plugins/my-plugin/
├── src/
│   ├── index.ts              # プラグイン定義
│   ├── entities/             # カスタムエンティティ
│   ├── services/             # ビジネスロジック
│   ├── api/                  # GraphQL Resolver
│   └── types.ts              # 型定義
└── package.json

プラグインの実装例

import { PluginCommonModule, VendurePlugin } from "@vendure/core";
import { gql } from "graphql-tag";
import { MyResolver } from "./api/my.resolver";
import { MyService } from "./services/my.service";

const myGraphqlSchema = gql`
  extend type Query {
    myQuery: String!
  }

  extend type Mutation {
    myMutation(input: String!): Boolean!
  }
`;

@VendurePlugin({
  imports: [PluginCommonModule],
  providers: [MyService],
  shopApiExtensions: {
    resolvers: [MyResolver],
    schema: myGraphqlSchema,
  },
})
export class MyPlugin {}

詳細は プラグイン実装ガイド を参照してください。

GraphQL APIの拡張

Resolverの実装

import { Resolver, Query, Mutation, Args } from "@nestjs/graphql";
import { Ctx, RequestContext } from "@vendure/core";

@Resolver()
export class MyResolver {
  constructor(private myService: MyService) {}

  @Query()
  async myQuery(@Ctx() ctx: RequestContext): Promise<string> {
    return this.myService.getData(ctx);
  }

  @Mutation()
  async myMutation(@Ctx() ctx: RequestContext, @Args("input") input: string): Promise<boolean> {
    return this.myService.processData(ctx, input);
  }
}

既存タイプの拡張

const schema = gql`
  extend type Product {
    customField: String!
  }

  extend type Query {
    customProductQuery(id: ID!): Product!
  }
`;

Admin API schema 変更時の生成物更新

  • packages/plugins 側で Admin API の schema / mutation / type を変更したら、tracked 生成物の差分も確認する。
  • このリポジトリでは少なくとも以下が更新対象になりうる。
  • packages/contract/schema/admin.graphql
  • apps/vendure-server/src/gql/graphql-env.d.ts
  • just dashboard-build や commit hook の codegen-smart で後から差分が出ることがあるため、機能本体をコミットした後でも git status を再確認する。
  • 直近の実例では、SMILE import rollback の GraphQL 拡張後に packages/contract/schema/admin.graphql の追加入力が必要になった。

データベースマイグレーション

運用方針(TypeORM)

  • Vendure公式が推奨する標準手段は TypeORM マイグレーションnpx vendure migrate で生成・実行・ロールバックを行い、本番では synchronize を無効化false)する。参照: Vendure Migrations ガイド
  • 本リポジトリでは下記スクリプトに統一(Vendure CLI を内部で呼び出し)。環境に応じて利用すること。

マイグレーションの生成

# 生成後に timestamp 正規化 / lint / data guard まで実行
pnpm exec nx run ritsubi-vendure-server:migrate:generate -- --name=my-migration
  • migrate:generate は生成物を 13 桁 epoch-ms timestamp へ正規化します。 AI / 手作業で 14 桁 YYYYMMDDHHmmss の migration file を作らないでください。 手書きが必要な drop / backfill / hand-written migration の場合も 13 桁 unix-ms (例: 1780100500000_my_change.ts / class MyChange1780100500000) で揃え、 サンプルや例示でも 14 桁形式を書かないでください。directory に残る既存 14 桁 file は legacy grandfather なので、それを見て pattern-match して真似しないでください。
  • migrate:review:latest で最新の未コミット migration だけを再 review できます。
  • VENDURE_SYNCHRONIZE=true は development/test 用です。共有環境では通常の migration フローを使い、空の DB 初期化時のみ ALLOW_SCHEMA_SYNC=true を明示します。
  • 既存スキーマがある環境では、ALLOW_SCHEMA_SYNC=true でも「pending migration がない」だけで自動 synchronize は実行しません。ローカルでスキーマを作り直す場合は db:reset:clean / db:reset:dry-run を使います。

スキーマ変更の設計指針

  • customFields: Vendure core entity に少数の安定した列を追加したいときに使う。追加後は生成 migration を鵜呑みにせず、必要な customFields... カラムが入っているか必ず確認する。
  • JSONB / シリアライズ済み payload: 形が頻繁に変わる、問い合わせ要件が弱い、短期的に試したい属性を載せる。
  • 独自テーブル / カスタムエンティティ: 検索・結合・制約・履歴管理が必要なもの、rename / 型変更が多く migration を壊しやすいものに使う。

rename・型変更・大量の add/drop が絡む変更は、自動生成に任せず手書き migration を前提にします。

マイグレーションの実行

# 開発環境
pnpm exec nx run ritsubi-vendure-server:migrate:run

# 本番環境
pnpm run migrate

詳細は 開発ガイド を参照してください。

カスタムエンティティの追加

import { Entity, Column, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class MyCustomEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;
}

プラグインでエンティティを登録:

@VendurePlugin({
  entities: [MyCustomEntity],
})
export class MyPlugin {}

イベントシステムの活用

Vendureは豊富なイベントを提供します:

import { EventBus, OrderPlacedEvent } from "@vendure/core";

@Injectable()
export class MyService {
  constructor(private eventBus: EventBus) {
    // イベントの購読
    this.eventBus.ofType(OrderPlacedEvent).subscribe((event) => {
      console.log("Order placed:", event.entity.id);
    });
  }
}

Vendure設定

vendure-config.tsの主要設定項目

設定ファイル: apps/vendure-server/src/vendure-config.ts

基本設定

export const config: VendureConfig = {
  // API設定
  apiOptions: {
    port: 3021,
    adminApiPath: "admin-api",
    shopApiPath: "shop-api",
    cors: {
      origin: true,
      credentials: true,
    },
  },

  // 認証設定
  authOptions: {
    tokenMethod: ["bearer", "cookie"],
    cookieOptions: {
      secret: "cookie-secret",
      httpOnly: true,
      secure: false,
      sameSite: "lax",
    },
  },

  // データベース設定
  dbConnectionOptions: {
    type: "postgres",
    host: "localhost",
    port: 5433,
    database: "ritsubi_vendure_dev",
    synchronize: false, // 本番ではfalse
  },

  // デフォルト言語
  defaultLanguageCode: LanguageCode.ja,
};

プラグインの初期化

plugins: [
  // React Dashboard
  DashboardPlugin.init({
    route: 'dashboard',
    appDir: join(__dirname, '../dist/dashboard'),
  }),

  // Asset Server
  AssetServerPlugin.init({
    route: 'assets',
    assetUploadDir: join(__dirname, '../static/assets'),
    // shop API は R2 custom domain、admin API は same-server /assets/ を返す
    // 詳細は object-storage.md を参照
    assetUrlPrefix: (ctx) =>
      ctx.apiType === 'shop' && process.env.VENDURE_ASSET_URL
        ? process.env.VENDURE_ASSET_URL
        : process.env.VENDURE_BASE_URL
          ? `${process.env.VENDURE_BASE_URL}/assets/`
          : undefined,
  }),

  // カスタムプラグイン
  MyPlugin.init({ options }),
],

認証設定

Cookie認証

authOptions: {
  tokenMethod: ['cookie'],
  cookieOptions: {
    secret: process.env.COOKIE_SECRET,
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax',
  },
}

Bearer Token認証

authOptions: {
  tokenMethod: ['bearer'],
}

CORS設定

apiOptions: {
  cors: {
    origin: process.env.STOREFRONT_URL || 'http://localhost:4001',
    credentials: true,
  },

}

API開発

GraphQLスキーマの理解

VendureのGraphQLスキーマは自動生成されます。スキーマを確認するには:

  1. GraphiQLで開く:
  2. Portless: http://vendure.localhost:<worktree-proxy-port>/admin-api
  3. localhost: http://localhost:${VENDURE_PORT:-3021}/admin-api
  4. スキーマ生成コマンドを実行:
pnpm run schema:generate

生成されたスキーマは packages/contract/schema/ に保存されます。

GraphiQLの使用方法

GraphiQLはVendureに標準搭載されています。

アクセス方法

  • Admin API:
  • Portless: http://vendure.localhost:<worktree-proxy-port>/admin-api
  • localhost: http://localhost:${VENDURE_PORT:-3021}/admin-api
  • Shop API:
  • Portless: http://vendure.localhost:<worktree-proxy-port>/shop-api
  • localhost: http://localhost:${VENDURE_PORT:-3021}/shop-api

主要機能

  • 自動補完: Ctrl+Space で利用可能なフィールドを表示
  • ドキュメント: 右側の「Docs」パネルでスキーマを確認
  • クエリ実行: クエリを書いて実行ボタンをクリック

詳細は API ドキュメント・可視化ガイド を参照してください。

クエリとミューテーションの書き方

クエリ例

query GetProducts {
  products {
    items {
      id
      name
      slug
      variants {
        id
        sku
        price
      }
    }
    totalItems
  }
}

ミューテーション例

mutation CreateProduct {
  createProduct(
    input: { translations: [{ languageCode: ja, name: "新商品", slug: "new-product" }] }
  ) {
    id
    name
  }
}

エラーハンドリング

GraphQL APIは標準的なエラーレスポンスを返します:

{
  "errors": [
    {
      "message": "エラーメッセージ",
      "extensions": {
        "code": "ERROR_CODE",
        "exception": {
          "stacktrace": ["..."]
        }
      }
    }
  ],

  "data": null
}

参照リンク

ストアフロント開発

ストアフロント(Vite)の開発については、以下を参照してください:

  • apps/storefront/AGENTS.md: ストアフロント開発ガイドライン
  • コンポーネント管理ルール
  • スタイリング規約(Tailwind CSS v4 + shadcn/ui)
  • UIプレビュー運用ルール

サーバー開発

Vendureサーバーの開発については、以下を参照してください:

  • apps/vendure-server/AGENTS.md: サーバー開発ガイドライン
  • Dashboard Plugin設定
  • 日本語化(国際化)手順
  • Dashboard拡張の開発

プラグイン実装

カスタムプラグインの実装については、以下を参照してください:

システムアーキテクチャ

インフラストラクチャとシステムアーキテクチャについては、以下を参照してください:

カート投入成功時の通知仕様

  • 表示タイミング: Vendure の addItemToOrderOrder 型で成功し、CustomerProvider.refreshCustomer() が完了した後に toast.success("カートに追加しました") を表示する。API が ErrorResult を返す場合や、同意未完了などで失敗した場合は成功 toast を出さず、既存のエラー toast / 同意モーダルへフォールバックする。
  • レイアウト: カート投入後に sheet / drawer / modal を自動表示しない。ユーザーは商品詳細・一覧・クイックオーダー画面に留まり、カート内容確認はヘッダーのカート導線または /cart で行う。
  • 操作: 成功後も activeOrderrefreshCustomer() で再取得し、ヘッダーやカートページの数量・合計は server 権威値へ同期する。

実装箇所: apps/storefront/src/components/product/add-to-cart-button.tsx

その他のリファレンス

データ投入・シード

  • 開発時シード: pnpm exec nx run ritsubi-vendure-server:db:seed:local(dist/node 実行)
  • 本番相当: pnpm exec nx run ritsubi-vendure-server:db:seed(distビルド後)
  • クリーンリセット: pnpm exec nx run ritsubi-vendure-server:db:reset:clean(スキーマ再作成+マイグレーション+シード)
  • ドライ整合性チェック(DB書き込みなし)
    pnpm exec nx run ritsubi-vendure-server:seed:validate -- --products ./apps/vendure-server/src/data/products.csv
  • defaultZone / zones / countries / taxCategories / products.csvヘッダーと必須値 / 数値フィールド / SKU・slug重複に加え、Vendure公式 ImportParser で必須列・翻訳列・カスタムフィールドも検証し、問題があれば exit code 1
  • --json で JSON 出力可能
  • 詳細は vendure-seed-data-guide.md を参照

更新日: 2025-01-XX
メンテナー: Ritsubi開発チーム