nestjs-prisma
Advanced tools
Comparing version 0.19.0-dev.0 to 0.19.0-dev.1
@@ -11,4 +11,6 @@ import { ArgumentsHost, ContextType, HttpException, HttpServer } from '@nestjs/common'; | ||
constructor(applicationRef?: HttpServer, errorCodesStatusMapping?: ErrorCodesStatusMapping); | ||
catch(exception: Prisma.PrismaClientKnownRequestError, host: ArgumentsHost): void | HttpException | Prisma.PrismaClientKnownRequestError; | ||
catch(exception: Prisma.PrismaClientKnownRequestError | Prisma.NotFoundError, host: ArgumentsHost): void | HttpException | Prisma.PrismaClientKnownRequestError; | ||
private catchClientKnownRequestError; | ||
private catchNotFoundError; | ||
exceptionShortMessage(message: string): string; | ||
} |
@@ -29,2 +29,10 @@ "use strict"; | ||
catch(exception, host) { | ||
if (exception instanceof client_1.Prisma.PrismaClientKnownRequestError) { | ||
return this.catchClientKnownRequestError(exception, host); | ||
} | ||
else if (exception instanceof client_1.Prisma.NotFoundError) { | ||
return this.catchNotFoundError(exception, host); | ||
} | ||
} | ||
catchClientKnownRequestError(exception, host) { | ||
const statusCode = this.errorCodesStatusMapping[exception.code]; | ||
@@ -50,2 +58,16 @@ const message = `[${exception.code}]: ` + this.exceptionShortMessage(exception.message); | ||
} | ||
catchNotFoundError({ message }, host) { | ||
const statusCode = common_1.HttpStatus.NOT_FOUND; | ||
if (host.getType() === 'http') { | ||
const ctx = host.switchToHttp(); | ||
const response = ctx.getResponse(); | ||
response.status(statusCode).send(JSON.stringify({ | ||
statusCode, | ||
message, | ||
})); | ||
} | ||
else if (host.getType() === 'graphql') { | ||
return new common_1.HttpException({ statusCode, message }, statusCode); | ||
} | ||
} | ||
exceptionShortMessage(message) { | ||
@@ -60,3 +82,3 @@ const shortMessage = message.substring(message.indexOf('→')); | ||
PrismaClientExceptionFilter = __decorate([ | ||
(0, common_1.Catch)(client_1.Prisma === null || client_1.Prisma === void 0 ? void 0 : client_1.Prisma.PrismaClientKnownRequestError), | ||
(0, common_1.Catch)(client_1.Prisma === null || client_1.Prisma === void 0 ? void 0 : client_1.Prisma.PrismaClientKnownRequestError, client_1.Prisma === null || client_1.Prisma === void 0 ? void 0 : client_1.Prisma.NotFoundError), | ||
__metadata("design:paramtypes", [Object, Object]) | ||
@@ -63,0 +85,0 @@ ], PrismaClientExceptionFilter); |
{ | ||
"name": "nestjs-prisma", | ||
"version": "0.19.0-dev.0", | ||
"version": "0.19.0-dev.1", | ||
"description": "Library and schematics to add Prisma integration to a NestJS application", | ||
@@ -13,3 +13,3 @@ "scripts": { | ||
"changelog": "npx conventional-changelog-cli conventional-changelog -p angular -i ./CHANGELOG.md -s", | ||
"rocket": "npm run build && npm publish --tag dev", | ||
"rocket": "npm run build && npm publish", | ||
"format": "prettier --write \"lib/**/*.ts\" \"schematics/**/*.ts\"", | ||
@@ -46,21 +46,21 @@ "lint": "eslint \"{lib,schematics}/**/*.ts\" --fix" | ||
"devDependencies": { | ||
"@nestjs/common": "9.0.3", | ||
"@nestjs/core": "9.0.3", | ||
"@nestjs/platform-express": "9.0.3", | ||
"@prisma/client": "^4.0.0", | ||
"@nestjs/common": "9.1.2", | ||
"@nestjs/core": "9.1.2", | ||
"@nestjs/platform-express": "9.1.2", | ||
"@prisma/client": "^4.4.0", | ||
"@types/jasmine": "3.8.2", | ||
"@types/node": "^16.11.1", | ||
"@typescript-eslint/eslint-plugin": "5.15.0", | ||
"@typescript-eslint/parser": "5.15.0", | ||
"eslint": "8.11.0", | ||
"@typescript-eslint/eslint-plugin": "5.38.1", | ||
"@typescript-eslint/parser": "5.38.1", | ||
"eslint": "8.24.0", | ||
"eslint-config-prettier": "8.5.0", | ||
"eslint-plugin-prettier": "4.0.0", | ||
"eslint-plugin-prettier": "4.2.1", | ||
"jasmine": "^3.6.3", | ||
"prettier": "2.7.1", | ||
"prisma": "^4.0.0", | ||
"prisma": "^4.4.0", | ||
"reflect-metadata": "0.1.13", | ||
"rimraf": "^3.0.2", | ||
"rxjs": "^7.5.6", | ||
"typescript": "~4.7.4" | ||
"rxjs": "^7.5.7", | ||
"typescript": "~4.8.3" | ||
} | ||
} |
514
README.md
@@ -24,5 +24,15 @@ <h1 align="center">nestjs-prisma</h1> | ||
Install the library: | ||
### Automatic Install | ||
```bash | ||
Use the [nest add](https://nestjs-prisma.dev/docs/schematics) command to automatically setup the library, Prisma and Docker (optionally): | ||
```sh | ||
nest add nestjs-prisma | ||
``` | ||
### Manual Install | ||
Add `nestjs-prisma` library to your [NestJS application](https://docs.nestjs.com/#installation): | ||
```sh | ||
# npm | ||
@@ -35,10 +45,11 @@ npm install nestjs-prisma | ||
or install it automatically using the [schematics command](https://docs.nestjs.com/cli/usages#nest-add): | ||
Furthermore, setup [Prisma](https://www.prisma.io/docs/getting-started/setup-prisma/add-to-existing-project/relational-databases-typescript-postgres#set-up-prisma) in your NestJS application, if you haven't already. | ||
```bash | ||
nest add nestjs-prisma | ||
```sh | ||
npm i -D prisma | ||
npm install @prisma/client | ||
npx prisma init | ||
``` | ||
Besides installing the library, the [schematics](#schematics) allows to configure Prisma, Docker and even a custom `PrismaService`. | ||
## Basic usage | ||
@@ -58,3 +69,3 @@ | ||
Use the `PrismaService` via dependency injection in your controller, resolver, services, guards and more: | ||
Use the `PrismaService` via dependency injection in your controller, resolver, services, guards and more: | ||
@@ -83,480 +94,6 @@ ```ts | ||
### Shutdown Hook | ||
## Documentation | ||
Handle Prisma [shutdown](https://docs.nestjs.com/recipes/prisma#issues-with-enableshutdownhooks) signal to shutdown your Nest application. | ||
Visit our [official documentation](https://nestjs-prisma.dev/docs/installation). | ||
```ts | ||
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](https://www.prisma.io/docs/concepts/components/prisma-client/middleware) with `PrismaModule` | ||
```ts | ||
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](https://www.prisma.io/docs/concepts/components/prisma-client/middleware/logging-middleware). | ||
Create your Prisma Middleware and export it as a `function` | ||
```ts | ||
// 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. | ||
```ts | ||
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](#logging-middleware). | ||
## Configure `PrismaModule` | ||
`PrismaModule` allows to be used [globally](https://docs.nestjs.com/modules#global-modules) and to pass options to the `PrismaClient`. | ||
```ts | ||
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: | ||
```ts | ||
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. | ||
```ts | ||
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: | ||
```ts | ||
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` | ||
```ts | ||
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](https://www.prisma.io/docs/reference/api-reference/error-reference#prisma-client-query-engine) 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. | ||
1. Instantiate the filter in your `main.ts` and pass the `HttpAdapterHost` | ||
```ts | ||
//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: | ||
```ts | ||
//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](https://www.prisma.io/docs/reference/api-reference/error-reference#prisma-client-query-engine) 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 | | ||
2. Use `APP_FILTER` token in any module | ||
```ts | ||
//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. | ||
```ts | ||
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`. | ||
```ts | ||
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](https://docs.nestjs.com/techniques/configuration) module. Add `PRISMA_QUERY_LOG_LEVEL` to your `.env` file with one of the log levels (`log`, `debug`, `warn`, `error`). | ||
```ts | ||
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](https://github.com/notiz-dev/nestjs-prisma/blob/main/schematics/nestjs-prisma/index.ts#L35-L42) 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` and `PrismaModule` (optionally) | ||
- Add `Dockerfile` and `docker-compose.yml` (optionally) | ||
- Excludes `prisma` directory from build via `tsconfig.build.json` | ||
Example output of the schematics: | ||
```bash | ||
✔ 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` | ||
```bash | ||
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`. | ||
```ts | ||
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`. | ||
### 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: | ||
```bash | ||
nest add nestjs-prisma --prismaVersion 3.2.1 | ||
``` | ||
If you want to skip installing dependencies use `--skipInstall` flag: | ||
```bash | ||
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`. | ||
```bash | ||
nest add nestjs-prisma --addDocker --dockerNodeImageVersion 14-alpine | ||
``` | ||
## Contributing | ||
@@ -566,5 +103,7 @@ | ||
The code is split up into two directories: | ||
The code is split up into three directories: | ||
``` | ||
+-- docs | ||
+-- examples | ||
+-- lib | ||
@@ -574,3 +113,8 @@ +-- schematics | ||
The `docs` directory contains an astro website and the [docs content](./docs/src/pages/docs) pages. | ||
The `examples` directory contains example applications. | ||
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. | ||
@@ -577,0 +121,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1148
164720
141