Skip to main content
Version: 2.xx.xx

Appwrite

Introduction

refine and Appwrite work in harmony, offering you quick development options. You can use your data (API, Database) very simply by using refine's Appwrite data provider.

You can only focus on your UI as we can handle your data quickly and simply.

caution

This guide has been prepared assuming you know the basics of refine. If you haven't learned these basics yet, we recommend reading the Tutorial.

Setup

npm install @pankod/refine-appwrite

Usage

It is very simple to use and consists of two steps. First, define your Appwrite project id and then give it to the dataprovider.

Appwrite Client

appwriteClient.ts
import { Appwrite } from "@pankod/refine-appwrite";

const APPWRITE_URL = "http://localhost/v1";
const APPWRITE_PROJECT = "YOUR_APPWRITE_PROJECT_ID";

const appwriteClient = new Appwrite();

appwriteClient.setEndpoint(APPWRITE_URL).setProject(APPWRITE_PROJECT);

export appwriteClient;

Authprovider

authProvider.ts
import { AuthProvider } from "@pankod/refine";

import appwriteClient from "./appwriteClient";

export const authProvider: AuthProvider = {
login: ({ email, password }) => {
return appwriteClient.account.createSession(email, password);
},
logout: async () => {
await appwriteClient.account.deleteSession("current");

return "/";
},
checkError: () => Promise.resolve(),
checkAuth: async () => {
const session = await appwriteClient.account.getSession("current");

if (session) {
return Promise.resolve();
}

return Promise.reject();
},
getPermissions: () => Promise.resolve(),
getUserIdentity: async () => {
const user = await appwriteClient.account.get();

if (user) {
return user;
}
},
};
App.tsx
import { Refine, AuthProvider } from "@pankod/refine";
import { dataProvider } from "@pankod/refine-appwrite";
import routerProvider from "@pankod/refine-react-router";

import appwriteClient from "./appwriteClient";
import authProvider from "./authProvider";

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

Create Collections

We created two collections on Appwrite Database as posts and categories and added a relation between them.

Category Collection:

  • Title: text
category

Post Collection:

  • Title: text
  • CategoryId: text
  • Content: text
  • Images: wilcard
posts

Then we need to create an appwrite user to be able to login with refine.

user

Permissions

In order to list posts and categories, you need to give read and write permission by Appwrite.

Example: Post Collection Permissons

permission

We indicate that the read and write permission is open to everyone by giving the "*" parameter.

Refer to the Appwrite Permissions documentation for detailed information.β†’

Check out how you can use permissions when creating posts with refine β†’

Login page​

refine default login screen allows you to login with username. Appwrite allows login with email, therefore we need to override the login page.

Show Code

pages/login.tsx
import React from "react";
import {
Row,
Col,
AntdLayout,
Card,
Typography,
Form,
Input,
Button,
Checkbox,
} from "@pankod/refine";
import "./styles.css";

import { useLogin } from "@pankod/refine";

const { Text, Title } = Typography;

export interface ILoginForm {
username: string;
password: string;
remember: boolean;
}

export const Login: React.FC = () => {
const [form] = Form.useForm<ILoginForm>();

const { mutate: login } = useLogin<ILoginForm>();

const CardTitle = (
<Title level={3} className="title">
Sign in your account
</Title>
);

return (
<AntdLayout className="layout">
<Row
justify="center"
align="middle"
style={{
height: "100vh",
}}
>
<Col xs={22}>
<div className="container">
<div className="imageContainer">
<img src="./refine.svg" alt="Refine Logo" />
</div>
<Card title={CardTitle} headStyle={{ borderBottom: 0 }}>
<Form<ILoginForm>
layout="vertical"
form={form}
onFinish={(values) => {
login(values);
}}
requiredMark={false}
>
<Form.Item
name="email"
label="Email"
rules={[{ required: true, type: "email" }]}
>
<Input size="large" placeholder="Email" />
</Form.Item>
<Form.Item
name="password"
label="Password"
rules={[{ required: true }]}
style={{ marginBottom: "12px" }}
>
<Input
type="password"
placeholder="●●●●●●●●"
size="large"
/>
</Form.Item>
<div style={{ marginBottom: "12px" }}>
<Form.Item
name="remember"
valuePropName="checked"
noStyle
>
<Checkbox
style={{
fontSize: "12px",
}}
>
Remember me
</Checkbox>
</Form.Item>

<a
style={{
float: "right",
fontSize: "12px",
}}
href="#"
>
Forgot password?
</a>
</div>
<Button
type="primary"
size="large"
htmlType="submit"
block
>
Sign in
</Button>
</Form>
<div style={{ marginTop: 8 }}>
<Text style={{ fontSize: 12 }}>
Don’t have an account?{" "}
<a href="#" style={{ fontWeight: "bold" }}>
Sign up
</a>
</Text>
</div>
</Card>
</div>
</Col>
</Row>
</AntdLayout>
);
};

