Skip to main content
Version: 3.xx.xx

Data Provider

Overview​

A data provider is the place where a refine app communicates with an API.
Data providers also act as adapters for refine making it possible to consume different API's and data services conveniently.
A data provider makes HTTP requests and returns response data back using predefined methods.

A data provider must include following methods:

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

refine includes many out-of-the-box data providers to use in your projects like

Community ❀️​


info

refine consumes this 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..

note

Data hooks uses React Query to manage data fetching. React Query handles important concerns like caching, invalidation, loading states etc..







Usage​

To activate 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} />;
};
tip

To activate multiple data provider in refine, we have to pass the default key with dataProvider for default data provider and we can pass other data provider with any key to the <Refine /> component.

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

import defaultDataProvider from "./dataProvider";
import exampleDataProvider from "./dataProvider";

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

Creating a data provider​

We will build "Simple REST Dataprovider" of @pankod/refine-simple-rest from scratch to show the logic of how data provider methods interact with the API.

We will provide you a fully working, fake REST API located at https://api.fake-rest.refine.dev. You may take a look at available resources and routes of the API before proceeding to the next step.
Our "Simple REST Dataprovider" will be consuming this fake REST API.

note

Fake REST API is based on JSON Server Project. Simple REST Dataprovider is fully compatible with the REST rules and methods of the JSON Server.


Let's build a method that returns our data provider:

dataProvider.ts
import axios, { AxiosInstance } from "axios";
import { DataProvider } from "./interfaces/dataProvider.ts";

const axiosInstance = axios.create();

const SimpleRestDataProvider = (
apiUrl: string,
httpClient: AxiosInstance = axiosInstance,
): DataProvider => ({
create: ({ resource, variables, metaData }) => Promise,
createMany: ({ resource, variables, metaData }) => Promise,
deleteOne: ({ resource, id, variables, metaData }) => Promise,
deleteMany: ({ resource, ids, variables, metaData }) => Promise,
getList: ({ resource, pagination, sort, filters, metaData }) => Promise,
getMany: ({ resource, ids, metaData }) => Promise,
getOne: ({ resource, id, metaData }) => Promise,
update: ({ resource, id, variables, metaData }) => Promise,
updateMany: ({ resource, ids, variables, metaData }) => Promise,
custom: ({
url,
method,
sort,
filters,
payload,
query,
headers,
metaData,
}) => Promise,
getApiUrl: () => "",
});

It will take the API URL as a parameter and an optional HTTP client. We will use axios as the default HTTP client.


create​

This method allows us to create a single item in a resource.

dataProvider.ts
const SimpleRestDataProvider = (
apiUrl: string,
httpClient: AxiosInstance = axiosInstance,
): DataProvider => ({
...
create: async ({ resource, variables }) => {
const url = `${apiUrl}/${resource}`;

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

return {
data,
};
},
...
})

Parameter Types​

NameTypeDefault
resourcestring
variablesTVariables{}

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


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

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

const { mutate } = useCreate();

mutate({
resource: "categories",
values: {
title: "New Category",
},
});

Refer to the useCreate documentation for more information. β†’


createMany​

This method allows us to create multiple items in a resource.

dataProvider.ts
const SimpleRestDataProvider = (
apiUrl: string,
httpClient: AxiosInstance = axiosInstance,
): DataProvider => ({
...
createMany: async ({ resource, variables }) => {
const response = await Promise.all(
variables.map(async (param) => {
const { data } = await httpClient.post(
`${apiUrl}/${resource}`,
param,
);
return data;
}),
);

return { data: response };
},
...
})

Parameter Types​

NameTypeDefault
resourcestring
variablesTVariables[]{}

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


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

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

const { mutate } = useCreateMany();

mutate({
resource: "categories",
values: [
{
title: "New Category",
},
{
title: "Another New Category",
},
],
});

Refer to the useCreateMany documentation for more information. β†’


deleteOne​

This method allows us to delete an item in a resource.

dataProvider.ts
const SimpleRestDataProvider = (
apiUrl: string,
httpClient: AxiosInstance = axiosInstance,
): DataProvider => ({
...
deleteOne: async ({ resource, id }) => {
const url = `${apiUrl}/${resource}/${id}`;

const { data } = await httpClient.delete(url);

return {
data,
};
},
...
})

Parameter Types​

NameTypeDefault
resourcestring
idBaseKey
variablesTVariables[]{}

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


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

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

const { mutate } = useDelete();

mutate({ resource: "categories", id: "2" });

Refer to the useDelete documentation for more information. β†’


deleteMany​

This method allows us to delete multiple items in a resource.

dataProvider.ts
const SimpleRestDataProvider = (
apiUrl: string,
httpClient: AxiosInstance = axiosInstance,
): DataProvider => ({
...
deleteMany: async ({ resource, ids }) => {
const response = await Promise.all(
ids.map(async (id) => {
const { data } = await httpClient.delete(
`${apiUrl}/${resource}/${id}`,
);
return data;
}),
);
return { data: response };
},
...
})

Parameter Types​

