Easy Prisma support for your NestJS application.
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
.
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
.
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();
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.
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,
};
}
}
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.ts
and 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_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 {}
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 {}
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
PrismaService
andPrismaModule
(optionally) - Add
Dockerfile
anddocker-compose.yml
(optionally) - Excludes
prisma
directory 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
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 forPrismaService
andPrismaModule
.
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
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.