Appwrite
Refine provides a data provider for Appwrite, a backend as a service platform, to build CRUD applications.
@refinedev/appwrite
requires Appwrite version >= 1.0- To learn more about data fetching in Refine, check out the Data Fetching guide.
- To learn more about realtime features of Refine, check out the Realtime guide.
- Example below uses
@refinedev/antd
as the UI library but Refine is UI agnostic and you can use any UI library you want.
Installation
- npm
- pnpm
- yarn
npm i @refinedev/appwrite
pnpm add @refinedev/appwrite
yarn add @refinedev/appwrite
Usage
First, we'll create our Appwrite client and use it in our dataProvider
, authProvider
and liveProvider
.
Dependencies: @refinedev/core@latest,@refinedev/appwrite@latest
Code Files
Create Collections
We created two collections on Appwrite Database as posts
and categories
and added a relation between them.
- Category Collection
- Post Collection
- Authentication
Category Collection
:
- Title: text
Post Collection
:
- Title: text
- CategoryId: text
- Content: text
- Images: wildcard
Then we need to create an appwrite user to be able to login with Refine.
Permissions
In order to list posts and categories, you need to give read and write permission by Appwrite.
Example: Post Collection Permissions
We indicate that the read and write permission is open to everyone by giving the "*" parameter.
- Check out Appwrite's Permissions documentation for detailed information.
- Check out how you can use permissions when creating posts with Refine
Login page
Before creating CRUD pages, let's create a login page. For this we use the AuthPage
component. This component returns ready-to-use authentication pages for login
, register
, forgot password
and update password
actions.
Below we see its implementation in the App.tsx
file:
Now we can login with the user we created by Appwrite. We can then list, create and edit posts.
List Page
When defining your resources, name
must match the Appwrite Collection ID. You can change the label with the resource meta.
export const App = () => (
<Refine
// ...
resources={[
{
name: "61bc3660648a6",
meta: {
label: "Post",
},
},
]}
/>
);
Now that we've created our collections, we can create and list documents. Let's list the posts and categories that we have created by Appwrite with Refine.
Show Code
import { useMany } from "@refinedev/core";
import {
List,
TextField,
useTable,
EditButton,
ShowButton,
getDefaultSortOrder,
} from "@refinedev/antd";
import { Table, Space } from "antd";
import { IPost, ICategory } from "interfaces";
export const PostsList: React.FC = () => {
const { tableProps, sorter } = useTable<IPost>({
sorters: {
initial: [
{
field: "$id",
order: "asc",
},
],
},
});
const categoryIds =
tableProps?.dataSource?.map((item) => item.categoryId) ?? [];
const { data, isLoading } = useMany<ICategory>({
resource: "61bc4afa9ee2c",
ids: categoryIds,
queryOptions: {
enabled: categoryIds.length > 0,
},
});
return (
<List>
<Table {...tableProps} rowKey="id">
<Table.Column
dataIndex="id"
title="ID"
sorter
defaultSortOrder={getDefaultSortOrder("id", sorter)}
/>
<Table.Column dataIndex="title" title="Title" sorter />
<Table.Column
dataIndex="categoryId"
title="Category"
render={(value) => {
if (isLoading) {
return <TextField value="Loading..." />;
}
return (
<TextField
value={data?.data.find((item) => item.id === value)?.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} />
</Space>
)}
/>
</Table>
</List>
);
};
Create Page
We can now create posts and set categories from our Refine UI.
Show Code
import { useState } from "react";
import { Create, useForm, useSelect } from "@refinedev/antd";
import { Form, Input, Select, Upload } from "antd";
import { RcFile } from "antd/lib/upload/interface";
import MDEditor from "@uiw/react-md-editor";
import { IPost, ICategory } from "interfaces";
import { storage, normalizeFile } from "utility";
export const PostsCreate: React.FC = () => {
const { formProps, saveButtonProps } = useForm<IPost>();
const { selectProps: categorySelectProps } = useSelect<ICategory>({
resource: "61bc4afa9ee2c",
optionLabel: "title",
optionValue: "id",
});
return (
<Create saveButtonProps={saveButtonProps}>
<Form {...formProps} layout="vertical">
<Form.Item
label="Title"
name="title"
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<Form.Item
label="Category"
name="categoryId"
rules={[
{
required: true,
},
]}
>
<Select {...categorySelectProps} />
</Form.Item>
<Form.Item
label="Content"
name="content"
rules={[
{
required: true,
},
]}
>
<MDEditor data-color-mode="light" />
</Form.Item>
<Form.Item label="Images">
<Form.Item
name="images"
valuePropName="fileList"
normalize={normalizeFile}
noStyle
>
<Upload.Dragger
name="file"
listType="picture"
multiple
customRequest={async ({ file, onError, onSuccess }) => {
try {
const rcFile = file as RcFile;
const { $id } = await storage.createFile(
"default",
rcFile.name,
rcFile,
);
const url = storage.getFileView("default", $id);
onSuccess?.({ url }, new XMLHttpRequest());
} catch (error) {
onError?.(new Error("Upload Error"));
}
}}
>
<p className="ant-upload-text">
Drag & drop a file in this area
</p>
</Upload.Dragger>
</Form.Item>
</Form.Item>
</Form>
</Create>
);
};
By default, Read Access and Write Access are public when creating documents via Refine. If you want to restrict permissions and only allow specific users, you need to specify it in meta.
import { Permission, Role } from "@refinedev/appwrite";
const { formProps, saveButtonProps } = useForm<IPost>({
meta: {
writePermissions: [Permission.read(Role.any())],
readPermissions: [Permission.read(Role.any())],
},
});
Edit Page
You can edit the posts and categories we have created update your data.
Show Code
import React from "react";
import { Edit, useForm, useSelect } from "@refinedev/antd";
import { Form, Input, Select, Upload } from "antd";
import { RcFile } from "antd/lib/upload/interface";
import MDEditor from "@uiw/react-md-editor";
import { IPost, ICategory } from "interfaces";
import { storage, normalizeFile } from "utility";
export const PostsEdit: React.FC = () => {
const { formProps, saveButtonProps, queryResult } = useForm<IPost>();
const postData = queryResult?.data?.data;
const { selectProps: categorySelectProps } = useSelect<ICategory>({
resource: "61bc4afa9ee2c",
defaultValue: postData?.categoryId,
optionLabel: "title",
optionValue: "id",
});
return (
<Edit saveButtonProps={saveButtonProps}>
<Form {...formProps} layout="vertical">
<Form.Item
label="Title"
name="title"
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<Form.Item
label="Category"
name="categoryId"
rules={[
{
required: true,
},
]}
>
<Select {...categorySelectProps} />
</Form.Item>
<Form.Item
label="Content"
name="content"
rules={[
{
required: true,
},
]}
>
<MDEditor data-color-mode="light" />
</Form.Item>
<Form.Item label="Images">
<Form.Item
name="images"
valuePropName="fileList"
normalize={normalizeFile}
noStyle
>
<Upload.Dragger
name="file"
listType="picture"
multiple
customRequest={async ({ file, onError, onSuccess }) => {
try {
const rcFile = file as RcFile;
const { $id } = await storage.createFile(
"default",
rcFile.name,
rcFile,
);
const url = storage.getFileView("default", $id);
onSuccess?.({ url }, new XMLHttpRequest());
} catch (error) {
onError?.(new Error("Upload Error"));
}
}}
>
<p className="ant-upload-text">
Drag & drop a file in this area
</p>
</Upload.Dragger>
</Form.Item>
</Form.Item>
</Form>
</Edit>
);
};
Example
Username: demo@refine.dev
Password: demodemo
npm create refine-app@latest -- --example data-provider-appwrite