NameTypeDefault
resourcestring
idsBaseKey[]
variablesTVariables[]{}

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


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

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

const { mutate } = useDeleteMany();

mutate({
resource: "categories",
ids: ["2", "3"],
});

Refer to the useDeleteMany documentation for more information. β†’


update​

This method allows us to update an item in a resource.

dataProvider.ts
const SimpleRestDataProvider = (
apiUrl: string,
httpClient: AxiosInstance = axiosInstance,
): DataProvider => ({
...
update: async ({ resource, id, variables }) => {
const url = `${apiUrl}/${resource}/${id}`;

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

return {
data,
};
},
...
})

Parameter Types​

NameTypeDefault
resourcestring
idBaseKey
variablesTVariables{}

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


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

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

const { mutate } = useUpdate();

mutate({
resource: "categories",
id: "2",
values: { title: "New Category Title" },
});

Refer to the useUpdate documentation for more information. β†’


updateMany​

This method allows us to update multiple items in a resource.

dataProvider.ts
const SimpleRestDataProvider = (
apiUrl: string,
httpClient: AxiosInstance = axiosInstance,
): DataProvider => ({
...
updateMany: async ({ resource, ids, variables }) => {
const response = await Promise.all(
ids.map(async (id) => {
const { data } = await httpClient.patch(
`${apiUrl}/${resource}/${id}`,
variables,
);
return data;
}),
);

return { data: response };
},
...
})

Parameter Types​

NameTypeDefault
resourcestring
idsBaseKey[]
variablesTVariables{}

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


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

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

const { mutate } = useUpdateMany();

mutate({
resource: "posts",
ids: ["1", "2"],
values: { status: "draft" },
});

Refer to the useUpdateMany documentation for more information. β†’


getOne​

This method allows us to retrieve a single item in a resource.

dataProvider.ts
const SimpleRestDataProvider = (
apiUrl: string,
httpClient: AxiosInstance = axiosInstance,
): DataProvider => ({
...
getOne: async ({ resource, id }) => {
const url = `${apiUrl}/${resource}/${id}`;

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

return {
data,
};
},
...
})

Parameter Types​

NameTypeDefault
resourcestring
idBaseKey

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

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

const { data } = useOne<ICategory>({ resource: "categories", id: "1" });

Refer to the useOne documentation for more information. β†’


getMany​

This method allows us to retrieve multiple items in a resource.

dataProvider.ts
import { stringify } from "query-string";

const SimpleRestDataProvider = (
apiUrl: string,
httpClient: AxiosInstance = axiosInstance,
): DataProvider => ({
...
getMany: async ({ resource, ids }) => {
const { data } = await httpClient.get(
`${apiUrl}/${resource}?${stringify({ id: ids })}`,
);

return {
data,
};
},
...
})
tip

We are using the query-string package for stringify.

Parameter Types​

NameTypeDefault
resourcestring
idsBaseKey[]

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

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

const { data } = useMany({ resource: "categories", ids: ["1", "2"] });

Refer to the useMany documentation for more information. β†’


getList​

This method allows us to retrieve a collection of items in a resource.

