mcpField
M
magento-graphql

magento-graphql

github.com/finsilabs開発6079 回インストール5 日前に更新
リポジトリ

magento-graphql について

ヘッドレスストアやアプリでMagento 2とAdobe CommerceのGraphQLを利用する開発スキルです。Magentoを基盤に開発するチーム向けに、商品検索、カートや顧客操作、カスタムリゾルバーを扱います。

主な機能

  1. 01GraphQLクライアントの設定
  2. 02商品カタログのクエリ
  3. 03ゲストカート操作
  4. 04顧客認証
  5. 05カスタムリゾルバー開発

活用シーン

  1. 01ヘッドレスMagentoストアの構築
  2. 02モバイルアプリでのMagentoデータ利用
  3. 03カートとチェックアウトの管理
  4. 04カスタムモジュールデータの公開

インストール

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

$npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill magento-graphql

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

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

tile.json
evals/cart-lifecycle-and-customer-authenticati/
criteria.json
task.md
evals/graphql-client-setup-and-auth-security/
criteria.json
task.md
evals/product-catalog-queries-with-polymorphic/
criteria.json
task.md

詳細

Magento GraphQL API

概要

Magento 2(および Adobe Commerce)には、ストアフロントでの操作に対応する包括的な GraphQL API が標準搭載されています。商品カタログ、検索、カート、チェックアウト、顧客認証、注文履歴などを扱えます。エンドポイントは /graphql で、公開カタログデータの取得に管理者トークンは必要ありません。カートや顧客関連の操作には、ゲストカート ID(UUID)または顧客ログインで取得した Bearer トークンが必要です。PWA Studio(Venia)はこの API のみを使って構築されているため、ヘッドレス Magento のリファレンス実装となっています。

このスキルを使用する場面

  • Magento 2 で React、Vue、または Next.js を使ったヘッドレスストアフロントを構築するとき
  • Magento の商品データやチェックアウトデータを必要とするネイティブモバイルアプリを作成するとき
  • GraphQL リゾルバーを使ってカスタムデータを取得する PWA Studio 拡張機能を実装するとき
  • 既存のヘッドレスプロジェクトで、REST API 呼び出しをより効率的な GraphQL クエリに置き換えるとき
  • サードパーティーやカスタムモジュールのデータを公開するカスタム GraphQL リゾルバーを構築するとき
  • ヘッドレス CMS を Magento の商品カタログと連携するとき

