mcpField
S
shopify-admin-graphql

shopify-admin-graphql

github.com/tamiror6開発38106 回インストール38 週間前に更新
リポジトリ

shopify-admin-graphql について

shopify-admin-graphqlは、GraphQL経由でShopify Admin APIを利用するコード向けの開発スキルです。アプリやバックグラウンドジョブで使う認証、一般的なクエリ、ページネーション、スロットリング、エラー処理を扱います。

主な機能

  1. 01Remix認証を通じたAPIアクセス
  2. 02オンライン・オフラインの管理セッション
  3. 03ショップ、商品、顧客、注文のクエリ
  4. 04大規模データセットのページネーション
  5. 05スロットリングとGraphQLエラーの処理
  6. 06GraphQLクエリのベストプラクティス

活用シーン

  1. 01Remix loaderでShopifyデータをクエリ
  2. 02GraphQL mutationでShopifyのレコードを更新
  3. 03リクエストコンテキストなしでWebhookを処理
  4. 04ページネーションとリトライで大量データを取得

インストール

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

$npx skills add https://github.com/tamiror6/shopify-app-skills --skill shopify-admin-graphql

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

詳細

Shopify Admin GraphQL

Shopify Admin API と GraphQL 経由で通信するコードを追加または変更する際は、このスキルを使用してください。

使用する場面

  • Shopify のデータ(ショップ、顧客、注文、商品、在庫)をクエリする場合
  • Shopify のデータ(顧客、注文、商品の作成や更新)を変更する場合
  • 大規模なデータセットのページネーションを実装する場合
  • API のスロットリングやレート制限に対応する場合
  • メタフィールドやその他の Shopify リソースを扱う場合

GraphQL クライアントの取得

Remix のローダー/アクション内(推奨)

@shopify/shopify-app-remix の authenticate.admin() を使用してください。

import { authenticate } from "../shopify.server";

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const { admin } = await authenticate.admin(request);
  
  const response = await admin.graphql(`
    query GetShopDetails {
      shop {
        id
        name
        myshopifyDomain
        plan {
          displayName
        }
      }
    }
  `);
  
  const { data } = await response.json();
  return json({ shop: data.shop });
};
変数を使用する場合
export const action = async ({ request }: ActionFunctionArgs) => {
  const { admin } = await authenticate.admin(request);
  const formData = await request.formData();
  const email = formData.get("email") as string;
  
  // Validate email format to prevent query manipulation
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!email || !emailRegex.test(email)) {
    return json({ error: "Invalid email format" }, { status: 400 });
  }
  
  // Escape quotes and wrap in quotes to treat as literal value
  const sanitizedEmail = email.replace(/"/g, '\\"');
  
  const response = await admin.graphql(`
    query FindCustomerByEmail($query: String!) {
      customers(first: 1, query: $query) {
        edges {
          node {
            id
            email
            phone
            firstName
            lastName
          }
        }
      }
    }
  `, {
    variables: { query: `email:"${sanitizedEmail}"` }
  });
  
  const { data } = await response.json();
  return json({ customer: data.customers.edges[0]?.node });
};
バックグラウンドジョブ/Webhook(オフラインアクセス)

リクエストコンテキストがない場合は、オフラインセッショントークンを使用してください。

import { unauthenticated } from "../shopify.server";

export async function processWebhook(shop: string) {
  const { admin } = await unauthenticated.admin(shop);
  
  const response = await admin.graphql(`
    query GetShop {
      shop {
        name
      }
    }
  `);
  
  const { data } = await response.json();
  return data.shop;
}

よくあるクエリパターン

ショップの詳細
query GetShopDetails {
  shop {
    id
    name
    email
    myshopifyDomain
    primaryDomain {
      url
    }
    plan {
      displayName
    }
    currencyCode
    timezoneAbbreviation
  }
}
ページネーションを使用した商品一覧
query GetProducts($first: Int!, $after: String) {
  products(first: $first, after: $after) {
    pageInfo {
      hasNextPage
      endCursor
    }
    edges {
      node {
        id
        title
        handle
        status
        variants(first: 10) {
          edges {
            node {
              id
              title
              price
              sku
            }
          }
        }
      }
    }
  }
}
顧客の検索
query FindCustomer($query: String!) {
  customers(first: 1, query: $query) {
    edges {
      node {
        id
        email
        phone
        firstName
        lastName
        ordersCount
        totalSpent
      }
    }
  }
}
ID による注文の取得
query GetOrder($id: ID!) {
  order(id: $id) {
    id
    name
    email
    phone
    totalPriceSet {
      shopMoney {
        amount
        currencyCode
      }
    }
    lineItems(first: 50) {
      edges {
        node {
          title
          quantity
          variant {
            id
            sku
          }
        }
      }
    }
    shippingAddress {
      address1
      city
      country
    }
  }
}

スロットリングへの対応

Shopify では、レート制限にリーキーバケットアルゴリズムを使用しています。一括操作やバックグラウンドジョブでは、リトライロジックを実装してください。

interface RetryOptions {
  maxRetries?: number;
  initialDelay?: number;
  maxDelay?: number;
}

async function executeWithRetry<T>(
  admin: AdminApiContext,
  query: string,
  variables?: Record<string, unknown>,
  options: RetryOptions = {}
): Promise<T> {
  const { maxRetries = 3, initialDelay = 1000, maxDelay = 10000 } = options;
  
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await admin.graphql(query, { variables });
      const result = await response.json();
      
      if (result.errors?.some((e: any) => e.extensions?.code === "THROTTLED")) {
        throw new Error("THROTTLED");
      }
      
      return result.data as T;
    } catch (error) {
      if (attempt === maxRetries) throw error;
      
      const delay = Math.min(initialDelay * Math.pow(2, attempt), maxDelay);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  throw new Error("Max retries exceeded");
}

エラー処理

const response = await admin.graphql(query, { variables });
const { data, errors } = await response.json();

if (errors) {
  // Handle GraphQL errors
  console.error("GraphQL errors:", errors);
  
  // Check for specific error types
  const throttled = errors.some((e: any) => 
    e.extensions?.code === "THROTTLED"
  );
  
  const notFound = errors.some((e: any) => 
    e.message?.includes("not found")
  );
  
  if (throttled) {
    // Retry with backoff
  }
}

ベストプラクティス

  1. デバッグしやすいよう、オペレーション名を付けてください:query GetShopDetails { ... }
  2. レスポンスサイズを減らし、パフォーマンスを向上させるため、必要なフィールドだけをリクエストしてください
  3. リストにはページネーションを使用してください。上限のないデータをリクエストしないでください
  4. エラーを適切に処理してください。errors 配列と HTTP エラーの両方を確認してください
  5. バックグラウンドジョブには、指数バックオフを伴うリトライを実装してください
  6. クエリ間でフィールド選択が繰り返される場合は、フラグメントを使用してください
  7. 関連データを含む複雑なクエリでは、REST より GraphQL を優先してください

参考資料

コメント