diff --git a/cSpell.json b/cSpell.json index 6787fe9f98..6579087e2f 100644 --- a/cSpell.json +++ b/cSpell.json @@ -51,6 +51,7 @@ "Nuxt", "Sveltekit", "Pothos", + "backoff", "Replibyte", "Snaplet" ], diff --git a/content/200-concepts/100-components/02-prisma-client/053-middleware/200-logging-middleware.mdx b/content/200-concepts/100-components/02-prisma-client/053-middleware/200-logging-middleware.mdx index 059f0d6eab..145fa01447 100644 --- a/content/200-concepts/100-components/02-prisma-client/053-middleware/200-logging-middleware.mdx +++ b/content/200-concepts/100-components/02-prisma-client/053-middleware/200-logging-middleware.mdx @@ -84,3 +84,7 @@ enum Role { ``` + +## Going further + +You can also use [Prisma Client extensions](/concepts/components/prisma-client/client-extensions) to log the time it takes to perform a query. A functional example can be found in [this GitHub repository](https://github.com/prisma/prisma-client-extensions/tree/main/query-logging). diff --git a/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx b/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx index 096249f324..75aedb04ee 100644 --- a/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx +++ b/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx @@ -6,13 +6,149 @@ metaDescription: 'This page explains how to add custom validation to Prisma Clie -Prisma Client has type-safety and run-time type validation but does not include validation for user input. +You can add runtime validation for your user input for Prisma Client queries in one of the following ways: -This means you can use any validation library you'd like. The Node.js ecosystem offers a number of high-quality, easy-to-use validation libraries to choose from including: [joi](https://github.com/sideway/joi), [validator.js](https://github.com/validatorjs/validator.js), [Yup](https://github.com/jquense/yup), [Zod](https://github.com/colinhacks/zod) and [Superstruct](https://github.com/ianstormtaylor/superstruct). +- [Prisma Client extensions](/concepts/components/prisma-client/client-extensions) +- A custom function + +You can use any validation library you'd like. The Node.js ecosystem offers a number of high-quality, easy-to-use validation libraries to choose from including: [joi](https://github.com/sideway/joi), [validator.js](https://github.com/validatorjs/validator.js), [Yup](https://github.com/jquense/yup), [Zod](https://github.com/colinhacks/zod) and [Superstruct](https://github.com/ianstormtaylor/superstruct). -## Custom Signup Validation +## Input validation with Prisma Client extensions + +> Prisma Client extensions are currently in [Preview](/about/prisma/releases#preview). + +This example adds runtime validation when creating and updating values using a Zod schema to check that the data passed to Prisma Client is valid. + + + +Query extensions do not currently work for nested operations. In this example, validations are only run on the top level data object passed to methods such as `prisma.product.create()`. Validations implemented this way do not automatically run for [nested writes](https://www.prisma.io/docs/concepts/components/prisma-client/relation-queries#nested-writes). + + + +, ]}> + + + +```ts copy +import { PrismaClient, Prisma } from "@prisma/client"; +import { z } from 'zod' + +/** + * Zod schema + */ +export const ProductCreateInput = z.object({ + slug: z + .string() + .max(100) + .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/), + name: z.string().max(100), + description: z.string().max(1000), + price: z + .instanceof(Prisma.Decimal) + .refine((price) => price.gte("0.01") && price.lt("1000000.00")), +}) satisfies z.Schema; + +/** + * Prisma Client Extension + */ +const prisma = new PrismaClient().$extends({ + query: { + product: { + create({ args, query }) { + args.data = ProductCreateInput.parse(args.data); + return query(args); + }, + update({ args, query }) { + args.data = ProductCreateInput.partial().parse(args.data); + return query(args); + }, + updateMany({ args, query }) { + args.data = ProductCreateInput.partial().parse(args.data); + return query(args); + }, + upsert({ args, query }) { + args.create = ProductCreateInput.parse(args.create); + args.update = ProductCreateInput.partial().parse(args.update); + return query(args); + }, + }, + }, +}); + +async function main (){ + /** + * Example usage + */ + // Valid product + const product = await prisma.product.create({ + data: { + slug: "example-product", + name: "Example Product", + description: "Lorem ipsum dolor sit amet", + price: new Prisma.Decimal("10.95"), + }, + }); + + // Invalid product + try { + await prisma.product.create({ + data: { + slug: "invalid-product", + name: "Invalid Product", + description: "Lorem ipsum dolor sit amet", + price: new Prisma.Decimal("-1.00"), + }, + }); + } catch (err: any) { + console.log(err?.cause?.issues); + } +} + +main() +``` + + + + + +```prisma copy +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" + previewFeatures = ["clientExtensions"] +} + +model Product { + id String @id @default(cuid()) + slug String + name String + description String + price Decimal + reviews Review[] +} + +model Review { + id String @id @default(cuid()) + body String + stars Int + product Product @relation(fields: [productId], references: [id], onDelete: Cascade) + productId String +} +``` + + + + + +The above example uses a Zod schema to validate and parse data provided in a query at runtime before a record is written to the database. + +## Input validation with a custom validation function Here's an example using [Superstruct](https://github.com/ianstormtaylor/superstruct) to validate that the data needed to signup a new user is correct: @@ -50,7 +186,8 @@ async function signup(input: Signup): Promise { The example above shows how you can create a custom type-safe `signup` function that ensures the input is valid before creating a user. -## Going Further +## Going further +- Learn how you can use [Prisma Client extensions](/concepts/components/prisma-client/client-extensions) to add input validation for your queries — [example](https://github.com/prisma/prisma-client-extensions/tree/main/input-validation). - Learn how you can organize your code better by moving the `signup` function into [a custom model](/concepts/components/prisma-client/custom-models). - There's an [outstanding feature request](https://github.com/prisma/prisma/issues/3528) to bake user validation into Prisma Client. If you'd like to see that happen, make sure to upvote that issue and share your use case! diff --git a/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx b/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx index 367f818e78..37746bea17 100644 --- a/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx +++ b/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx @@ -10,9 +10,106 @@ Computed fields allow you to derive a new field based on existing data. A common -## Deriving a Full Name from a First and Last Name +## Using a Prisma Client extension -Prisma Client does not yet natively support computed fields, but with a bit of TypeScript magic, you can define a function that accepts a generic as an input then extend that generic to ensure it conforms to a specific structure. Finally, you can return that generic with additional computed fields. Let's see how that might look: +> Prisma Client extensions are currently in [Preview](/about/prisma/releases#preview). + +The following example illustrates how to create a [Prisma Client extension](/concepts/components/prisma-client/client-extensions) that adds a `fullName` computed field at runtime to the `User` model in a Prisma schema. + + ,]}> + + + + + + + +```ts +import { PrismaClient } from '@prisma/client' + +const prisma = new PrismaClient().$extends({ + result: { + user: { + fullName: { + needs: { firstName: true, lastName: true }, + compute(user) { + return `${user.firstName} ${user.lastName}` + }, + }, + }, + }, +}) + +async function main() { + /** + * Example query containing the `fullName` computed field in the response + */ + const user = await prisma.user.findFirst() +} + +main() +``` + + + + +```js no-copy +{ + id: 'clgzkgy2j00004tc6iwu3gkzu', + firstName: 'Aurelia', + lastName: 'Schneider', + email: 'Jalen_Berge40@hotmail.com', + fullName: 'Aurelia Schneider', +} +``` + + + + + + + + + +```prisma copy +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" + previewFeatures = ["clientExtensions"] +} + +model User { + id Int @id @default(autoincrement()) + email String @unique + name String? + + posts Post[] +} + +model Post { + id Int @id @default(autoincrement()) + title String + published Boolean @default(true) + content String? + + authorId Int? + author User? @relation(fields: [authorId], references: [id]) +} +``` + + + + + +The computed fields are type-safe and can return anything from a concatenated value to complex objects or functions that can act as an instance method for your models. + +## Using a computation function + +Prisma Client does not yet natively support computed fields, but, you can define a function that accepts a generic type as an input then extend that generic to ensure it conforms to a specific structure. Finally, you can return that generic with additional computed fields. Let's see how that might look: , ]}> @@ -74,7 +171,8 @@ A `WithFullName` return type has also been defined, which takes whatever ` With this function, any object that contains `firstName` and `lastName` keys can compute a `fullName`. Pretty neat, right? -## Going Further +## Going further +- Learn how you can use [Prisma Client extensions](/concepts/components/prisma-client/client-extensions) to add a computed field to your schema — [example](https://github.com/prisma/prisma-client-extensions/tree/main/computed-fields). - Learn how you can move the `computeFullName` function into [a custom model](/concepts/components/prisma-client/custom-models). - There's an [outstanding feature request](https://github.com/prisma/prisma/issues/3394) to add native support to Prisma Client. If you'd like to see that happen, make sure to upvote that issue and share your use case! diff --git a/content/200-concepts/100-components/02-prisma-client/063-excluding-fields.mdx b/content/200-concepts/100-components/02-prisma-client/063-excluding-fields.mdx index aed449f449..2851b919b8 100644 --- a/content/200-concepts/100-components/02-prisma-client/063-excluding-fields.mdx +++ b/content/200-concepts/100-components/02-prisma-client/063-excluding-fields.mdx @@ -65,7 +65,7 @@ In the TypeScript example, we've provided two generics: `User` and `Key`. The `K These generics flow through the logic, returning a `User` that omits the list of `Key`s provided. -## Going Further +## Going further - Learn how you can move the `exclude` function into [a custom model](/concepts/components/prisma-client/custom-models). - There's an [outstanding feature request](https://github.com/prisma/prisma/issues/5042) to add exclude support natively in Prisma Client. If you'd like to see that happen, make sure to upvote that issue and share your use case! diff --git a/content/200-concepts/100-components/02-prisma-client/064-custom-models.mdx b/content/200-concepts/100-components/02-prisma-client/064-custom-models.mdx index 898729f930..0fd28a3b41 100644 --- a/content/200-concepts/100-components/02-prisma-client/064-custom-models.mdx +++ b/content/200-concepts/100-components/02-prisma-client/064-custom-models.mdx @@ -6,11 +6,95 @@ metaDescription: 'This page explains how to wrap Prisma Client in custom models' -As your application grows, you may find the need to group related logic together. We suggest either wrapping a model in a class or extending Prisma Client model object. +As your application grows, you may find the need to group related logic together. We suggest either: + +- Creating static methods using a [Prisma Client extension](/concepts/components/prisma-client/client-extensions) +- Wrapping a model in a class +- Extending Prisma Client model object -## Wrap a Prisma Model in a Class +## Static methods with Prisma Client extensions + +> Prisma Client extensions are currently in [Preview](/about/prisma/releases#preview). + +The following example demonstrates how to create a Prisma Client extension that adds a `signUp` and `findManyByDomain` methods to a User model. + +,]}> + + + +```tsx +import bcrypt from 'bcryptjs' +import { PrismaClient } from '@prisma/client' + +const prisma = new PrismaClient().$extends({ + model: { + user: { + async signUp(email: string, password: string) { + const hash = await bcrypt.hash(password, 10) + return prisma.user.create({ + data: { + email, + password: { + create: { + hash, + }, + }, + }, + }) + }, + + async findManyByDomain(domain: string) { + return prisma.user.findMany({ + where: { email: { endsWith: `@${domain}` } }, + }) + }, + }, + }, +}) + +async function main() { + // Example usage + await prisma.user.signUp('user2@example2.com', 's3cret') + + await prisma.user.findManyByDomain('example2.com') +} +``` + + + + + +```prisma file="prisma/schema.prisma" copy +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" + previewFeatures = ["clientExtensions"] +} + +model User { + id String @id @default(cuid()) + email String + password Password? +} + +model Password { + hash String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + userId String @unique +} +``` + + + + + +## Wrap a model in a class In the example below, you'll see how you can wrap the `user` model in the Prisma Client within a `Users` class. @@ -50,7 +134,7 @@ Note that in the example above, you're only exposing a `signup` method from Pris This approach works well when you have a large application and you want to intentionally limit what your models can do. -## Extending Prisma Client +## Extending Prisma Client model object But what if you don't want to hide existing functionality but still want to group custom functions together? In this case, you can use `Object.assign` to extend Prisma Client without limiting its functionality: @@ -89,3 +173,7 @@ async function main() { ``` Now you can use your custom `signup` method alongside `count`, `updateMany`, `groupBy` and all of the other wonderful methods that Prisma Client provides. Best of all, it's all type-safe! + +## Going further + +We recommend using [Prisma Client extensions](/concepts/components/prisma-client/client-extensions) to extend your models with [custom model methods](https://github.com/prisma/prisma-client-extensions/tree/main/instance-methods). diff --git a/content/200-concepts/100-components/02-prisma-client/260-client-extensions/200-extension-examples.mdx b/content/200-concepts/100-components/02-prisma-client/260-client-extensions/200-extension-examples.mdx new file mode 100644 index 0000000000..c57df5d3c7 --- /dev/null +++ b/content/200-concepts/100-components/02-prisma-client/260-client-extensions/200-extension-examples.mdx @@ -0,0 +1,37 @@ +--- +title: 'Examples' +metaTitle: 'Prisma Client Extension | Examples' +metaDescription: 'Extend the functionality of Prisma Client: Examples' +--- + + + +Prisma Client extensions is currently in [Preview](/about/prisma/releases#preview). The extensions are provided as examples only, and without warranty. They are not +intended to be used in production environments. + + + +## Examples + +| Example | Description | +| :---------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------ | +| [`audit-log-context`](https://github.com/prisma/prisma-client-extensions/tree/main/audit-log-context) | Provides the current user's ID as context to Postgres audit log triggers | +| [`callback-free-itx`](https://github.com/prisma/prisma-client-extensions/tree/main/callback-free-itx) | Adds a method to start interactive transactions without callbacks | +| [`computed-fields`](https://github.com/prisma/prisma-client-extensions/tree/main/computed-fields) | Adds virtual / computed fields to result objects | +| [`input-transformation`](https://github.com/prisma/prisma-client-extensions/tree/main/input-transformation) | Transforms the input arguments passed to Prisma Client queries to filter the result set | +| [`input-validation`](https://github.com/prisma/prisma-client-extensions/tree/main/input-validation) | Runs custom validation logic on input arguments passed to mutation methods | +| [`instance-methods`](https://github.com/prisma/prisma-client-extensions/tree/main/instance-methods) | Adds Active Record-like methods like `save()` and `delete()` to result objects | +| [`json-field-types`](https://github.com/prisma/prisma-client-extensions/tree/main/json-field-types) | Uses strongly-typed runtime parsing for data stored in JSON columns | +| [`model-filters`](https://github.com/prisma/prisma-client-extensions/tree/main/model-filters) | Adds reusable filters that can composed into complex `where` conditions for a model | +| [`obfuscated-fields`](https://github.com/prisma/prisma-client-extensions/tree/main/obfuscated-fields) | Prevents sensitive data (e.g. `password` fields) from being included in results | +| [`query-logging`](https://github.com/prisma/prisma-client-extensions/tree/main/query-logging) | Wraps Prisma Client queries with simple query timing and logging | +| [`readonly-client`](https://github.com/prisma/prisma-client-extensions/tree/main/readonly-client) | Creates a client that only allows read operations | +| [`retry-transactions`](https://github.com/prisma/prisma-client-extensions/tree/main/retry-transactions) | Adds a retry mechanism to transactions with exponential backoff and jitter | +| [`row-level-security`](https://github.com/prisma/prisma-client-extensions/tree/main/row-level-security) | Uses Postgres row-level security policies to isolate data a multi-tenant application | +| [`static-methods`](https://github.com/prisma/prisma-client-extensions/tree/main/static-methods) | Adds custom query methods to Prisma Client models | +| [`transformed-fields`](https://github.com/prisma/prisma-client-extensions/tree/main/transformed-fields) | Demonstrates how to use result extensions to transform query results and add i18n to an app | +| [`exists-method`](https://github.com/prisma/prisma-client-extensions/tree/main/exists-method) | Demonstrates how to add an `exists` method to all your models | + +## Going further + +- Learn more about [Prisma Client extensions](/concepts/components/prisma-client/client-extensions).