nestjs-prisma
nestjs-prisma copied to clipboard
Easy Prisma support for your NestJS application
nestjs-prisma
Easy Prisma support for your NestJS application.
Installation
Install the library:
# npm
npm install nestjs-prisma
# yarn
yarn add nestjs-prisma
or install it automatically using the schematics command:
nest add nestjs-prisma
Besides installing the library, the schematics allows to configure Prisma, Docker and even a custom PrismaService.
Basic usage
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 {}
Use the PrismaService via dependency injection in your controller, resolver, services, guards and more:
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 },
});
}
}
You have access to all exposed methods and arguments of the generated PrismaClient through PrismaService.
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';
@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 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 {}
Try out the built in Logging Middleware.
Configure PrismaModule
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,
};
}
}
PrismaClientExceptionFilter
nestjs-prisma provides a PrismaClientExceptionFilter to catch unhandled PrismaClientKnownRequestError and returns different HttpStatus codes instead of 500 Internal server error. The exception filter supports REST (Express/Fasitfy) and GraphQL.
To use the filter you have the following two options.
- Instantiate the filter in your
main.tsand pass theHttpAdapterHost
//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();
Optionally, provide your own error code mapping via the constructor:
//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, {
// Prisma Error Code: HTTP Status Response
P2000: HttpStatus.BAD_REQUEST,
P2002: HttpStatus.CONFLICT,
P2025: HttpStatus.NOT_FOUND,
}),
);
await app.listen(3000);
}
bootstrap();
See the list of Prisma CLient Errors in the Prisma docs.
This will override the default error code mapping:
| Error Code | Http Status |
|---|---|
| P2000 - "The provided value for the column is too long for the column's type. Column: {column_name}" | Bad Request - 400 |
| P2002 - "Unique constraint failed on the {constraint}" | Conflict - 409 |
| P2025 - "An operation failed because it depends on one or more records that were required but not found. {cause}" | Not Found - 404 |
- Use
APP_FILTERtoken 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 {}
Logging Middleware
nestjs-prisma provides a loggingMiddleware to log the query execution time.
import { Module } from '@nestjs/common';
import { PrismaModule, loggingMiddleware } from 'nestjs-prisma';
@Module({
imports: [
PrismaModule.forRoot({
prismaServiceOptions: {
middlewares: [loggingMiddleware()],
},
}),
],
})
export class AppModule {}
The default log messages are looking as follows.
Prisma Query User.findUnique took 6ms
Prisma Query User.create took 4ms
Prisma Query Product.findMany took 10ms
Customize the logging middleware by providing your own logger, logLevel and logMessage.
import { Module } from '@nestjs/common';
import { PrismaModule, loggingMiddleware, QueryInfo } from 'nestjs-prisma';
@Module({
imports: [
PrismaModule.forRoot({
prismaServiceOptions: {
middlewares: [
loggingMiddleware({
logger: new Logger('PrismaMiddleware'),
logLevel: 'log', // default is `debug`
logMessage: (query: QueryInfo) =>
`[Prisma Query] ${query.model}.${query.action} - ${query.executionTime}ms`,
}),
],
},
}),
],
})
export class AppModule {}
The customized log messages are looking as follows.
[Nest] 51348 - 29/07/2022, 10:08:41 LOG [PrismaMiddleware] [Prisma Query] User.findUnique - 4ms
[Nest] 51348 - 29/07/2022, 10:08:50 LOG [PrismaMiddleware] [Prisma Query] User.create - 6ms
[Nest] 51348 - 29/07/2022, 10:09:13 LOG [PrismaMiddleware] [Prisma Query] Product.findMany - 9ms
Change the log level from your .env file using the @nestjs/config module. Add PRISMA_QUERY_LOG_LEVEL to your .env file with one of the log levels (log, debug, warn, error).
import { Module } from '@nestjs/common';
import { PrismaModule, loggingMiddleware, QueryInfo } 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 {}
Schematics
The schematics automatically performs additional steps to configure Prisma and Docker in your project.
- Initialize Prisma
npx prisma init --datasource-provider postgres|... - Add Prisma npm scripts to your
package.json - Add seed script for Prisma
- Generate custom
PrismaServiceandPrismaModule(optionally) - Add
Dockerfileanddocker-compose.yml(optionally) - Excludes
prismadirectory from build viatsconfig.build.json
Example output of the schematics:
✔ 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
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-prismaas dependency otherwise you have two import suggestions forPrismaServiceandPrismaModule.
Schematic options
All available options to passe to the schematic command:
| 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 PostgreSQL as a default database in
docker-compose.yaml.
nest add nestjs-prisma --addDocker --dockerNodeImageVersion 14-alpine
Contributing
You are welcome to contribute to this project.
The code is split up into two directories:
+-- lib
+-- schematics
The lib directory contains everything exposed by nestjs-prisma as a library.
The schematics directory contains the blue prints for installing the library with the schematic command.
Here are some tips if you like to make changes to the schematics.
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 article about Custom Angular Schematics which also applies to Nest.