基本的な手順

  1. GraphQL クライアントをセットアップする

    Magento GraphQL では、標準の HTTP POST を使って /graphql にリクエストを送信します。Apollo Client または軽量な fetch ラッパーを使用します。

    // lib/magento.ts
    const MAGENTO_URL = process.env.NEXT_PUBLIC_MAGENTO_URL; // e.g. https://magento.example.com
    
    export async function magentoQuery<T>(
      query: string,
      variables: Record<string, unknown> = {},
      token?: string
    ): Promise<T> {
      const headers: Record<string, string> = {
        "Content-Type": "application/json",
        Store: process.env.NEXT_PUBLIC_MAGENTO_STORE_CODE ?? "default",
      };
    
      if (token) {
        headers["Authorization"] = `Bearer ${token}`;
      }
    
      const response = await fetch(`${MAGENTO_URL}/graphql`, {
        method: "POST",
        headers,
        body: JSON.stringify({ query, variables }),
        next: { revalidate: 300 }, // Next.js ISR caching
      });
    
      if (!response.ok) {
        throw new Error(`Magento GraphQL HTTP error: ${response.status}`);
      }
    
      const { data, errors } = await response.json();
      if (errors?.length) {
        throw new Error(errors[0].message);
      }
      return data as T;
    }
    
  2. 商品カタログをクエリする

    // Fetch products by category UID or search
    export async function getProducts(params: {
      categoryUid?: string;
      search?: string;
      pageSize?: number;
      currentPage?: number;
    }) {
      return magentoQuery<{ products: MagentoProductList }>(`
        query GetProducts(
          $search: String
          $filter: ProductAttributeFilterInput
          $pageSize: Int
          $currentPage: Int
        ) {
          products(
            search: $search
            filter: $filter
            pageSize: $pageSize
            currentPage: $currentPage
            sort: { position: ASC }
          ) {
            total_count
            page_info { current_page page_size total_pages }
            aggregations {
              attribute_code label count
              options { label value count }
            }
            items {
              uid sku name url_key
              ... on SimpleProduct {
                price_range {
                  minimum_price {
                    regular_price { value currency }
                    final_price { value currency }
                    discount { percent_off amount_off }
                  }
                }
              }
              ... on ConfigurableProduct {
                configurable_options {
                  attribute_code label
                  values { uid label swatch_data { value } }
                }
                variants {
                  attributes { uid label code value_index }
                  product { sku stock_status price_range { minimum_price { final_price { value } } } }
                }
              }
              small_image { url label }
              thumbnail { url label }
              rating_summary review_count
              stock_status
            }
          }
        }
      `, {
        search: params.search,
        filter: params.categoryUid ? { category_uid: { eq: params.categoryUid } } : undefined,
        pageSize: params.pageSize ?? 20,
        currentPage: params.currentPage ?? 1,
      });
    }
    
  3. ゲストカートを作成して商品を追加する

    // Create a guest cart and get the cart ID
    export async function createGuestCart(): Promise<string> {
      const data = await magentoQuery<{ createEmptyCart: string }>(`
        mutation { createEmptyCart }
      `);
      return data.createEmptyCart; // Returns a UUID cart ID
    }
    
    // Add a simple product to cart
    export async function addSimpleProductToCart(cartId: string, sku: string, qty: number) {
      return magentoQuery(`
        mutation AddSimpleProduct($cartId: String!, $sku: String!, $qty: Float!) {
          addSimpleProductsToCart(
            input: {
              cart_id: $cartId
              cart_items: [{ data: { sku: $sku, quantity: $qty } }]
            }
          ) {
            cart {
              items {
                uid quantity
                product { name sku }
                prices { price { value currency } }
              }
              prices {
                subtotal_excluding_tax { value currency }
                grand_total { value currency }
              }
            }
          }
        }
      `, { cartId, sku, qty });
    }
    
    // Add a configurable product with selected variant
    export async function addConfigurableProductToCart(
      cartId: string,
      parentSku: string,
      variantSku: string,
      qty: number
    ) {
      return magentoQuery(`
        mutation AddConfigurable($cartId: String!, $parentSku: String!, $variantSku: String!, $qty: Float!) {
          addConfigurableProductsToCart(
            input: {
              cart_id: $cartId
              cart_items: [{
                parent_sku: $parentSku
                data: { sku: $variantSku, quantity: $qty }
              }]
            }
          ) {
            cart {
              items { uid quantity product { name } }
            }
          }
        }
      `, { cartId, parentSku, variantSku, qty });
    }
    
  4. 顧客を認証してアカウントを管理する

    // Customer login — returns a bearer token
    export async function loginCustomer(email: string, password: string): Promise<string> {
      const data = await magentoQuery<{ generateCustomerToken: { token: string } }>(`
        mutation Login($email: String!, $password: String!) {
          generateCustomerToken(email: $email, password: $password) {
            token
          }
        }
      `, { email, password });
      return data.generateCustomerToken.token;
    }
    
    // Get authenticated customer's cart
    export async function getCustomerCart(token: string) {
      return magentoQuery(`
        query {
          customerCart {
            id
            items {
              uid quantity
              product { name sku small_image { url } }
              prices { price { value currency } }
            }
            prices { grand_total { value currency } }
          }
        }
      `, {}, token);
    }
    
    // Get order history
    export async function getCustomerOrders(token: string, pageSize = 10) {
      return magentoQuery(`
        query GetOrders($pageSize: Int) {
          customer {
            orders(pageSize: $pageSize, sort: { sort_field: CREATED_AT, sort_direction: DESC }) {
              total_count
              items {
                id number status order_date
                total { grand_total { value currency } }
                items { product_name product_sku quantity_ordered }
                shipping_address { firstname lastname city region { code } country_code }
              }
            }
          }
        }
      `, { pageSize }, token);
    }
    
  5. Magento 2 モジュールにカスタム GraphQL リゾルバーを作成する

    <?php
    // app/code/MyVendor/CustomGraphQL/etc/schema.graphqls
    
    type Query {
        myCustomProducts(
            brand: String @doc(description: "Filter by brand")
        ): MyCustomProductOutput @resolver(class: "MyVendor\\CustomGraphQL\\Model\\Resolver\\CustomProducts") @doc(description: "Get custom product list")
    }
    
    type MyCustomProductOutput {
        items: [CustomProductItem]
        total_count: Int
    }
    
    type CustomProductItem {
        sku: String
        name: String
        brand: String
        custom_attribute: String
    }
    
    <?php
    // app/code/MyVendor/CustomGraphQL/Model/Resolver/CustomProducts.php
    namespace MyVendor\CustomGraphQL\Model\Resolver;
    
    use Magento\Framework\GraphQl\Config\Element\Field;
    use Magento\Framework\GraphQl\Query\ResolverInterface;
    use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
    use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
    
    class CustomProducts implements ResolverInterface
    {
        public function __construct(
            private readonly CollectionFactory $collectionFactory
        ) {}
    
        public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
        {
            $collection = $this->collectionFactory->create();
            $collection->addAttributeToSelect(['sku', 'name', 'brand', 'custom_attribute']);
            $collection->addAttributeToFilter('status', 1);
            $collection->addAttributeToFilter('visibility', ['neq' => 1]);
    
            if (!empty($args['brand'])) {
                $collection->addAttributeToFilter('brand', ['like' => '%' . $args['brand'] . '%']);
            }
    
            $items = [];
            foreach ($collection as $product) {
                $items[] = [
                    'sku'              => $product->getSku(),
                    'name'             => $product->getName(),
                    'brand'            => $product->getData('brand'),
                    'custom_attribute' => $product->getData('custom_attribute'),
                ];
            }
    
            return ['items' => $items, 'total_count' => $collection->getSize()];
        }
    }
    

