nestjs-prisma

repository·main·Indexed 20 days ago

https://github.com/notiz-dev/nestjs-prisma

Library and schematics to add Prisma integration to a NestJS application. It provides a seamless way to use PrismaClient via NestJS dependency injection through PrismaService and PrismaModule. Supports automatic installation via NestJS Schematics, custom PrismaClient Extensions using CustomPrismaService, and integration with Fastify and GraphQL.

Tokens
22K
Snippets
89
Records
99
Agent score
72%

What's inside nestjs-prisma

  1. Understand the project structure and routing

    main

    The project follows a standard Astro structure:

    • Routing: Astro automatically exposes files in src/pages/ as routes based on their filenames. Supported file extensions include .astro and .md.
    • Components: Custom components (Astro, React, Vue, Svelte, or Preact) are typically stored in src/components/.
    • Layouts: Reusable page structures are located in src/layouts/.
    • Static Assets: Images and other static files should be placed in the public/ directory.
  2. Use PrismaClientExceptionFilter to handle Prisma errors

    main

    The PrismaClientExceptionFilter catches unhandled PrismaClientKnownRequestError instances and maps them to appropriate HTTP status codes instead of returning a generic 500 Internal server error. This filter is compatible with REST (Express/Fastify) and GraphQL.

    By default, the following Prisma error codes are mapped:

    Error CodeHttp Status
    P2000 (Value too long for column)Bad Request (400)
    P2002 (Unique constraint failed)Conflict (409)
    P2025 (Record not found)Not Found (404)
  3. Configure Prisma event-based logging

    main

    To enable event-based logging in nestjs-prisma, you must configure the log property within prismaOptions inside the PrismaModule.forRoot configuration. Set the emit property to 'event' and specify the desired level (e.g., 'query').

    import { Module } from '@nestjs@common';
    import { PrismaModule } from 'nestjs-prisma';
    
    @Module({
      imports: [
        PrismaModule.forRoot({
          prismaServiceOptions: {
            prismaOptions: {
              log: [
                {
                  emit: 'event',
                  level: 'query',
                },
              ],
            },
          },
        }),
      ],
    })
    export class AppModule {}
  4. Configure logging middleware level via environment variables

    main

    To dynamically control the log level using @nestjs/config, add the PRISMA_QUERY_LOG_LEVEL key to your .env file. Supported values are log, debug, warn, or error.

    Use PrismaModule.forRootAsync to inject the ConfigService and retrieve the value.

    import { Module, Logger } from '@nestjs/common';
    import { ConfigModule, ConfigService } from '@nestjs/config';
    import { PrismaModule, loggingMiddleware } from 'nestjs-prisma';
    
    @Module({
      imports: [
        ConfigModule.forRoot({ isGlobal: true }),
        PrismaModule.forRootAsync({
          useFactory: (config: ConfigService) => {
            return {
              middlewares: [
                loggingMiddleware({
                  logger: new Logger('PrismaMiddleware'),
                  logLevel: config.get('PRISMA_QUERY_LOG_LEVEL'),
                }),
              ],
              prismaOptions: { log: ['warn', 'error'] },
            };
          },
          inject: [ConfigService],
        }),
      ],
    })
    export class AppModule {}
  5. Use CustomPrismaModule and CustomPrismaService for custom locations

    main

    If you are using a custom Prisma Client output location or need multiple Prisma Clients, use CustomPrismaModule and CustomPrismaService (available in nestjs-prisma@v0.20.0 or later).

    1. Register the module

    In your AppModule, import CustomPrismaModule.forRoot() and provide a unique name and an instance of your custom PrismaClient. The name is critical as it is used for dependency injection.

    2. Inject the service

    To use the client in a service, use the @Inject() decorator with the unique name you defined in the module. You must pass your custom PrismaClient type as a generic to CustomPrismaService<T> to ensure type-safety and auto-completion.

    Access the actual Prisma Client instance via the .client property of the injected service.

    import { Module } from '@nestjs/common';
    import { CustomPrismaModule } from 'nestjs-prisma';
    import { PrismaClient } from '@notiz/prisma';
    
    @Module({
      imports: [
        CustomPrismaModule.forRoot({
          name: 'PrismaServiceAuth', // Unique name for injection
          client: new PrismaClient(),
        }),
      ],
    })
    export class AppModule {}
    
    // In your service
    import { Inject, Injectable } from '@nestjs/common';
    import { CustomPrismaService } from 'nestjs-prisma';
    import { PrismaClient } from '@notiz/prisma';
    
    @Injectable()
    export class AppService {
      constructor(
        @Inject('PrismaServiceAuth')
        private prisma: CustomPrismaService<PrismaClient>,
      ) {}
    
      users() {
        // Access the client via the .client property
        return this.prisma.client.user.findMany();
      }
    }
  6. Integrate PrismaModule and PrismaService

    main

    To use Prisma within your NestJS application, import PrismaModule into your AppModule (or any other module). You can configure the module using forRoot(...) or forRootAsync(...).

    Once imported, you can inject PrismaService into any class (controllers, services, resolvers, etc.) via dependency injection. PrismaService provides full access to all methods and arguments of the generated PrismaClient.

    import { Module } from '@nestjs/common';
    import { PrismaModule } from 'nestjs-prisma';
    
    @Module({
      imports: [PrismaModule],
    })
    export class AppModule {}
    
    // Usage in a service
    import { Injectable } from '@nestjs/common';
    import { PrismaService } from 'nestjs-prisma';
    
    @Injectable()
    export class AppService {
      constructor(private prisma: PrismaService) {}
    
      users() {
        return this.prisma.user.findMany();
      }
    
      user(userId: string) {
        return this.prisma.user.findUnique({
          where: { id: userId },
        });
      }
    }
  7. Configure PrismaModule asynchronously using useFactory

    main

    Use PrismaModule.forRootAsync() with the useFactory property to provide configuration dynamically. This is useful when you need to inject other services, such as ConfigService, to load settings from environment variables.

    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 {}
  8. Configure PrismaModule asynchronously using useClass

    main

    Use PrismaModule.forRootAsync() with the useClass property to provide configuration via a dedicated service. The class must implement the PrismaOptionsFactory interface and provide a createPrismaOptions() method.

    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,
        };
      }
    }
  9. Create a shareable Query Logging extension

    main

    Instead of defining the extension logic inline, you can extract it into a shareable function using Prisma.defineExtension. This allows you to pass in dependencies like a NestJS Logger and reuse the extension across different parts of your application or different projects.

    To use it, call your extension function inside the $extends method of a PrismaClient instance.

    // query-logger.extension.ts
    import { Logger } from '@nestjs/common';
    import { Prisma } from '@prisma/client';
    
    export const queryLoggingExtension = (logger: Logger) =>
      Prisma.defineExtension({
        name: 'prisma-extension-query-logger',
        query: {
          $allModels: {
            async $allOperations({ operation, model, args, query }) {
              const start = performance.now();
              const result = await query(args);
              const end = performance.now();
              const time = Math.ceil((end - start) * 100) / 100;
              logger.log(`Prisma Query ${model}.${operation} took ${time}ms`);
              return result;
            },
          },
        },
      });
    
    // prisma.extension.ts
    import { Logger } from '@nestjs/common';
    import { PrismaClient } from '@prisma/client';
    import { queryLoggingExtension } from './query-logger.extension';
    
    const logger = new Logger('PrismaClient');
    export const extendedPrismaClient = new PrismaClient().$extends(
      queryLoggingExtension(logger),
    );
    export const ExtendedPrismaClient = typeof extendedPrismaClient;