Skip to main content
🧙‍♂️ refine grants your wishes! Please give us a ⭐️ on GitHub to keep the magic going.
Version: 4.xx.xx

GraphQL

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.

Hooks and components that support MetaDataQuery:

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

Setup

npm i @refinedev/core @refinedev/antd @refinedev/strapi-graphql
CAUTION

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

INFORMATION

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 @refinedev/strapi-graphql, we have to pass the API address with GraphQLClient.

src/App.tsx
import { Refine } from "@refinedev/core";
import {
Layout,
ReadyPage,
notificationProvider,
ErrorComponent,
} from "@refinedev/antd";
import routerProvider from "@refinedev/react-router-v6";
import dataProvider, { GraphQLClient } from "@refinedev/strapi-graphql";

import { BrowserRouter, Routes, Route, Outlet } from "react-router-dom";

const client = new GraphQLClient("API_URL");

const App: React.FC = () => {
return (
<BrowserRouter>
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider(client)}
notificationProvider={notificationProvider}
>
{/* ... */}
</Refine>
</BrowserRouter>
);
};
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.

post
{
"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 meta to hooks that we will fetch data from.

localhost:5173
// src/pages/posts/list.tsx

import {
List,
EditButton,
ShowButton,
DeleteButton,
useTable,
useSelect,
getDefaultSortOrder,
FilterDropdown,
} from "@refinedev/antd";
import { Table, Space, Select } from "antd";

const PostList = () => {
const { tableProps, sorter } = useTable<IPost>({
sorters: {
initial: [
{
field: "id",
order: "asc",
},
],
},
meta: {
fields: [
"id",
"title",
{
category: ["title"],
},
],
},
});

const { selectProps } = useSelect<ICategory>({
resource: "categories",
meta: {
fields: ["id", "title"],
},
});

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) => {
if (record.category) {
return record.category.title;
}

return "-";
}}
/>
<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>
);
};

Edit Page

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

localhost:5173
// src/pages/posts/edit.tsx

import { Edit, useForm, useSelect } from "@refinedev/antd";
import { Select, Form, Input } from "antd";
import { HttpError } from "@refinedev/core";

interface IPost {
id: string;
title: string;
content: string;
category: ICategory;
}

interface ICategory {
id: string;
title: string;
}

const PostEdit: React.FC = () => {
const { formProps, saveButtonProps, queryResult } = useForm<
IPost,
HttpError,
IPost
>({
meta: {
fields: [
"id",
"title",
{
category: ["id", "title"],
},
"content",
],
},
});

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

const { TextArea } = Input;

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,
},
]}
>
<TextArea />
</Form.Item>
</Form>
</Edit>
);
};
INFORMATION

The create page is largely the same as the edit page, there is no need to pass meta 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 meta.

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.

localhost:5173
// src/pages/posts/edit.tsx

import { Show, RefreshButton } from "@refinedev/antd";
import { Select, Form, Input, Typography } from "antd";
import { useShow } from "@refinedev/core";

const PostShow: React.FC = () => {
const { Title, Text } = Typography;

const { queryResult } = useShow<IPost>({
resource: "posts",
meta: {
fields: [
"id",
"title",
{
category: ["title"],
},
"content",
],
},
});
const { data, isLoading } = queryResult;
const record = data?.data;

return (
<Show
isLoading={isLoading}
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>
<Text value={record?.content} />
</Show>
);
};

Usage with Inferencer

You can also use @refinedev/inferencer package to generate sample codes for your views. Since the GraphQL data providers rely on meta fields, you'll need to provide some meta values beforehand and then Inferencer will use these values to infer the fields of the data provider's response, generate a code and a preview.

Check out Inferencer docs for more information.

Example

Run on your local
npm create refine-app@latest -- --example data-provider-strapi-graphql