Skip to main content
Version: 4.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.

INFORMATION

Appwrite version >= 1.0 is required

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 @refinedev/appwrite
CAUTION

To make this example more visual, we used the @refinedev/antd package. If you are using Refine headless, you need to provide the components, hooks or helpers imported from the @refinedev/antd package.

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.

Creating Appwrite Client​

appwriteClient.ts
import { Appwrite, Account, Storage } from "@refinedev/appwrite";

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

const appwriteClient = new Appwrite();

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

// for authentication
const account = new Account(appwriteClient);
// for file upload
const storage = new Storage(appwriteClient);

export { appwriteClient, account, storage };

Creating Auth Provider​

authProvider.ts
import { AuthBindings } from "@refinedev/core";

import { account } from "./appwriteClient";

const authProvider: AuthBindings = {
login: async ({ email, password }) => {
try {
await account.createEmailSession(email, password);
return {
success: true,
redirectTo: "/",
};
} catch (e) {
const { type, message, code } = e as AppwriteException;
return {
success: false,
error: {
message,
name: `${code} - ${type}`,
},
};
}
},
logout: async () => {
try {
await account.deleteSession("current");
} catch (error: any) {
return {
success: false,
error,
};
}

return {
success: true,
redirectTo: "/login",
};
},
onError: async (error) => {
console.error(error);
return { error };
},
check: async () => {
try {
const session = await account.get();

if (session) {
return {
authenticated: true,
};
}
} catch (error: any) {
return {
authenticated: false,
error: error,
logout: true,
redirectTo: "/login",
};
}

return {
authenticated: false,
error: {
message: "Check failed",
name: "Session not found",
},
logout: true,
redirectTo: "/login",
};
},
getPermissions: async () => null,
getIdentity: async () => {
const user = await account.get();

if (user) {
return user;
}

return null;
},
};

Configure Refine Component​

App.tsx
import { Refine, AuthBindings } from "@refinedev/core";
import { Layout, ReadyPage, notificationProvider, ErrorComponent } from "@refinedev/antd";
import routerProvider from "@refinedev/react-router-v6";

import { BrowserRouter, Routes, Route, Outlet } from "react-router-dom";

import { dataProvider, liveProvider } from "@refinedev/appwrite";

import { appwriteClient, account } from "./appwriteClient";
import authProvider from "./authProvider";

const App: React.FC = () => {
return (
<BrowserRouter>
<Refine
dataProvider={dataProvider(appwriteClient, {
databaseId: "default",
})}
liveProvider={liveProvider(appwriteClient, {
databaseId: "default",
})}
options={{ liveMode: "auto" }}
authProvider={authProvider}
routerProvider={routerProvider}
notificationProvider={notificationProvider}
>
{/* ... */}
</Refine>
</BrowserRouter>
);
};
Live/Realtime πŸš€

@refinedev/appwrite package supports Live/Realtime Provider natively πŸš€

Refer to the Live/Realtime Provider docs for detailed usage. β†’

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: wildcard
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 Permissions

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​​

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:

localhost:5173
// src/App.tsx

import { Refine, Authenticated } from "@refinedev/core";
import routerProvider, { CatchAllNavigate, NavigateToResource } from "@refinedev/react-router-v6";
import { BrowserRouter, Routes, Route, Outlet } from "react-router-dom";
import { dataProvider, liveProvider } from "@refinedev/appwrite";
import {
ThemedLayoutV2,
RefineThemes,
notificationProvider,
List,
EditButton,
ShowButton,
useTable,
AuthPage,
ErrorComponent,
} from "@refinedev/antd";
import { ConfigProvider, Layout, Table, Space } from "antd";

const App: React.FC = () => {
return (
<BrowserRouter>
<ConfigProvider theme={RefineThemes.Blue}>
<Refine
dataProvider={dataProvider(appwriteClient, {
databaseId: "default",
})}
liveProvider={liveProvider(appwriteClient, {
databaseId: "default",
})}
authProvider={authProvider}
routerProvider={routerProvider}
resources={[
{
name: "61c43ad33b857",
list: "/posts",
create: "/posts/create",
edit: "/posts/edit/:id",
show: "/posts/show/:id",
meta: {
label: "Post",
},
},
]}
notificationProvider={notificationProvider}
options={{
liveMode: "auto",
syncWithLocation: true,
warnWhenUnsavedChanges: true,
}}
>
<Routes>
<Route
element={
<Authenticated fallback={<CatchAllNavigate to="/login" />}>
<ThemedLayoutV2>
<Outlet />
</ThemedLayoutV2>
</Authenticated>
}
>
<Route index element={<NavigateToResource resource="61c43ad33b857" />} />

<Route path="/posts">
<Route index element={<PostList />} />
<Route path="create" element={<PostCreate />} />
<Route path="edit/:id" element={<PostEdit />} />
<Route path="show/:id" element={<PostShow />} />
</Route>
</Route>

<Route
element={
<Authenticated fallback={<Outlet />}>
<NavigateToResource resource="61c43ad33b857" />
</Authenticated>
}
>
<Route
path="/login"
element={
<AuthPage
type="login"
formProps={{
initialValues: {
email: "demo@refine.dev",
password: "demodemo",
},
}}
/>
}
/>
</Route>

<Route
element={
<Authenticated>
<ThemedLayoutV2>
<Outlet />
</ThemedLayoutV2>
</Authenticated>
}
>
<Route path="*" element={<ErrorComponent />} />
</Route>
</Routes>
</Refine>
</ConfigProvider>
</BrowserRouter>
);
};

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 meta.

const App: React.FC = () => {
return (
<Refine
dataProvider={dataProvider(appwriteClient, {
databaseId: "default",
})}
liveProvider={liveProvider(appwriteClient, {
databaseId: "default",
})}
options={{ liveMode: "auto" }}
authProvider={authProvider}
routerProvider={routerProvider}
Layout={Layout}
ReadyPage={ReadyPage}
notificationProvider={notificationProvider}
catchAll={<ErrorComponent />}
LoginPage={Login}
resources={[
{
name: "61bc3660648a6",
meta: {
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 { 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>
);
};

localhost:5173

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 &amp; drop a file in this area</p>
</Upload.Dragger>
</Form.Item>
</Form.Item>
</Form>
</Create>
);
};

localhost:5173
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 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 &amp; drop a file in this area</p>
</Upload.Dragger>
</Form.Item>
</Form.Item>
</Form>
</Edit>
);
};

localhost:5173

Example​

ADDITIONAL INFO

Username: demo@refine.dev

Password: demodemo

Run on your local
npm create refine-app@latest -- --example data-provider-appwrite