Skip to content

Commit

Permalink
support custom/multiple prisma service (#40)
Browse files Browse the repository at this point in the history
* add custom prisma service, #31

* provide PrismaClient instance

* 0.20.0-dev.1

* add forRootAsync for custom prisma module

* inject prisma client into custom prisma service
* add useFactory/useClass to forRootAsync

* 0.20.0-dev.2

* add custom location and extension docs

* add extension example

* add extensions example and docs
  • Loading branch information
marcjulian authored Jan 16, 2023
1 parent e268df7 commit 43e528c
Show file tree
Hide file tree
Showing 34 changed files with 15,758 additions and 4 deletions.
2 changes: 1 addition & 1 deletion docs/src/components/Navigation.astro
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const currentPageMatch = currentPage.endsWith('/')
<li>
<a
href={link.link}
class={`border-l pl-3 ${
class={`flex border-l pl-3 ${
currentPageMatch === link.link
? 'border-current text-violet-500'
: 'border-transparent text-gray-700 hover:border-current hover:border-gray-500 dark:text-gray-300 dark:hover:border-gray-400'
Expand Down
19 changes: 19 additions & 0 deletions docs/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@ export const navigation: Navigation[] = [
{ title: 'Prisma Logging', link: '/docs/prisma-logging' },
],
},
{
title: 'Advanced',
links: [
{
title: 'Custom Prisma Client Location',
link: '/docs/custom-prisma-client-location',
},
// { title: 'Multiple Prisma Clients', link: '' },
{
title: 'Prisma Client Extensions (Preview)',
link: '/docs/prisma-client-extensions',
},
],
},
{
title: 'Built-in Tools',
links: [
Expand Down Expand Up @@ -81,6 +95,11 @@ export const examples: Example[] = [
description: 'NestJS app with Fastify, Prisma and nestjs-prisma.',
link: 'https://github.com/notiz-dev/nestjs-prisma/tree/main/examples/fastify',
},
{
name: 'Extensions',
description: 'Using the Prisma Client extension (Preview) with nestjs-prisma',
link: 'https://github.com/notiz-dev/nestjs-prisma/tree/main/examples/extensions',
},
{
name: 'nestjs-prisma-starter',
description:
Expand Down
26 changes: 26 additions & 0 deletions docs/src/pages/docs/basic-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,32 @@ title: Basic Usage
layout: ../../layouts/Doc.astro
---

## Prisma schema

`nestjs-prisma` requires the Prisma Client to be generated to the default output location (`./node_modules/.prisma/client`). The client will be imported from `@prisma/client`.

```prisma
// prisma/schema.prisma
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
}
```

If you like to choose a **different** output location or want to use **multiple** Prisma Client, you can [customize the Prisma Client location](/docs/custom-prisma-client-location).

## PrismaModule and PrismaService

Add `PrismaModule` to the `imports` section in your `AppModule` or other modules to gain access to `PrismaService`.

```ts
Expand Down
148 changes: 148 additions & 0 deletions docs/src/pages/docs/custom-prisma-client-location.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
---
title: Custom Prisma Client Location
layout: ../../layouts/Doc.astro
---

Prisma allows you to [customize the output location](https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/generating-prisma-client#the-location-of-prisma-client) of your Prisma Client.

```prisma
generator client {
provider = "prisma-client-js"
output = "../node_modules/@notiz/prisma"
}
```

For the schema above you would import `PrismaClient` from `@notiz/prisma` instead of the default `@prisma/client`.

```diff
-import { PrismaClient } from '@prisma/client';
+import { PrismaClient } from '@notiz/prisma';

const prisma = new PrismaClient();
```

This can be useful if you want to just use a **different location** or want to use **multiple** Prisma Clients in your project.

## CustomPrismaModule and CustomPrismaService

To use `nestjs-prisma` with custom output location for Prisma Client, you need to update to `[email protected]` or later and use `CustomPrismaModule` and `CustomPrismaService`.

Import `CustomPrismaModule` and provide a **unique** name and an **instance** of your `PrismaClient`. The unique name will be used to inject the `CustomPrismaService`.

```ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

import { CustomPrismaModule } from 'nestjs-prisma';
import { PrismaClient } from '@notiz/prisma'; // 👈 update to your output directory

@Module({
imports: [
CustomPrismaModule.forRoot({
name: 'PrismaServiceAuth', // 👈 must be unique for each PrismaClient
client: new PrismaClient(), // create new instance of PrismaClient
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
```

The `PrismaClient` instance could also be used together with the new preview feature [Prisma Client extensions](/docs/prisma-client-extensions).

Use the `Inject` decorator with the **unique** name to inject `CustomPrismaService`. Additionally, `CustomPrismaService` requires you to specify the `PrismaClient` as generic for type-safety & auto-completion.

```ts
import { Inject, Injectable } from '@nestjs/common';

import { CustomPrismaService } from 'nestjs-prisma';
import { PrismaClient } from '@notiz/prisma'; // 👈 update to your output directory

@Injectable()
export class AppService {
constructor(
@Inject('PrismaServiceAuth') // 👈 use unique name to reference
private prisma: CustomPrismaService<PrismaClient> // specify PrismaClient for type-safety & auto-completion
) {}

users() {
return this.prisma.client.user.findMany();
}

user(userId: string) {
return this.prisma.client.user.findFirstOrThrow({
where: { id: userId },
});
}
}
```

You can use the auto generated queries from `PrismaClient` by accessing `client` property of `CustomPrismaService` e.g. `this.prisma.client.user.create({ data: ... });`

## Multiple Prisma Clients

If you have multiple Prisma Clients repeat the above steps for each client.

Important to register each Prisma Client with a unique name and the correct `PrismaClient` instance.

```ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

import { CustomPrismaModule } from 'nestjs-prisma';
import { PrismaClient as PrismaAuth } from '@notiz/prisma'; // 👈 update to your output directory
import { PrismaClient as PrismaCms } from '@notiz/prisma-cms'; // 👈 update to your output directory

@Module({
imports: [
CustomPrismaModule.forRoot({
name: 'PrismaServiceAuth', // 👈 must be unique for each PrismaClient
client: new PrismaAuth(),
}),
CustomPrismaModule.forRoot({
name: 'PrismaServiceCms', // 👈 must be unique for each PrismaClient
client: new PrismaCms(),
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
```

Import `CustomPrismaService` with the correct reference name and Prisma Client as type.

```ts
import { Inject, Injectable } from '@nestjs/common';

import { CustomPrismaService } from 'nestjs-prisma';
import { PrismaClient as PrismaAuth } from '@notiz/prisma'; // 👈 update to your output directory
import { PrismaClient as PrismaCms } from '@notiz/prisma-cms'; // 👈 update to your output directory

@Injectable()
export class AppService {
constructor(
@Inject('PrismaServiceAuth') // 👈 use unique name to reference
private prismaAuth: CustomPrismaService<PrismaAuth>,
@Inject('PrismaServiceCms') // 👈 use unique name to reference
private prismaCms: CustomPrismaService<PrismaCms>
) {}

users() {
return this.prismaAuth.client.user.findMany();
}

user(userId: string) {
return this.prismaAuth.client.user.findFirstOrThrow({
where: { id: userId },
});
}

posts() {
return this.prismaCms.client.post.findMany();
}
}
```
115 changes: 115 additions & 0 deletions docs/src/pages/docs/prisma-client-extensions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
---
title: Prisma Client Extensions (Preview)
layout: ../../layouts/Doc.astro
---

To use the new [Prisma Client Extensions (Preview)](https://www.prisma.io/docs/concepts/components/prisma-client/client-extensions) you must update to Prisma Client [v4.7.0](https://github.com/prisma/prisma/releases/tag/4.7.0) or later and install `[email protected]` or later.

Follow this guide or checkout the [extensions example](https://github.com/notiz-dev/nestjs-prisma/tree/main/examples/extensions).

## Enable preview feature

Enable `clientExtensions` preview in your Prisma schema and generate Prisma Client again.

```prisma
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
previewFeatures = ["clientExtensions"]
}
model User {
id String @id @default(cuid())
email String @unique
name String?
}
```

## Prisma Extension

Create a file for your Prisma extension for example `prisma.extension.ts`

```ts
import { PrismaClient } from '@prisma/client';

export const extendedPrismaClient = new PrismaClient().$extends({
model: {
user: {
findByEmail: async (email: string) => {
return extendedPrismaClient.user.findFirstOrThrow({ where: { email } });
},
},
},
});

export type extendedPrismaClient = typeof extendedPrismaClient;
```

Register your extended Prisma Client using `CustomPrismaModule.forRootAsync`.

```ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

import { CustomPrismaModule } from 'nestjs-prisma';
import { extendedPrismaClient } from './prisma.extension';

@Module({
imports: [
// ✅ use `forRootAsync` when using `PrismaClient().$extends({})`
CustomPrismaModule.forRootAsync({
name: 'PrismaService',
useFactory: () => {
return extendedPrismaClient;
},
}),
// ❌ error: 'getOwnPropertyDescriptor' on proxy
// CustomPrismaModule.forRoot({
// name: 'PrismaServiceAuth',
// client: new PrismaClient().$extends({}),
// }),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
```

Inject `CustomPrismaService` into your controller/service and use the extended Prisma Client for type-safety.

```ts
import { Inject, Injectable } from '@nestjs/common';
import { CustomPrismaService } from 'nestjs-prisma';
import { extendedPrismaClient } from './prisma.extension';

@Injectable()
export class AppService {
constructor(
// ✅ use `prismaClient` from extension for correct type-safety
@Inject('PrismaService')
private prismaService: CustomPrismaService<extendedPrismaClient>
) {}

users() {
return this.prismaService.client.user.findMany();
}

user(email: string) {
// 🦾 use new `findByEmail`
return this.prismaService.client.user.findByEmail(email);
}
}
```

Now you have access to your extensions `this.prismaService.client.user.findByEmail(email)`.

## Type issues with Prisma Client v4.7.0

Change `declaration` to `false` in your `tsconfig.json` - workaround for https://github.com/prisma/prisma/issues/16536#issuecomment-1332055501

`The inferred type of this node exceeds the maximum length the compiler will serialize. An explicit type annotation is needed.`
25 changes: 25 additions & 0 deletions examples/extensions/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir : __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};
Loading

0 comments on commit 43e528c

Please sign in to comment.