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
:
Setup
- npm
- pnpm
- yarn
npm i @pankod/refine-core @pankod/refine-antd @pankod/refine-strapi-graphql
pnpm add @pankod/refine-core @pankod/refine-antd @pankod/refine-strapi-graphql
yarn add @pankod/refine-core @pankod/refine-antd @pankod/refine-strapi-graphql
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.
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
.
import { Refine } from "@pankod/refine-core";
import {
Layout,
ReadyPage,
useNotificationProvider,
ErrorComponent,
} from "@pankod/refine-antd";
import routerProvider from "@pankod/refine-react-router-v6";
import dataProvider, { GraphQLClient } from "@pankod/refine-strapi-graphql";
const client = new GraphQLClient("API_URL");
const App: React.FC = () => {
return (
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider(client)}
Layout={Layout}
ReadyPage={ReadyPage}
notificationProvider={useNotificationProvider}
catchAll={<ErrorComponent />}
/>
);
};
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.
{
"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.
- usage
- output
export const PostList: React.FC<IResourceComponentsProps> = () => {
const { tableProps, sorter } = useTable<IPost>({
initialSorter: [
{
field: "id",
order: "asc",
},
],
metaData: {
fields: [
"id",
"title",
{
category: ["title"],
},
],
},
});
const { selectProps } = useSelect<ICategory>({
resource: "categories",
metaData: {
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) => 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>
);
};
query ($sort: String, $where: JSON, $start: Int, $limit: Int) {
posts (sort: $sort, where: $where, start: $start, limit: $limit) {
id,
title,
category {
title
}
}
}
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.
- usage
- output
export const PostEdit: React.FC<IResourceComponentsProps> = () => {
const { formProps, saveButtonProps, queryResult } = useForm<
IPost,
HttpError,
IPost
>({
metaData: {
fields: [
"id",
"title",
{
category: ["id", "title"],
},
"content",
],
},
});
const postData = queryResult?.data?.data;
const { selectProps: categorySelectProps } = useSelect<ICategory>({
resource: "categories",
defaultValue: postData?.category.id,
metaData: {
fields: ["id", "title"],
},
});
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>
);
};
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
.
mutation ($input: updatePostInput) {
updatePost (input: $input) {
post {
id
}
}
}
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.
- usage
- output
export const PostShow: React.FC<IResourceComponentsProps> = () => {
const { queryResult } = useShow<IPost>({
resource: "posts",
metaData: {
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>
<MarkdownField value={record?.content} />
</Show>
);
};
mutation ($input: updatePostInput) {
updatePost (input: $input) {
post {
id
}
}
}
Example
npm create refine-app@latest -- --example --branch v3 data-provider-strapi-graphql