Skip to main content
Version: 3.xx.xx

Data Provider

The data provider acts as a data layer for your app that makes the HTTP requests and encapsulates how the data is retrieved. refine consumes these methods via data hooks.

You don't need to worry about creating data providers from scratch. refine offers built-in data provider support for the most popular API providers. So you can use one of them or you can create your own data provider according to your needs.

NOTE

Data hooks use TanStack Query to manage data fetching. It handles important concerns like caching, invalidation, loading states, etc.


Usage

To activate the data provider in refine, we have to pass the dataProvider to the <Refine /> component.

App.tsx
import { Refine } from "@pankod/refine-core";

import dataProvider from "./dataProvider";

const App: React.FC = () => {
return <Refine dataProvider={dataProvider} />;
};

Refer to the Data Provider tutorial for more information and usage examples →

Multiple Data Providers

refine gives you the ability to use multiple data providers in your app. All you need to do is to pass key, value pairs to the dataProvider prop of the <Refine /> component in a form of value being the data provider and the key being the name of the data provider.

Here is an example of using multiple data providers in your app:

localhost:3000/posts
Live previews only work with the latest documentation.
import { Refine, useList } from "@pankod/refine-core";
import { Layout, Collapse, Tag } from "@pankod/refine-antd";
import dataProvider from "@pankod/refine-simple-rest";
import routerProvider from "@pankod/refine-react-router-v6";

const API_URL = "https://api.fake-rest.refine.dev";
const FINE_FOODS_API_URL = "https://api.finefoods.refine.dev";

interface IPost {
id: number;
title: string;
status: "published" | "draft" | "rejected";
}

interface IProduct {
id: number;
name: string;
price: number;
}

const App: React.FC = () => {
return (
<Refine
routerProvider={routerProvider}
Layout={Layout}
dataProvider={{
default: dataProvider(API_URL),
fineFoods: dataProvider(FINE_FOODS_API_URL),
}}
resources={[
{
// **refine** will use the `default` data provider for this resource
name: "posts",
list: PostList,
},
{
name: "products",
options: {
// **refine** will use the `fineFoods` data provider for this resource
dataProviderName: "fineFoods",
},
},
]}
/>
);
};

const PostList: React.FC = () => {
const { data: posts } = useList<IPost>({
resource: "posts",
// Data provider can be selected through props
dataProviderName: "default",
});
// We've defined the data provider for this resource as "fineFoods" in its config so we don't need to pass it here
const { data: products } = useList<IProduct>({ resource: "products" });

console.log({
posts,
products,
});

return (
<Collapse defaultActiveKey={["products"]}>
<Collapse.Panel header="Posts" key="posts">
{posts?.data.map((post) => (
<div
key={post.title}
style={{
display: "flex",
flexDirection: "row",
gap: "0.5rem",
marginBottom: "0.25rem",
}}
>
{post.title}
<Tag>{post.status}</Tag>
</div>
))}
</Collapse.Panel>
<Collapse.Panel header="Products" key="products">
{products?.data.map((product) => (
<div
key={product.name}
style={{
display: "flex",
flexDirection: "row",
gap: "0.5rem",
marginBottom: "0.25rem",
}}
>
{product.name}
<Tag>{product.price / 10}</Tag>
</div>
))}
</Collapse.Panel>
</Collapse>
);
};
CAUTION

default key is required for the default data provider and it will be used as the default data provider.

App.tsx
const App = () => {
return (
<Refine
dataProvider={{
default: defaultDataProvider,
example: exampleDataProvider,
}}
/>
);
};

You can pick data providers in two ways:

  • Using dataProviderName prop in the data hooks and all data-related components/functions.
useTable({
dataProviderName: "example",
});
  • Using options.dataProviderName property in your resource config

This will be the default data provider for the specified resource but you can still override it in the data hooks and components.

const App = () => {
return (
<Refine
dataProvider={{
default: defaultDataProvider,
example: exampleDataProvider,
}}
resources={[
{
// **refine** will use the `default` data provider for this resource
name: "posts",
},
{
name: "products",
options: {
// **refine** will use the `exampleDataProvider` data provider for this resource
dataProviderName: "exampleDataProvider",
},
},
]}
/>
);
};

Methods

Data provider's methods are expected to return a Promise. So, you can use these async methods to create a data provider.

import { DataProvider } from "@pankod/refine-core";

const dataProvider: DataProvider = {
// required methods
getList: ({ resource, pagination, hasPagination, sort, filters, metaData }) =>
Promise,
create: ({ resource, variables, metaData }) => Promise,
update: ({ resource, id, variables, metaData }) => Promise,
deleteOne: ({ resource, id, variables, metaData }) => Promise,
getOne: ({ resource, id, metaData }) => Promise,
getApiUrl: () => "",
// optional methods
getMany: ({ resource, ids, metaData }) => Promise,
createMany: ({ resource, variables, metaData }) => Promise,
deleteMany: ({ resource, ids, variables, metaData }) => Promise,
updateMany: ({ resource, ids, variables, metaData }) => Promise,
custom: ({ url, method, filters, sort, payload, query, headers, metaData }) =>
Promise,
};
INFORMATION