例

商品詳細ページ — スウォッチ付きの設定可能な商品
export async function getProductByUrlKey(urlKey: string) {
  return magentoQuery<{ products: { items: MagentoProduct[] } }>(`
    query GetProduct($urlKey: String!) {
      products(filter: { url_key: { eq: $urlKey } }) {
        items {
          uid sku name url_key
          meta_title meta_description
          description { html }
          short_description { html }
          ... on ConfigurableProduct {
            configurable_options {
              id uid label attribute_code position
              values {
                uid label
                swatch_data {
                  ... on ColorSwatchData { value }
                  ... on ImageSwatchData { thumbnail value }
                  ... on TextSwatchData { value }
                }
              }
            }
            variants {
              attributes { uid label code value_index }
              product {
                uid sku stock_status
                media_gallery { url label disabled }
                price_range {
                  minimum_price {
                    regular_price { value currency }
                    final_price { value currency }
                  }
                }
              }
            }
          }
          media_gallery { url label disabled position }
          reviews(pageSize: 5) {
            items {
              summary text created_at
              ratings_breakdown { name value }
              nickname
            }
          }
        }
      }
    }
  `, { urlKey });
}
チェックアウトフロー — 配送先住所を設定して注文する
export async function setShippingAddress(cartId: string, address: AddressInput) {
  return magentoQuery(`
    mutation SetShipping($cartId: String!, $address: CartAddressInput!) {
      setShippingAddressesOnCart(input: {
        cart_id: $cartId
        shipping_addresses: [{ address: $address }]
      }) {
        cart {
          shipping_addresses {
            available_shipping_methods {
              carrier_code method_code carrier_title method_title
              amount { value currency }
            }
          }
        }
      }
    }
  `, { cartId, address });
}

export async function placeOrder(cartId: string) {
  return magentoQuery<{ placeOrder: { order: { order_number: string } } }>(`
    mutation PlaceOrder($cartId: String!) {
      placeOrder(input: { cart_id: $cartId }) {
        order { order_number }
        errors { message code }
      }
    }
  `, { cartId });
}

ベストプラクティス

  • Store HTTP ヘッダーを使用して特定のストアビューを指定します。指定しない場合、Magento はデフォルトのストアを使用するため、複数のストアを設定している環境では価格や通貨が正しく返されません
  • Varnish または組み込みキャッシュを使って Magento の GraphQL クエリキャッシュを有効にします。公開カタログのクエリを数分間キャッシュすると、PHP の実行を減らせます
  • ポリモーフィックな商品タイプにはインラインフラグメントを使用します。該当する場合は、常に ... on SimpleProduct、... on ConfigurableProduct、... on BundleProduct を含めます
  • カート ID を Cookie に保存します。ゲストカート ID(UUID)は、24 時間操作がないと期限切れになります。ログイン後は mergeCarts ミューテーションを使って顧客カートと統合します
  • 関連するクエリをまとめます。Apollo Client のバッチ処理または手動でのクエリ統合により、商品、カテゴリ、CMS ブロックのデータを同時に必要とするページで RTT を減らせます
  • errors 配列を適切に処理します。GraphQL はエラーが発生しても 200 を返すため、レスポンス本文の errors プロパティを必ず確認します
  • 顧客トークンを安全にキャッシュします。XSS によるトークン窃取を防ぐため、Bearer トークンは localStorage ではなく httpOnly Cookie に保存します

よくある問題

問題解決策
GraphQL には表示されない商品が管理画面には表示される商品の visibility 属性を確認します。「Not Visible Individually」に設定された商品はカタログクエリに表示されません
設定可能な商品のバリアントで stock_status が空になるvariants.product 内の product フィールドをクエリします。在庫は親商品ではなく、シンプル商品(バリアント)単位で管理されます
カスタムリゾルバーが認識されないGraphQL スキーマファイルを追加した後に bin/magento setup:upgrade を実行します。また、schema.graphqls の構文も慎重に確認してください
顧客ログイン後のカート統合に失敗するmergeCarts(source_cart_id: $guestCartId, destination_cart_id: $customerCartId) を呼び出し、その後ゲストカート ID の Cookie を削除します
GraphQL のレスポンスが遅いMagento の組み込み GraphQL キャッシュを有効にします:bin/magento config:set system/full_page_cache/caching_application 1。公開クエリには Varnish を使用します
セッション中に Bearer トークンの期限が切れるトークンの更新を実装します。The current customer isn't authorized エラーを捕捉し、ログイン画面にリダイレクトするか、利用可能な場合はリフレッシュトークンを使用します

Related Skills

  • @magento-module-development
  • @magento-indexing-caching
  • @magento-multi-store
  • @headless-commerce-architecture
  • @graphql-api-design

コメント