はじめに
- codegenでgraphqlの開発がどのように自動化できるのか調査する
- graphqlのきれいなコードの収め方を考える
参考
ソース確認
package.jsonの確認
- @shopify/api-codegen-presetがインストール済み
- スクリプトに、 graphql-codegenが設定済み
- 追加で、npm add @shopify/admin-api-client @shopify/storefront-api-client を実行
{
"name": "xxxxx",
"private": true,
"scripts": {
"build": "remix vite:build",
"dev": "shopify app dev",
"config:link": "shopify app config link",
"generate": "shopify app generate",
"deploy": "shopify app deploy",
"config:use": "shopify app config use",
"env": "shopify app env",
"start": "remix-serve ./build/server/index.js",
"docker-start": "npm run setup && npm run start",
"setup": "prisma generate && prisma migrate deploy",
"lint": "eslint --cache --cache-location ./node_modules/.cache/eslint .",
"shopify": "shopify",
"prisma": "prisma",
"graphql-codegen": "graphql-codegen",
"vite": "vite"
},
"type": "module",
"engines": {
"node": "^18.20 || ^20.10 || >=21.0.0"
},
"dependencies": {
"@prisma/client": "^5.11.0",
"@remix-run/dev": "^2.7.1",
"@remix-run/node": "^2.7.1",
"@remix-run/react": "^2.7.1",
"@remix-run/serve": "^2.7.1",
"@shopify/admin-api-client": "^1.0.1",
"@shopify/app-bridge-react": "^4.1.2",
"@shopify/polaris": "^12.0.0",
"@shopify/shopify-app-remix": "^3.0.2",
"@shopify/shopify-app-session-storage-prisma": "^5.0.2",
"@shopify/storefront-api-client": "^1.0.1",
"isbot": "^5.1.0",
"prisma": "^5.11.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"vite-tsconfig-paths": "^5.0.1"
},
"devDependencies": {
"@remix-run/eslint-config": "^2.7.1",
"@shopify/api-codegen-preset": "^1.0.1",
"@types/eslint": "^8.40.0",
"@types/node": "^22.2.0",
"@types/react": "^18.2.31",
"@types/react-dom": "^18.2.14",
"eslint": "^8.42.0",
"eslint-config-prettier": "^9.1.0",
"prettier": "^3.2.4",
"typescript": "^5.2.2",
"vite": "^5.1.3"
},
"workspaces": [
"extensions/*"
],
"trustedDependencies": [
"@shopify/plugin-cloudflare"
],
"resolutions": {
"undici": "6.13.0"
},
"overrides": {
"undici": "6.13.0"
},
"author": "xxxxx"
}
.graphqlrc.tsの確認
import fs from "fs";
import { LATEST_API_VERSION } from "@shopify/shopify-api";
import { shopifyApiProject, ApiType } from "@shopify/api-codegen-preset";
import type { IGraphQLConfig } from "graphql-config";
function getConfig() {
const config: IGraphQLConfig = {
projects: {
default: shopifyApiProject({
apiType: ApiType.Admin,
apiVersion: LATEST_API_VERSION,
documents: ["./app/**/*.{js,ts,jsx,tsx}", "./app/.server/**/*.{js,ts,jsx,tsx}"],
outputDir: "./app/types",
}),
},
};
let extensions: string[] = [];
try {
extensions = fs.readdirSync("./extensions");
} catch {
// ignore if no extensions
}
for (const entry of extensions) {
const extensionPath = `./extensions/${entry}`;
const schema = `${extensionPath}/schema.graphql`;
if (!fs.existsSync(schema)) {
continue;
}
config.projects[entry] = {
schema,
documents: [`${extensionPath}/**/*.graphql`],
};
}
return config;
}
module.exports = getConfig();
graphql-codegenの実行
% npm run graphql-codegen
> graphql-codegen
> graphql-codegen
✔ Parse Configuration
✔ Generate out
出力されたファイル
- types/admin-2024-07.schema.json
- types/admin.generated.d.ts
- types/admin.types.d.ts
出力ファイルの理解
- projects.defaultの設定
- スキーマは、./app/types/*.schema.json
- ./app/**/以下にAdmin API用のドキュメントを配置。codegenでは./app/types/*.tsを出力
- 他のプロジェクトを追加する場合
- extensions/プロジェクト名/のディレクトリを作成
- スキーマ:extensions/プロジェクト名/shema.graphql
- ドキュメント:extensions/プロジェクト名/**/*.graphql
クエリの作成例
- app/graphql/admin/OrdersQuery.ts
export const ADMIN_ORDERS_QUERY = `#graphql
{
orders(first: 50, reverse: true) {
edges {
node {
id
createdAt
customer {
firstName
}
displayFulfillmentStatus
email
originalTotalPriceSet {
presentmentMoney {
amount
currencyCode
}
}
}
}
}
}
` as const;
import {
IndexTable,
Page,
} from "@shopify/polaris";
import { authenticate } from "../shopify.server";
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { OrderEdge, Order } from '../types/admin.types.d';
import {ADMIN_ORDERS_QUERY} from '../graphql/admin/OrdersQuery';
export const meta = () => {
return [{ title: "Orders" }];
};
export const loader = async ({ request }: LoaderFunctionArgs) => {
const { admin } = await authenticate.admin(request);
const response = await admin.graphql(ADMIN_ORDERS_QUERY);
const responseJson = await response.json();
const orders: Order[] = responseJson?.data?.orders?.edges?.map((row: OrderEdge) => row.node);
return json({
orders
});
};
export default function Orders() {
const { orders } = useLoaderData<{ orders: Order[] }>();
const resourceName = {
singular: 'order',
plural: 'orders',
};
const rowMarkup = orders.map(
(
{ id, originalTotalPriceSet, createdAt, email, customer },
index,
) => (
<IndexTable.Row
id={id}
key={id}
position={index}
>
<IndexTable.Cell>#{id?.replace('gid://shopify/Order/', '')}</IndexTable.Cell>
<IndexTable.Cell>{customer?.firstName}</IndexTable.Cell>
<IndexTable.Cell>{email}</IndexTable.Cell>
<IndexTable.Cell>{originalTotalPriceSet?.presentmentMoney?.amount}</IndexTable.Cell>
<IndexTable.Cell>{createdAt}</IndexTable.Cell>
</IndexTable.Row>
),
);
return (
<Page title="Orders" fullWidth>
<IndexTable
resourceName={resourceName}
itemCount={orders.length}
headings={[
{ title: 'Order Id' },
{ title: 'Name' },
{ title: 'Email' },
{ title: 'Total' },
{ title: 'Created' },
]}
selectable={false}
>
{rowMarkup}
</IndexTable>
</Page>
);
}