refine consumes data provider methods using data hooks.

Data hooks are used to operate CRUD actions like creating a new record, listing a resource or deleting a record, etc.

Refer to the Data Provider tutorial for more information and usage examples →

getList
required

getList method is used to get a list of resources with sorting, filtering, and pagination features. It takes resource, sort, pagination, and, filters as parameters and returns data and total.

refine will consume this getList method using the useList or useInfiniteList data hook.

getList: async ({
resource,
hasPagination,
pagination,
sort,
filter,
metaData,
}) => {
const { current, pageSize } = pagination;
const { field, order } = sort;
const { field, operator, value } = filter;

// You can handle the request according to your API requirements.

return {
data,
total,
};
};
TIP

getList also can support cursor-based pagination. Refer to this example for more information.

Parameter Types:

NameType
resourcestring
hasPagination?boolean (defaults to true)
pagination?Pagination
sort?CrudSorting
filters?CrudFilters
metaData?MetaDataQuery

create
required

The create method creates a new record with the resource and variables parameters.

refine will consume this create method using the useCreate data hook.

create: async ({ resource, variables, metaData }) => {
// You can handle the request according to your API requirements.

return {
data,
};
};

Parameter Types

NameTypeDefault
resourcestring
variablesTVariables{}
metaData?MetaDataQuery

TVariables is a user defined type which can be passed to useCreate to type variables.

update
required

The update method updates the record with the resource, id, and, variables parameters.

refine will consume this update method using the useUpdate data hook.

update: async ({ resource, id, variables, metaData }) => {
// You can handle the request according to your API requirements.

return {
data,
};
};

Parameter Types:

NameTypeDefault
resourcestring
idBaseKey
variablesTVariables{}
metaData?MetaDataQuery

TVariables is a user defined type which can be passed to useUpdate to type variables.

deleteOne
required

The deleteOne method delete the record with the resource and id parameters.

refine will consume this deleteOne method using the useDelete data hook.

deleteOne: async ({ resource, id, variables, metaData }) => {
// You can handle the request according to your API requirements.

return {
data,
};
};

Parameter Types:

NameTypeDefault
resourcestring
idBaseKey
variablesTVariables[]{}
metaData?MetaDataQuery

TVariables is a user defined type which can be passed to useDelete to type variables.

getOne
required

The getOne method gets the record with the resource and id parameters.

refine will consume this getOne method using the useOne data hook.

getOne: async ({ resource, id, metaData }) => {
// You can handle the request according to your API requirements.

return {
data,
};
};

Parameter Types:

NameTypeDefault
resourcestring
idBaseKey
metaData?MetaDataQuery

getApiUrl
required

The getApiUrl method returns the apiUrl value.

refine will consume this getApiUrl method using the useApiUrl data hook.

src/data-provider.ts
import { DataProvider } from "@pankod/refine-core";

export const dataProvider = (apiUrl: string): DataProvider => ({
getApiUrl: () => apiUrl,
// ...
});

custom

An optional method named custom can be added to handle requests with custom parameters like URL, CRUD methods, and configurations. It's useful if you have non-standard REST API endpoints or want to make a connection with external resources.

refine will consume this custom method using the useCustom data hook.

custom: async ({
url,
method,
filters,
sort,
payload,
query,
headers,
metaData,
}) => {
// You can handle the request according to your API requirements.

return {
data,
};
};

Parameter Types

NameType
urlstring
methodget, delete, head, options, post, put, patch
sort?CrudSorting
filters?CrudFilters
payload?{}
query?{}
headers?{}
metaData?MetaDataQuery

Bulk Actions

Bulk actions are actions that can be performed on multiple items at once. Performing bulk actions is a common pattern in admin panels. If your API supports bulk actions, you can implement them in your data provider.

TIP

Bulk operations are a way to perform many database operations at once, improving speed and efficiency. They can be used for data import and export, and have the added benefit of being atomic, meaning that they are treated as a single unit.

getMany

The getMany method gets the records with the resource and ids parameters. Implementation of this method is optional. If you don't implement it, refine will use getOne method to handle multiple requests.

refine will consume this getMany method using the useMany data hook.

getMany: async ({ resource, ids, metaData }) => {
// You can handle the request according to your API requirements.

return {
data,
};
};

Parameter Types:

NameTypeDefault
resourcestring
ids[BaseKey]
metaData?MetaDataQuery

createMany

This method allows us to create multiple items in a resource. Implementation of this method is optional. If you don't implement it, refine will use create method to handle multiple requests.

refine will consume this createMany method using the useCreateMany data hook.

createMany: async ({ resource, variables, metaData }) => {
// You can handle the request according to your API requirements.

return {
data,
};
};

