nestjs-prisma

Schematics to add Prisma support to a NestJS application

Usage no npm install needed!

<script type="module">
  import nestjsPrisma from 'https://cdn.skypack.dev/nestjs-prisma';
</script>

README

NestJS Prisma Library and Schematics

npm version Lib and Schematics CI

Add Prisma support to your NestJS application.

Installation

nest add nestjs-prisma

Example output

✔ Package installation in progress... ☕
Starting library setup...
? Which datasource provider do you want to use for `prisma init`? postgresql
? Do you like to Dockerize your application? (Supports postgresql and mysql) Yes
    ✅️ Added prisma@latest
    ✅️ Added @prisma/client@latest
    ✅️ Added Prisma scripts [6]
    ✅️ Added Prisma Seed script
    ✅️ Added Docker file
    ✅️ Added Docker Compose and .env
    ✅️ Add "prisma" directory to "excludes" in tsconfig.build.json
CREATE .dockerignore (42 bytes)
CREATE Dockerfile (455 bytes)
CREATE .env (642 bytes)
CREATE docker-compose.yml (497 bytes)
UPDATE package.json (2754 bytes)
UPDATE tsconfig.build.json (130 bytes)
✔ Packages installed successfully.
✔ Packages installed successfully.
    ✅️ Initialized Prisma - Datasource postgresql

Use PrismaService and PrismaModule provided by nestjs-prisma

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

import { Module } from '@nestjs/common';
import { PrismaModule } from 'nestjs-prisma';

@Module({
  imports: [PrismaModule.forRoot()],
})
export class AppModule {}

Shutdown Hook

Handle Prisma shutdown signal to shutdown your Nest application.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { PrismaService } from 'nestjs-prisma';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // enable shutdown hook
  const prismaService: PrismaService = app.get(PrismaService);
  prismaService.enableShutdownHooks(app);

  await app.listen(3000);
}
bootstrap();

Prisma Middleware

Apply Prisma Middlewares with PrismaModule

import { Module } from '@nestjs/common';
import { PrismaModule } from 'nestjs-prisma';
import { loggingMiddleware } from './logging-middleware';

@Module({
  imports: [
    PrismaModule.forRoot({
      prismaServiceOptions: {
        middlewares: [
          async (params, next) => {
            // Before query: change params
            const result = await next(params);
            // After query: result
            return result;
          },
        ], // see example loggingMiddleware below
      },
    }),
  ],
})
export class AppModule {}

Here is an example for using a Logging middleware.

Create your Prisma Middleware and export it as a function

// src/logging-middleware.ts
import { Prisma } from '@prisma/client';

export function loggingMiddleware(): Prisma.Middleware {
  return async (params, next) => {
    const before = Date.now();

    const result = await next(params);

    const after = Date.now();

    console.log(
      `Query ${params.model}.${params.action} took ${after - before}ms`,
    );

    return result;
  };
}

Now import your your Middleware and add the function into the middlewares array.

import { Module } from '@nestjs/common';
import { PrismaModule } from 'nestjs-prisma';
import { loggingMiddleware } from './logging-middleware';

@Module({
  imports: [
    PrismaModule.forRoot({
      prismaServiceOptions: {
        middlewares: [loggingMiddleware()],
      },
    }),
  ],
})
export class AppModule {}

PrismaModule configuration

PrismaModule allows to be used globally and to pass options to the PrismaClient.

import { Module } from '@nestjs/common';
import { PrismaModule } from 'nestjs-prisma';

@Module({
  imports: [
    PrismaModule.forRoot({
      isGlobal: true,
      prismaServiceOptions: {
        prismaOptions: { log: ['info'] },
        explicitConnect: true,
      },
    }),
  ],
})
export class AppModule {}

Additionally, PrismaModule provides a forRootAsync to pass options asynchronously. One option is to use a factory function:

import { Module } from '@nestjs/common';
import { PrismaModule } from 'nestjs-prisma';

@Module({
  imports: [
    PrismaModule.forRootAsync({
      isGlobal: true,
      useFactory: () => ({
        prismaOptions: {
          log: ['info', 'query'],
        },
        explicitConnect: false,
      }),
    }),
  ],
})
export class AppModule {}

