NestJS + Prisma + PostgreSQL ~ RLS multi-tenancy using nestjs-prisma, nestjs-cls and Prisma Client Extensions
Tags: prisma, postgres, node, nestjs
Published: 2023-05-11 23:33:00

([Github repository for this article](https://github.com/moofoo/nestjs-prisma-postgres-tenancy))

Here's a simple way to do multi-tenancy in NestJS with Prisma using Client Extensions and AsyncLocalStorage (via nestjs-cls). 

This post describes the basics of a _generic_ NestJS implementation. Look at [this repo](https://github.com/moofoo/nestjs-prisma-postgres-tenancy) for a complete and functional example app. Checkout branch [`async-hooks`](https://github.com/moofoo/nestjs-prisma-postgres-tenancy/tree/async-hooks) to see the AsyncLocalStorage-based implementation (which relates to this post). The repo also demonstrates how to use request-scoped providers ([`main` branch](https://github.com/moofoo/nestjs-prisma-postgres-tenancy)) and durable request-scoped providers ([`durable` branch](https://github.com/moofoo/nestjs-prisma-postgres-tenancy/tree/durable)).

Step #1: Setup AsyncLocalStorage using nestjs-cls in your bootstrap function:

```typescript
// main.ts
import {ClsMiddleware} from 'nestjs-cls';

async function bootstrap() {
  // init app...

  app.use(
    new ClsMiddleware({
      async setup(cls, req) {
        cls.set('TENANT_ID', req.params('tenant_id'));
      },
    }).use
  );

  // etc
}
```

Step #2: Add `ClsModule.forRoot({ global:true })` to your App Module (app.module.ts) imports.

Step #3: Create a file that exports a custom Factory Provider that returns an extended Prisma client.

```typescript
// prisma-tenancy.provider.ts
import { PrismaModule, PrismaService } from 'nestjs-prisma';
import { ClsService } from 'nestjs-cls';

const useFactory = (prisma: PrismaService, store: ClsService) => {
    return prisma.$extends({
        query: {
            $allModels: {
                async $allOperations({ args, query }) {
                    const tenantId = store.get('TENANT_ID');


                    const [, result] = await prisma.$transaction([
                        prisma.$executeRaw`SELECT set_config('tenancy.tenant_id', ${`${tenantId || 0}`}, TRUE)`,
                        query(args),
                    ]);
                    
                    return result;
                },
            },
        },
    });
};

export type ExtendedTenantClient = ReturnType<typeof useFactory>;

export const TENANCY_CLIENT_TOKEN = Symbol('TENANCY_CLIENT_TOKEN');

export const PrismaTenancyClientProvider = {
    provide: TENANCY_CLIENT_TOKEN,
    imports: [PrismaModule],
    inject: [PrismaService, ClsService],
    useFactory
};
```

Step #4: Create a module that exports the above Factory Provider

```typescript
// prisma-tenancy.module.ts
import { Module, Global } from '@nestjs/common';
import { PrismaTenancyClientProvider, TENANCY_CLIENT_TOKEN } from './prisma-tenancy.provider';
import { PrismaModule } from 'nestjs-prisma';

@Global()
@Module({
    imports: [PrismaModule],
    providers: [PrismaTenancyClientProvider],
    exports: [TENANCY_CLIENT_TOKEN]
})
export class PrismaTenancyModule { }
```

Step #5: Add the module above to your root `app.module.ts` imports.

Now, you can inject the extended client using the token for your custom provider (`TENANCY_CLIENT_TOKEN`, in this case).

```typescript
import {Injectable, Inject} from "@nestjs/common";
import {
  TENANCY_CLIENT_TOKEN,
  ExtendedTenantClient,
} from "./prisma-tenancy.provider";

@Injectable()
export class SomeService {
  constructor(
    @Inject(TENANCY_CLIENT_TOKEN) private readonly prisma: ExtendedTenantClient
  ) {}

  // etc
}
```

## Useful Links
- [Prisma Client Extensions](https://www.prisma.io/docs/concepts/components/prisma-client/client-extensions)
- [Prisma Client Extensions RLS Example](https://github.com/prisma/prisma-client-extensions/tree/main/row-level-security)
- [Prisma Query Extension](https://www.prisma.io/docs/concepts/components/prisma-client/client-extensions/query)
- [Prisma Transactions and batch queries](https://www.prisma.io/docs/concepts/components/prisma-client/transactions)
- [Prisma Raw database access](https://www.prisma.io/docs/concepts/components/prisma-client/raw-database-access#executeraw)
- [Postgres Row Security Policies](https://www.postgresql.org/docs/current/ddl-rowsecurity.html)
- [NestJS Custom Factory Provider](https://docs.nestjs.com/fundamentals/custom-providers#factory-providers-usefactory)
- [NestJS Recipe: AsyncLocalStorage](https://docs.nestjs.com/recipes/async-local-storage)
- [NestJS Recipe: Prisma](https://docs.nestjs.com/recipes/prisma)
- [nestjs-cls](https://github.com/Papooch/nestjs-cls)
- [nestjs-prisma](https://nestjs-prisma.dev/)