Parameter Types:

NameTypeDefault
resourcestring
variablesTVariables[]{}
metaData?MetaDataQuery

TVariables is a user defined type which can be passed to useCreateMany to type variables.

deleteMany

This method allows us to delete multiple items in a resource. Implementation of this method is optional. If you don't implement it, refine will use deleteOne method to handle multiple requests.

refine will consume this deleteMany method using the useDeleteMany data hook.

deleteMany: async ({ resource, ids, variables, metaData }) => {
// You can handle the request according to your API requirements.

return {
data,
};
};
NameTypeDefault
resourcestring
ids[BaseKey]
variablesTVariables[]{}
metaData?MetaDataQuery

TVariables is a user defined type which can be passed to useDeleteMany to type variables.

updateMany

This method allows us to update multiple items in a resource. Implementation of this method is optional. If you don't implement it, refine will use update method to handle multiple requests.

refine will consume this updateMany method using the useUpdateMany data hook.

updateMany: async ({ resource, ids, variables, metaData }) => {
// You can handle the request according to your API requirements.

return {
data,
};
};
NameTypeDefault
resourcestring
ids[BaseKey]
variablesTVariables[]{}
metaData?MetaDataQuery

TVariables is a user defined type which can be passed to useUpdateMany to type variables.

Error Format

refine expects errors to be extended from HttpError.

Here is a basic example of how to implement error handling in your data provider.

src/data-provider.ts
import { DataProvider, HttpError } from "@pankod/refine-core";

export const dataProvider = (apiUrl: string): DataProvider => ({
getOne: async ({ resource, id }) => {
try {
const response = await fetch(`https://api.example.com/${resource}/${id}`);

if (!response.ok) {
const error: HttpError = {
message: response.statusText,
statusCode: response.status,
};
return Promise.reject(error);
}

return {
data: response.data,
};
} catch (error) {
const error: HttpError = {
message: error?.message || "Something went wrong",
statusCode: error?.status || 500,
};
return Promise.reject(error);
}
},
// ...
});

Also, Axios interceptor can be used to transform the error from the response before Axios returns the response to your code. Interceptors are methods that are triggered before the main method.

src/data-provider.ts
import axios from "axios";
import { DataProvider, HttpError } from "@pankod/refine-core";
import { stringify } from "query-string";

// Error handling with axios interceptors
const axiosInstance = axios.create();

axiosInstance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
const customError: HttpError = {
...error,
message: error.response?.data?.message,
statusCode: error.response?.status,
};

return Promise.reject(customError);
},
);

export const dataProvider = (apiUrl: string): DataProvider => ({
// Methods
});

metaData Usage

When using APIs, you may wish to include custom parameters, such as a custom header. To accomplish this, you can utilize the metaData field, which allows the sent parameter to be easily accessed by the data provider.

TIP

The metaData parameter can be used in all data, form, and table hooks.

Here is an example of how to send a custom header parameter to the getOne method using metaData:

  • Send a custom header parameter to the getOne method using metaData.
post/edit.tsx
import { useOne } from "@pankod/refine-core";

useOne({
resource: "post",
id: "1",
metaData: {
headers: {
"x-custom-header": "hello world",
},
},
});
  • Get the metaData parameter from the data provider.
src/data-provider.ts
import { DataProvider } from "@pankod/refine-core";

export const dataProvider = (apiUrl: string): DataProvider => ({
...
getOne: async ({ resource, id, variables, metaData }) => {
const { headers } = metaData;
const url = `${apiUrl}/${resource}/${id}`;

httpClient.defaults.headers = {
...headers,
};

const { data } = await httpClient.get(url, variables);

return {
data,
};
},
});

Supported Data Providers

Refine supports many data providers. To include them in your project, you can use npm install [packageName] or you can select the preferred data provider with the npm create refine-app@latest projectName during the project creation phase with CLI. This will allow you to easily use these data providers in your project.

Community ❤️

If you have created a custom data provider and would like to share it with the community, please don't hesitate to get in touch with us. We would be happy to include it on this page for others to use.

Supported Hooks

refine will consume:

FAQ

How can I create a custom data provider?

How can I customize existing data providers?

How I can override a specific method of Data Providers?

In some cases, you may need to override the method of refine data providers. The simplest way to do this is to use the Spread syntax

For example, Let's override the update function of the @pankod/refine-simple-rest. @pankod/refine-simple-rest uses the PATCH HTTP method for update, let's change it to PUT without forking the whole data provider.

import dataProvider from "@pankod/refine-simple-rest";

const simpleRestProvider = dataProvider("API_URL");
const myDataProvider = {
...simpleRestProvider,
update: async ({ resource, id, variables }) => {
const url = `${apiUrl}/${resource}/${id}`;

const { data } = await httpClient.put(url, variables);

return {
data,
};
},
};

<Refine dataProvider={myDataProvider} />;