Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(docs): update Multitenancy documentation #6436

Merged
merged 16 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions documentation/blog/2023-08-12-react-admin-vs-refine.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ These small features lead to a huge difference in the development experience, yo

Check out the following example of multitenant app built with Refine:

- [Multitenancy with Refine + Strapi](/docs/guides-concepts/multi-tenancy/#strapi)
- [Multitenancy with Refine + Strapi](/docs/guides-concepts/multitenancy)

## Differences in supported UI frameworks between Refine and react-admin,

Expand Down Expand Up @@ -247,7 +247,7 @@ While both react-admin and Refine offer access control features, Refine takes it

6. **Multitenancy Support:**

Refine provides built-in support for [multitenancy](/docs/guides-concepts/multi-tenancy/#strapi), allowing developers to create applications that serve multiple tenants with separate data and settings.
Refine provides built-in support for [multitenancy](/docs/guides-concepts/multitenancy), allowing developers to create applications that serve multiple tenants with separate data and settings.

Refer to [this](#multitenancy) section for details.

Expand Down
264 changes: 264 additions & 0 deletions documentation/docs/enterprise-edition/multitenancy/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
---
title: Multitenancy
guide: true
---

# Multitenancy <GuideBadge id="guides-concepts/multitenancy" />

Refine's Enterprise Edition provides built-in support for [Multitenancy](/docs/guides-concepts/multitenancy/). This feature allows you to build applications that can serve multiple tenants with a single codebase with help of
pre-built components and hooks with minimal configuration.

## Installation

This package is included in Refine's Enterprise Edition. To learn more about Refine's Enterprise Edition, please [contact us](https://s.refine.dev/okta-enterprise).

<InstallPackagesCommand args="@refinedev-ee/enterprise @refinedev-ee/multitenancy">

```yml title=".npmrc"
# A registry with the auth token should be added for the @refinedev-ee scope
@refinedev-ee:registry=https://registry.npmjs.org/
//registry.npmjs.org/:_authToken=$NPM_TOKEN
```

</InstallPackagesCommand>

## Usage

To use the multitenancy feature, we need to wrap our application with the `<RefineEnterprise />` component and provide the `multitenancyProvider` prop.

```tsx
import { RefineEnterprise } from "@refinedev-ee/enterprise";
import { useRouterAdapter, WithTenant } from "@refinedev-ee/multitenancy";

type Tenant = {
id: string;
name: string;
};

// ... other imports

const App = () => {
return (
<RefineEnterprise
// ... other props
multitenancyProvider={{
adapter: useRouterAdapter(),
fetchTenants: async () => {
const response = await dataProvider("<API_URL>").getList<Tenant>({
resource: "tenants",
pagination: {
mode: "off",
},
});
const tenants = response.data;
const defaultTenant = tenants[0];

return {
tenants,
defaultTenant,
};
},
}}
>
<WithTenant
fallback={<div>Tenant not found</div>}
loadingComponent={<div>Loading...</div>}
>
{/* Your app code */}
</WithTenant>
</RefineEnterprise>
);
};
```

## multitenancyProvider

The `multitenancyProvider` is a prop that accepts an object with two properties: `adapter` and `fetchTenants`.

### fetchTenants

The `fetchTenants` function is a crucial part of the `multitenancyProvider`, responsible for fetching tenant data from an API or data source and determining the default tenant for the app. The function should return an object with two properties: `tenants` and `defaultTenant`.

### adapters

Adapters define where tenant information is stored. Refine offers `useRouterAdapter` for storing tenants in the URL and `useLocalStorageAdapter` for local storage.

#### useRouterAdapter

Extracts the tenantId from the URL and updates the route when the tenant changes.

```tsx
import { useRouterAdapter } from "@refinedev-ee/multitenancy";

const multitenancyProvider = {
adapter: useRouterAdapter({
// The parameter name to use in the URL. (eg: localhost:3000/:tenantId/products)
parameterName: "tenantId",
// The tenant field to use in the route params. (eg: localhost:3000/:tenantId/products)
parameterKey: "id",
// Determines if the query string should be used to get the tenant instead of the route params. (eg: localhost:3000/products?tenantId=1)
useQueryString: false,
}),
fetchTenants: async () => {
// Fetch tenants from the API
},
};
```

#### useLocalStorageAdapter

Retrieves tenantId from local storage and updates it when the tenant changes.

```tsx
import { useLocalStorageAdapter } from "@refinedev-ee/multitenancy";

const multitenancyProvider = {
adapter: useLocalStorageAdapter({
// The key to use in the local storage. (eg: localStorage.getItem(key))
storageKey: "tenantId",
}),
fetchTenants: async () => {
// Fetch tenants from the API
},
};
```

## Components

### WithTenant

The `<WithTenant />` component is required to wrap your app code. It fetches `tenants`, handling the loading state and error state.

```tsx
import { RefineEnterprise } from "@refinedev-ee/enterprise";
import { WithTenant } from "@refinedev-ee/multitenancy";

<WithTenant
// render a component when the tenant is not available.
fallback={<div>Tenant not found</div>}
// render a component while the tenant is loading.
loadingComponent={<div>Loading...</div>}
>
{/* Your app code */}
</WithTenant>;
```

### Tenant Select components

These components allow users to select a tenant from a list of available tenants. They are automatically change the current tenant when a new tenant is selected from the list.

<Tabs wrapContent={false}>

<TabItem value="Ant Design">

```tsx
import { TenantSelect } from "@refinedev-ee/multitenancy/antd";

<TenantSelect
// Specifies the tenant object field to display in the select component.
optionLabel="title"
// Specifies the tenant object field to use as the value in the select component.
optionValue="id"
// Event handler for when a tenant is selected, receiving the selected tenant as an argument.
onChange={(tenant) => console.log(tenant)}
// Function to sort tenants.
sortTenants={(a, b) => a.name.localeCompare(b.name)}
/>;
```

</TabItem>

<TabItem value="Material UI">

```tsx
import { TenantSelect } from "@refinedev-ee/multitenancy/mui";

<TenantSelect
// Specifies the tenant object field to display in the select component.
optionLabel="title"
// Specifies the tenant object field to use as the value in the select component.
optionValue="id"
// Event handler for when a tenant is selected, receiving the selected tenant as an argument.
onChange={(tenant) => console.log(tenant)}
// Function to sort tenants.
sortTenants={(a, b) => a.name.localeCompare(b.name)}
/>;
```

</TabItem>

</Tabs>

### Hooks

Refine provides hooks to interact with the multitenancy feature.

#### useMultitenancy

The `useMultitenancy` hook is used to interact with the multitenancy context.

```tsx
import { useMultitenancy } from "@refinedev-ee/multitenancy";

const {
// The current tenant object.
tenant,
// The list of available tenants.
tenants,
// The loading state of the tenants.
isLoading,
// It triggers `authProvider.fetchTenants` to fetch tenants.
fetchTenants,
// It sets the current tenant. It accepts a tenant object as an argument.
setTenant,
// It deletes the current tenant.
deleteTenant,
} = useMultitenancy();
```

## Handling Multi-tenant Requests in Data Providers

Refine automatically sends the `tenantId` to the data provider in the `meta` object. You can access the `tenantId` in the data provider and use it to fetch tenant-specific data.

To customize the data providers, you can override each method in the data provider instance or use the [`swizzle`](/docs/packages/cli/#swizzle) command to be fully able to customize the data provider for your needs.

An example implementation of a custom `getList` method is shown below.

```ts
import dataProvider from "@refinedev/simple-rest";

const API_URL = "<API_URL>";
const baseDataProvider = dataProvider(API_URL);

const customDataProvider = {
...baseDataProvider,
getList: async ({ resource, filters = [], meta, ...props }) => {
const { tenantId } = meta;

// We're adding the tenantId to the filters
// Your API may have a different way of handling this
if (meta?.tenantId) {
filters.push({
field: "organization",
operator: "eq",
value: meta.tenantId,
});
}

// Call the base data provider's getList method with the updated filters
return baseDataProvider.getList({
resource,
filters,
meta,
...props,
});
},
};
```

### Examples

Here are some examples of multi-tenant apps built with [Refine Enterprise Edition](https://refine.dev/enterprise/):

- [Multitenancy App with Strapi](https://refine.dev/templates/multitenancy-strapi/)
- [Isolated Multitenancy App with Rest API](https://multitenancy-isolated.netlify.app/)

This file was deleted.

Loading
Loading