dataProvider.ts
const SimpleRestDataProvider = (
apiUrl: string,
httpClient: AxiosInstance = axiosInstance,
): DataProvider => ({
getList: async ({ resource, hasPagination, pagination, filters, sort }) => {
const url = `${apiUrl}/${resource}`;

const { data, headers } = await httpClient.get(
`${url}`,
);

const total = +headers["x-total-count"];

return {
data,
total,
};
},
}

Parameter Types​

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

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

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

const { data } = useList({ resource: "posts" });

Refer to the useList documentation for more information. β†’


Adding pagination

We will send start and end parameters to list a certain size of items.

dataProvider.ts
import { stringify } from "query-string";

const SimpleRestDataProvider = (
apiUrl: string,
httpClient: AxiosInstance = axiosInstance,
): DataProvider => ({
getList: async ({ resource, hasPagination = true, pagination, filters, sort }) => {
const url = `${apiUrl}/${resource}`;

const current = pagination?.current || 1;
const pageSize = pagination?.pageSize || 10;

const query = hasPagination ? {
_start: (current - 1) * pageSize,
_end: current * pageSize,
} : {};

const { data, headers } = await httpClient.get(
`${url}?${stringify(query)}`,
);

const total = +headers["x-total-count"];

return {
data,
total,
};
},

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

const { data } = useList({
resource: "posts",
config: {
pagination: { current: 1, pageSize: 10 },
hasPagination: true, // This can be omitted since it's default to `true` in the `getList` method of our data provider.
},
});

Listing will start from page 1 showing 10 records.


Adding sorting

We'll sort records by speficified order and field.

CrudSorting ?

dataProvider.ts
const generateSort = (sort?: CrudSorting) => {
let _sort = ["id"]; // default sorting field
let _order = ["desc"]; // default sorting

if (sort) {
_sort = [];
_order = [];

sort.map((item) => {
_sort.push(item.field);
_order.push(item.order);
});
}

return {
_sort,
_order,
};
};

const SimpleRestDataProvider = (
apiUrl: string,
httpClient: AxiosInstance = axiosInstance,
): DataProvider => ({
getList: async ({ resource, hasPagination = true, pagination, filters, sort }) => {
const url = `${apiUrl}/${resource}`;

const current = pagination?.current || 1;
const pageSize = pagination?.pageSize || 10;

const { _sort, _order } = generateSort(sort);

const query = {
...(hasPagination ? {
_start: (current - 1) * pageSize,
_end: current * pageSize,
} : {}),
_sort: _sort.join(","),
_order: _order.join(","),
};

const { data, headers } = await httpClient.get(
`${url}?${stringify(query)}`,
);

const total = +headers["x-total-count"];

return {
data,
total,
};
},
}

Since our API accepts only certain parameter formats like _sort and _order we may need to transform some of the parameters.

So we added the generateSort method to transform sort parameters.


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

const { data } = useList({
resource: "posts",
config: {
pagination: { current: 1, pageSize: 10 },
sort: [{ order: "asc", field: "title" }],
},
});

Listing starts from ascending alphabetical order on title field.


Adding filtering

Filters allow you to filter queries using refine's filter operators. It is configured via field, operator and value properites.

dataProvider.ts
const generateSort = (sort?: CrudSorting) => {
let _sort = ["id"]; // default sorting field
let _order = ["desc"]; // default sorting

if (sort) {
_sort = [];
_order = [];

sort.map((item) => {
_sort.push(item.field);
_order.push(item.order);
});
}

return {
_sort,
_order,
};
};

const mapOperator = (operator: CrudOperators): string => {
switch (operator) {
case "ne":
case "gte":
case "lte":
return `_${operator}`;
case "contains":
return "_like";
}

return ""; // default "eq"
};

const generateFilter = (filters?: CrudFilters) => {
const queryFilters: { [key: string]: string } = {};
if (filters) {
filters.map(({ field, operator, value }) => {
const mappedOperator = mapOperator(operator);
queryFilters[`${field}${mappedOperator}`] = value;
});
}

return queryFilters;
};

const SimpleRestDataProvider = (
apiUrl: string,
httpClient: AxiosInstance = axiosInstance,
): DataProvider => ({
getList: async ({ resource, hasPagination = true, pagination, filters, sort }) => {
const url = `${apiUrl}/${resource}`;

const current = pagination?.current || 1;
const pageSize = pagination?.pageSize || 10;

const { _sort, _order } = generateSort(sort);

const queryFilters = generateFilter(filters);

const query = {
...(hasPagination ? {
_start: (current - 1) * pageSize,
_end: current * pageSize,
} : {}),
_sort: _sort.join(","),
_order: _order.join(","),
};

const { data, headers } = await httpClient.get(
`${url}?${stringify(query)}&${stringify(queryFilters)}`,
);

const total = +headers["x-total-count"];

return {
data,
total,
};
},
}

Since our API accepts only certain parameter formats to filter the data, we may need to transform some parameters.

So we added the generateFilter and mapOperator methods to the transform filter parameters.

Refer to the list of all filter operators β†’

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

const { data } = useList({
resource: "posts",
config: {
pagination: { current: 1, pageSize: 10 },
sort: [{ order: "asc", field: "title" }],
filters: [
{
field: "status",
operator: "eq",
value: "rejected",
},
],
},
});

Only lists records whose status equals to "rejected".


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-stantard REST API endpoints or want to make a connection with external resources.

dataProvider.ts
const SimpleRestDataProvider = (
apiUrl: string,
httpClient: AxiosInstance = axiosInstance,
): DataProvider => ({
custom: async ({ url, method, filters, sort, payload, query, headers }) => {
let requestUrl = `${url}?`;

if (sort) {
const { _sort, _order } = generateSort(sort);
const sortQuery = {
_sort: _sort.join(","),
_order: _order.join(","),
};
requestUrl = `${requestUrl}&${stringify(sortQuery)}`;
}

if (filters) {
const filterQuery = generateFilter(filters);
requestUrl = `${requestUrl}&${stringify(filterQuery)}`;
}

if (query) {
requestUrl = `${requestUrl}&${stringify(query)}`;
}

if (headers) {
httpClient.defaults.headers = {
...httpClient.defaults.headers,
...headers,
};
}

let axiosResponse;
switch (method) {
case "put":
case "post":
case "patch":
axiosResponse = await httpClient[method](url, payload);
break;
case "delete":
axiosResponse = await httpClient.delete(url, {
data: payload,
});
break;
default:
axiosResponse = await httpClient.get(requestUrl);
break;
}

const { data } = axiosResponse;

return Promise.resolve({ data });
},
}

Parameter Types​

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

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

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

const { data, isLoading } = useCustom({
url: `${apiURL}/posts-unique-check`,
method: "get",
config: {
query: {
title: "Foo bar",
},
},
});

Refer to the useCustom documentation for more information. β†’

Error Format​

refine expects errors to be extended from HttpError.
Axios interceptor can be used to transform the error from response before Axios returns the response to your code. Interceptors are methods which are triggered before the main method.

dataProvider.ts
...
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);
},
);
...