You can inject dependencies such as ConfigModule to load options from .env files.

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { PrismaModule } from 'nestjs-prisma';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
    PrismaModule.forRootAsync({
      isGlobal: true,
      useFactory: async (configService: ConfigService) => {
        return {
          prismaOptions: {
            log: [configService.get('log')],
            datasources: {
              db: {
                url: configService.get('DATABASE_URL'),
              },
            },
          },
          explicitConnect: configService.get('explicit'),
        };
      },
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

Alternatively, you can use a class instead of a factory:

import { Module } from '@nestjs/common';
import { PrismaModule } from 'nestjs-prisma';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
    PrismaModule.forRootAsync({
      isGlobal: true,
      useClass: PrismaConfigService,
    }),
  ],
})
export class AppModule {}

Create the PrismaConfigService and extend it with the PrismaOptionsFactory

import { Injectable } from '@nestjs/common';
import { PrismaOptionsFactory, PrismaServiceOptions } from '.nestjs-prisma';

@Injectable()
export class PrismaConfigService implements PrismaOptionsFactory {
  constructor() {
    // TODO inject any other service here like the `ConfigService`
  }

  createPrismaOptions(): PrismaServiceOptions | Promise<PrismaServiceOptions> {
    return {
      prismaOptions: {
        log: ['info', 'query'],
      },
      explicitConnect: true,
    };
  }
}

Generate custom PrismaService and PrismaModule

nest add nestjs-prisma --addPrismaService

Add the flag --addPrismaService if you like to generate your own PrismaService and PrismaModule for further customizations. Add PrismaModule to the imports section in your AppModule or other modules to gain access to PrismaService.

import { Module } from '@nestjs/common';
import { PrismaModule } from './prisma/prisma.module';

@Module({
  imports: [PrismaModule],
})
export class AppModule {}

Note: It is safe to remove nestjs-prisma as dependency otherwise you have two import suggestions for PrismaService and PrismaModule.

PrismaClientExceptionFilter

nestjs-prisma provides an PrismaClientExceptionFilter to catch unhandled PrismaClientKnownRequestError and returning different status codes instead of 500 Internal server error.

To use the filter you have the following two options.

  1. Instantiate the filter in your main.ts and pass the HttpAdapterHost
//src/main.ts
import { ValidationPipe } from '@nestjs/common';
import { HttpAdapterHost, NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { PrismaClientExceptionFilter } from 'nestjs-prisma';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const { httpAdapter } = app.get(HttpAdapterHost);
  app.useGlobalFilters(new PrismaClientExceptionFilter(httpAdapter));

  await app.listen(3000);
}
bootstrap();
  1. Use APP_FILTER token in any module
//src/app.module.ts
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
import { PrismaClientExceptionFilter } from 'nestjs-prisma';

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: PrismaClientExceptionFilter,
    },
  ],
})
export class AppModule {}

Additional options

All available flags:

Flag Description Type Default
datasourceProvider Specifies the datasource provider for prisma init and docker. boolean Prompted
addDocker Create a Dockerfile and docker-compose.yaml. boolean Prompted
addPrismaService Create a Prisma service extending the Prisma Client and module. boolean false
dockerNodeImageVersion Node version for the builder and runner image. string 14
name The name for the Prisma service extending the Prisma Client and module. string Prisma
prismaVersion The Prisma version to be installed. string latest
skipInstall Skip installing dependency packages. boolean false
skipPrismaInit Skip initializing Prisma. boolean false

You can pass additional flags to customize the schematic. For example, if you want to install a different version for Prisma use --prismaVersion flag:

nest add nestjs-prisma --prismaVersion 3.2.1

If you want to skip installing dependencies use --skipInstall flag:

nest add nestjs-prisma --skipInstall

Add Dockerfile and docker-compose.yaml, you can even use a different node version (14-alpine or 16).

Currently uses to PostgreSQL as a default database in docker-compose.yaml.

nest add nestjs-prisma --addDocker --dockerNodeImageVersion 14-alpine

Developing

Install @angular-devkit/schematics-cli to be able to use schematics command

npm i -g @angular-devkit/schematics-cli

Now build the schematics and run the schematic.

npm run build:schematics
# or
npm run dev:schematics

# dry-run
schematics .:nest-add

# execute schematics
schematics .:nest-add --debug false
# or
schematics .:nest-add --dry-run false

Helpful

Helpful article about Custom Angular Schematics which also applies to Nest.