Back to Refine

GraphQL

documentation/versioned_docs/version-3.xx.xx/advanced-tutorials/data-provider/graphql.md

3.25.014.6 KB
Original Source

import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';

refine graphql and strapi-graphql data provider built with gql-query-builder and graphql-request is made for GraphQL implementation. It aims to create a query dynamically with gql-query-builder and send requests with graphql-request.

GraphQL Query Builder

GraphQL Query Builder allows us to build queries and mutations. The getList, getMany, and, getOne methods in our dataProvider generate a query to send a request. On the other hand, the create, createMany, update, updateMany, deleteOne, and, deleteMany methods generate a mutation to send a request.

In order to create a GraphQL query, our dataProvider has to take some options, such as specifying which fields will come in response, we pass these options to our dataProvider methods with MetaDataQuery.

Refer to the MetaDataQuery properties for detailed usage. &#8594

Hooks and components that support MetaDataQuery:

Supported data hooksSupported other hooksSupported components
useUpdate &#8594useForm &#8594DeleteButton &#8594
useUpdateMany &#8594useModalForm &#8594RefreshButton &#8594
useDelete &#8594useDrawerForm &#8594
useDeleteMany &#8594useStepsForm &#8594
useCreate &#8594useTable &#8594
useCreateMany &#8594useEditableTable &#8594
useList &#8594useSimpleList &#8594
useOne &#8594useShow &#8594
useMany &#8594useExport &#8594
useCustom &#8594useCheckboxGroup &#8594
useSelect &#8594
useRadioGroup &#8594

Setup

<InstallPackagesCommand args="@pankod/refine-core @pankod/refine-antd @pankod/refine-strapi-graphql"/>

:::caution To make this example more visual, we used the @pankod/refine-antd package. If you are using Refine headless, you need to provide the components, hooks or helpers imported from the @pankod/refine-antd package. :::

:::info We used strapi-graphql server for this guide. You can customize your data provider for your own GraphQL server. :::

Usage

To activate the data provider in @pankod/refine-strapi-graphql, we have to pass the API address with GraphQLClient.

tsx
import { Refine } from "@pankod/refine-core";
import {
  Layout,
  ReadyPage,
  useNotificationProvider,
  ErrorComponent,
} from "@pankod/refine-antd";
import routerProvider from "@pankod/refine-react-router-v6";
// highlight-next-line
import dataProvider, { GraphQLClient } from "@pankod/refine-strapi-graphql";

const client = new GraphQLClient("API_URL");

const App: React.FC = () => {
  return (
    <Refine
      routerProvider={routerProvider}
      // highlight-next-line
      dataProvider={dataProvider(client)}
      Layout={Layout}
      ReadyPage={ReadyPage}
      notificationProvider={useNotificationProvider}
      catchAll={<ErrorComponent />}
    />
  );
};

:::note With GraphQLClient you can do things like add headers for authentication. On the other hand, you can send a request with your query. :::

Create Collections

We created two collections on Strapi as posts and categories and added a relation between them. For detailed information on how to create a collection, you can check here.

You can see the fields of the collections we created as below.

json
{
  "id": 1,
  "title": "Eius ea autem.",
  "content": "Explicabo nihil delectus. Nam aliquid sunt numquam...",
  "category": {
    "id": 24,
    "title": "Placeat fuga"
  }
}

List Page

When sending the request, we must specify which fields will come, so we send fields in metaData to hooks that we will fetch data from.

<Tabs defaultValue="usage" values={[ {label: 'usage', value: 'usage'}, {label: 'output', value: 'output'} ]}> <TabItem value="usage">

tsx
export const PostList: React.FC<IResourceComponentsProps> = () => {
  const { tableProps, sorter } = useTable<IPost>({
    initialSorter: [
      {
        field: "id",
        order: "asc",
      },
    ],
    // highlight-start
    metaData: {
      fields: [
        "id",
        "title",
        {
          category: ["title"],
        },
      ],
    },
    // highlight-end
  });

  const { selectProps } = useSelect<ICategory>({
    resource: "categories",
    // highlight-start
    metaData: {
      fields: ["id", "title"],
    },
    // highlight-end
  });

  return (
    <List>
      <Table {...tableProps} rowKey="id">
        <Table.Column
          dataIndex="id"
          title="ID"
          sorter={{ multiple: 2 }}
          defaultSortOrder={getDefaultSortOrder("id", sorter)}
        />
        <Table.Column
          key="title"
          dataIndex="title"
          title="Title"
          sorter={{ multiple: 1 }}
        />
        <Table.Column<IPost>
          dataIndex="category"
          title="Category"
          filterDropdown={(props) => (
            <FilterDropdown {...props}>
              <Select
                style={{ minWidth: 200 }}
                mode="multiple"
                placeholder="Select Category"
                {...selectProps}
              />
            </FilterDropdown>
          )}
          render={(_, record) => record.category.title}
        />
        <Table.Column<IPost>
          title="Actions"
          dataIndex="actions"
          render={(_, record) => (
            <Space>
              <EditButton hideText size="small" recordItemId={record.id} />
              <ShowButton hideText size="small" recordItemId={record.id} />
              <DeleteButton hideText size="small" recordItemId={record.id} />
            </Space>
          )}
        />
      </Table>
    </List>
  );
};
</TabItem> <TabItem value="output">
ts