login

Now we can login with the user we created by Appwrite. We can then list, create and edit posts.

tip

refine resource name must be the same as Appwrite Collection ID. You can change your label with resource options.

const App: React.FC = () => {
return (
<Refine
dataProvider={dataProvider(appwriteClient)}
authProvider={authProvider}
routerProvider={routerProvider}
LoginPage={Login}
resources={[
{
name: "61bc3660648a6",
options: {
label: "Post",
},
},
]}
/>
);
};

export default App;

List Page

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 {
List,
Table,
TextField,
useTable,
IResourceComponentsProps,
Space,
EditButton,
ShowButton,
useMany,
getDefaultSortOrder,
} from "@pankod/refine";

import { IPost, ICategory } from "interfaces";

export const PostsList: React.FC<IResourceComponentsProps> = () => {
const { tableProps, sorter } = useTable<IPost>({
initialSorter: [
{
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>
);
};

list

Create Page

We can now create posts and set categories from our refine UI.

Show Code

import { useState } from "react";
import {
Create,
Form,
Input,
IResourceComponentsProps,
Select,
Upload,
useForm,
useSelect,
RcFile,
} from "@pankod/refine";

import ReactMarkdown from "react-markdown";
import ReactMde from "react-mde";

import "react-mde/lib/styles/css/react-mde-all.css";

import { IPost, ICategory } from "interfaces";
import { appwriteClient, normalizeFile } from "utility";

export const PostsCreate: React.FC<IResourceComponentsProps> = () => {
const { formProps, saveButtonProps } = useForm<IPost>();

const { selectProps: categorySelectProps } = useSelect<ICategory>({
resource: "61bc4afa9ee2c",
optionLabel: "title",
optionValue: "id",
});

const [selectedTab, setSelectedTab] =
useState<"write" | "preview">("write");

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,
},
]}
>
<ReactMde
selectedTab={selectedTab}
onTabChange={setSelectedTab}
generateMarkdownPreview={(markdown) =>
Promise.resolve(
<ReactMarkdown>{markdown}</ReactMarkdown>,
)
}
/>
</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 appwriteClient.storage.createFile(
rcFile,
);

const url =
appwriteClient.storage.getFileView($id);

onSuccess?.({ url }, new XMLHttpRequest());
} catch (error) {
onError?.(new Error("Upload Error"));
}
}}
>
<p className="ant-upload-text">
Drag &amp; drop a file in this area
</p>
</Upload.Dragger>
</Form.Item>
</Form.Item>
</Form>
</Create>
);
};

create

tip

As we mentioned above, we need permissions to list or create documents in Appwrite. By default, Read Access and Write Access are public when creating documents from refine UI.

If you want to restrict permissions and only allow specific users, you need to specify it in metaData.

const { formProps, saveButtonProps } = useForm<IPost>({
metaData: {
writePermissions: ["User ID, Team ID, or Role"],
readPermissions: ["User ID, Team ID, or Role"]
}
});

Edit Page

You can edit the posts and categories we have created update your data.

Show Code

import React, { useState } from "react";
import {
Edit,
Form,
Input,
IResourceComponentsProps,
RcFile,
Select,
Upload,
useForm,
useSelect,
} from "@pankod/refine";

import ReactMarkdown from "react-markdown";
import ReactMde from "react-mde";

import "react-mde/lib/styles/css/react-mde-all.css";

import { IPost, ICategory } from "interfaces";
import { appwriteClient, normalizeFile } from "utility";

export const PostsEdit: React.FC<IResourceComponentsProps> = () => {
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",
});

const [selectedTab, setSelectedTab] =
useState<"write" | "preview">("write");

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,
},
]}
>
<ReactMde
selectedTab={selectedTab}
onTabChange={setSelectedTab}
generateMarkdownPreview={(markdown) =>
Promise.resolve(
<ReactMarkdown>{markdown}</ReactMarkdown>,
)
}
/>
</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 appwriteClient.storage.createFile(
rcFile,
);

const url =
appwriteClient.storage.getFileView($id);

onSuccess?.({ url }, new XMLHttpRequest());
} catch (error) {
onError?.(new Error("Upload Error"));
}
}}
>
<p className="ant-upload-text">
Drag &amp; drop a file in this area
</p>
</Upload.Dragger>
</Form.Item>
</Form.Item>
</Form>
</Edit>
);
};

edit

Live Codesandbox Example

Username: demo@refine.dev

Password: demodemo