Skip to content

Commit

Permalink
feat(docs): update Multitenancy documentation (refinedev#6436)
Browse files Browse the repository at this point in the history
  • Loading branch information
alicanerdurmaz authored Nov 8, 2024
1 parent 493af2f commit 66a1261
Show file tree
Hide file tree
Showing 73 changed files with 800 additions and 5,006 deletions.
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

0 comments on commit 66a1261

Please sign in to comment.