query ($sort: String, $where: JSON, $start: Int, $limit: Int) {
    posts (sort: $sort, where: $where, start: $start, limit: $limit) {
        id,
        title,
        category {
            title
        }
    }
}
</TabItem> </Tabs>

Edit Page

On the edit page, useForm sends a request with the record id to fill the form. fields must be sent in metaData to determine which fields will come in this request.

<Tabs defaultValue="usage" values={[ {label: 'usage', value: 'usage'}, {label: 'output', value: 'output'} ]}> <TabItem value="usage">

tsx
export const PostEdit: React.FC<IResourceComponentsProps> = () => {
  const { formProps, saveButtonProps, queryResult } = useForm<
    IPost,
    HttpError,
    IPost
  >({
    // highlight-start
    metaData: {
      fields: [
        "id",
        "title",
        {
          category: ["id", "title"],
        },
        "content",
      ],
    },
    // highlight-end
  });

  const postData = queryResult?.data?.data;
  const { selectProps: categorySelectProps } = useSelect<ICategory>({
    resource: "categories",
    defaultValue: postData?.category.id,
    // highlight-start
    metaData: {
      fields: ["id", "title"],
    },
    // highlight-end
  });

  return (
    <Edit saveButtonProps={saveButtonProps}>
      <Form
        {...formProps}
        layout="vertical"
        onFinish={(values) =>
          formProps.onFinish?.({
            ...values,
            category: values.category.id,
          } as any)
        }
      >
        <Form.Item
          label="Title"
          name="title"
          rules={[
            {
              required: true,
            },
          ]}
        >
          <Input />
        </Form.Item>
        <Form.Item
          label="Category"
          name={["category", "id"]}
          rules={[
            {
              required: true,
            },
          ]}
        >
          <Select {...categorySelectProps} />
        </Form.Item>
        <Form.Item
          label="Content"
          name="content"
          rules={[
            {
              required: true,
            },
          ]}
        >
          <MDEditor data-color-mode="light" />
        </Form.Item>
      </Form>
    </Edit>
  );
};

:::info The create page is largely the same as the edit page, there is no need to pass metaData to useForm on the create page. If you want to use the created record as a response after the request, you can pass the fields with metaData. :::

</TabItem> <TabItem value="output">
ts
mutation ($input: updatePostInput) {
    updatePost (input: $input) {
        post  {
            id
        }
    }
}
</TabItem> </Tabs>

Show Page

<Show> component includes the <RefreshButton> by default. We can pass refetch method of queryResult to its onClick. This method repeats the last request made by the query. So it refreshes the data that is shown in page.

<Tabs defaultValue="usage" values={[ {label: 'usage', value: 'usage'}, {label: 'output', value: 'output'} ]}> <TabItem value="usage">

tsx
export const PostShow: React.FC<IResourceComponentsProps> = () => {
  const { queryResult } = useShow<IPost>({
    resource: "posts",
    // highlight-start
    metaData: {
      fields: [
        "id",
        "title",
        {
          category: ["title"],
        },
        "content",
      ],
    },
    // highlight-end
  });
  const { data, isLoading } = queryResult;
  const record = data?.data;

  return (
    <Show
      isLoading={isLoading}
      // highlight-next-line
      headerProps={{
        extra: <RefreshButton onClick={() => queryResult.refetch()} />,
      }}
    >
      <Title level={5}>Id</Title>
      <Text>{record?.id}</Text>

      <Title level={5}>Title</Title>
      <Text>{record?.title}</Text>

      <Title level={5}>Category</Title>
      <Text>{record?.category.title}</Text>

      <Title level={5}>Content</Title>
      <MarkdownField value={record?.content} />
    </Show>
  );
};
</TabItem> <TabItem value="output">
ts
mutation ($input: updatePostInput) {
    updatePost (input: $input) {
        post  {
            id
        }
    }
}
</TabItem> </Tabs>

Example

<CodeSandboxExample path="--branch v3 data-provider-strapi-graphql" />