Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add runtime validation to models #3528

Open
2color opened this issue Sep 4, 2020 · 43 comments
Open

Add runtime validation to models #3528

2color opened this issue Sep 4, 2020 · 43 comments
Labels
kind/feature A request for a new feature. team/client Issue for team Client. topic: extend-client Extending the Prisma Client topic: prisma-client

Comments

@2color
Copy link
Contributor

2color commented Sep 4, 2020

Problem

Before saving data to the database, it's common to perform validation.

When using Prisma to save user data (in an API), Prisma will perform runtime validation before saving to the database and throw an error if the user data doesn't match the type.

Even with TypeScript, these checks cannot happen at compile time because the user data is not known.

Since there are situations where you want to validate the data as a separate step to saving to the database, it'd be nice if Prisma exposed the same validation functionality as a standalone method on models.

Suggested solution

Provide a validate function for models which is based on the Prisma schema:

model User {
  id        Int     @default(autoincrement()) @id
  email     String  @unique
  firstName String?
  lastName  String?
  social    Json?
  isAdmin   Boolean @default(false)
}

API ideas:

const { isValid, error } = prisma.user.validate(user)

The idea is that If the input is valid, then the error will be undefined. If the input is invalid, error is assigned an Error object providing more information.

OR

const user = { firstName: 'Laura' }
try {
  const validatedUser = await prisma.user.validate(user)
} catch (error) {
  // handle error
}

Additional context

For inspiration, Joi's validation errors might be useful (albeit more extensive as Joi has richer validation rules).

@2color 2color added the kind/feature A request for a new feature. label Sep 4, 2020
@gregbarcza
Copy link

We would need the ability to add custom validation rules.
Example use case: Storing field like social security number where you have custom business logic to validate if the number is valid (eg. the sum of first and fifth digit must be equal sixth digit modulo 10)

@matthewmueller matthewmueller added team/client Issue for team Client. and removed team/product labels Nov 25, 2020
@orenmizr
Copy link

so.... until that happens.... what is the best way to do model validation today with prisma 2 ?

@2color
Copy link
Contributor Author

2color commented Jan 21, 2021

so.... until that happens.... what is the best way to do model validation today with prisma 2 ?

Hey @orenmizr 👋🏼,

Prisma validates input at runtime and throws an exception if validation fails (and the input data doesn't match the type in the Prisma schema)

This is very useful in both JavaScript projects where there are no compile-time checks and TypeScript projects where payload/input data cannot be type-checked at compile time.

Example

Given the following Prisma schema:

https://github.com/prisma/prisma-examples/blob/b5c3aaa8df27c39422ea64525f513e025c8358c7/javascript/script/prisma/schema.prisma#L10-L24

This is the error that will be thrown with mistyped input in a JavaScript node.js script using Prisma Client:

PrismaClientValidationError:
Invalid `prisma.user.create()` invocation:

{
  data: {
    email: 'alice@prisma.io',
    name: 1,
          ~
    posts: {
      create: {
        title: 'Watch the talks from Prisma Day 2019',
        content: 'https://www.prisma.io/blog/z11sg6ipb3i1/',
        published: 'hello'
                   ~~~~~~~
      }
    }
  },
  include: {
    posts: true
  }
}

Argument name: Got invalid value 1 on prisma.createOneUser. Provided Int, expected String or Null.
Argument published: Got invalid value 'hello' on prisma.createOneUser. Provided String, expected Boolean.

@orenmizr
Copy link

orenmizr commented Jan 21, 2021

thanks for replying 🙏
not sure if that is the original intent of the author - but i am looking for some validation into the model like in typeORM. you get an object over the network and you want to know whether you should continue with DB update.

@Column()
@IsEmail()
email: string;

@Column()
@IsDate()
createDate: Date;

@voycey
Copy link

voycey commented Jan 27, 2021

I'm unsure why this is not a thing - every good ORM should have the ability for easy validation, are there any 3rd party libraries that interface cleanly with Prisma for this?

@mmahalwy
Copy link

I guess another workaround is using something like yup and validating before creating/updating? But need to remember every time.

@gradinarot
Copy link

is there any news on this?

@malavshah9
Copy link

Yes, I think Prisma should have some method to go for runtime validation.

@2color
Copy link
Contributor Author

2color commented Apr 16, 2021

It's worth noting here that even if Prisma were to provide an interface for runtime model validation, Prisma's type system is not quite as complex as the one a validation library would have.

Prisma's type system is designed to abstract the various nuances between databases thereby simplifying the development experience and providing rich type safety. If it were to expose a model-based validation, changes to the PSL (Prisma schema language) would be necessary to validate things like email. Because for the Prisma schema to remain the source of truth, it would need to be able to represent this kind of information.

@alimehasin
Copy link

How about configure the PrismaClient when insatiate it and pass joi objects, each object will represent a model, PrismaClient will run the validation and either thrown an error if validation failed or complete the execution if it succeed.

As far as I know PrismaClient also do some basic validation and with joi integration we can get include the result of these validation in the same response, I am not sure but unique value validation might be done here and it is great to have unique value errors here.

@Manubi
Copy link

Manubi commented Apr 17, 2021

I also would like to see best practices regarding validation with prisma. :)

@maxerbox
Copy link

It's worth noting here that even if Prisma were to provide an interface for runtime model validation, Prisma's type system is not quite as complex as the one a validation library would have.

Prisma's type system is designed to abstract the various nuances between databases thereby simplifying the development experience and providing rich type safety. If it were to expose a model-based validation, changes to the PSL (Prisma schema language) would be necessary to validate things like email. Because for the Prisma schema to remain the source of truth, it would need to be able to represent this kind of information.

Could be supercool if we could add metadata for generators to fields

@unlight
Copy link

unlight commented Apr 20, 2021

+1 for custom metadata. There is an issue #3102 but no movement there

@GCastilho
Copy link

The ideas presented in #3102 seems really good, like:

directive Email {
  type String
  validate <email regex here>
}

directive Salary {
  type Number
  validate v => v > 0
}

Then being used like this:

model User {
  id    Int @id
  email String @unique @email
  salary Number @salary
}

An implicit conversion, say, a numeric string to a number (like mongoose does) would also be nice to prevent from manually having to cast the values or being too strict about types that JS considers "interchangeable" in some cases

@mmahalwy
Copy link

@GCastilho that's a great solution.

Another option is to set these types when initiating PrismaClient ?

@GCastilho
Copy link

@mmahalwy you mean like passing this validators when instantiating the PrismaClient? But then the prisma schema won't be the "single source of truth" anymore

@grimen
Copy link

grimen commented Jul 24, 2021

As most of you know there is a widely powerful, standardized, spread, accepted specification for schema validation which is JSON Schema, thus not taking that solution/ecosystem into account for Prisma validations would be a mistake IMO.

Since Prisma would never be able to replace data validation on all abstractions of a complete system, one would end up with writing validations and/or validation schema generators in more than one place if Prisma would implement an internal validator DSL, which would be unfortunate for such great potential of ecosystem.

@orenmizr
Copy link

orenmizr commented Jul 25, 2021

has anyone tried https://typegraphql.com/ ? thoughts ?
i have yet taken it for a ride but it seems to have class validation + typescript + prisma integration

@maxerbox
Copy link

has anyone tried https://typegraphql.com/ ? thoughts ?
i have yet taken it for a ride but it seems to have class validation + typescript + prisma integration

I'm using it because it generates Classes instead of Types for model objects (works with https://github.com/clayrisser/nestjs-crud-prisma), but it doesn't add runtime validation. Actually, there is an issue mentionning it MichalLytek/typegraphql-prisma#35

@unlight
Copy link

unlight commented Jul 25, 2021

In prisma-nestjs-graphql generator has implemented feature custom decorators which allow add class-validator decorators.
It's designed for using with @nestjs/graphql, but people are saying that it fits for their needs even if they are using REST.

@aliksend
Copy link

Consider config option to generate raw types (like now) or zod-schemas (with runtime req/resp validation) or joi-schemas etc.

@matthewmueller
Copy link
Contributor

We just wrote some documentation on how to add custom validation with Prisma: https://www.prisma.io/docs/concepts/components/prisma-client/custom-validation

We do plan to implement this feature into the Prisma Client eventually, but this is meant to help you along in the meantime. If you'd like to chat about this topic or you have any questions, feel free to book some time with me.

@noor-codes
Copy link

Prisma is an ORM which is responsible for managing database, not validations.
But If Prisma provides a way to add our own custom validations using validator.js, Yup or other similar libraries instead of doing it on it's own would be a better idea because validations are complex can add up to the Prisma bundle.

We can live without it but If Prisma can make the experience better. That would be great. No Harm done!

@gsamal
Copy link

gsamal commented Apr 7, 2022

I have an use-case where I am trying work on a dynamic query language for my app. It is going to be used with findMany and will accepts options such as table, fields, filters etc. I am working on an utility that will convert a custom schema to the format that Prisma accepts. Taking the simple example of User model from Prisma docs, the typescript type should be UserFindManyArgs (imported from prisma-client).

But the object I am building is dynamic and I wish to validate the final object with the type based on table name. F.e. if table name is User, the final options should be of type UserFindManyArgs. Is there any way to do it? I have checked out io-ts but finding it impossible to validate against existing type/interface.

@gsamal
Copy link

gsamal commented Apr 8, 2022

@2color I agree with your original suggested approach, it will be beneficial for a lot of cases. I would also like to have validate feature of other payloads such as the one we can feed into FindMany.
Ref.

const result = await prisma.user.findMany({
  where: {
    email: {
      endsWith: 'prisma.io',
    },
    posts: {
      some: {
        published: true,
      },
    },
  },
  include: {
    posts: {
      where: {
        published: true,
      },
    },
  },
})

@millsp millsp added the topic: extend-client Extending the Prisma Client label Apr 25, 2022
@okezieobi
Copy link

Prisma is an ORM which is responsible for managing database, not validations.
But If Prisma provides a way to add our own custom validations using validator.js, Yup or other similar libraries instead of doing it on it's own would be a better idea because validations are complex can add up to the Prisma bundle.

We can live without it but If Prisma can make the experience better. That would be great. No Harm done!

My thoughts exactly, Prisma already makes types based on its models accessable, there are libraries that can build validations based on those types like class-validator or typescript-ts.

Fun fact, I have been ORM hopping for the last few weeks, from Sequelize to TypeORM to here and imho Prisma has the most unique take on what an ORM should be concerned with, I like it the way it is, it's lean and mean, like Rocky right before he knocks out Ivan 😁

@MentalGear
Copy link

MentalGear commented May 22, 2022

I think the big selling point of @prisma is that it is a one-stop solution for many database interaction features.

If validators could be used on the frontend, it would mean less work for devs to keep validation in sync since we can use 1 source of truth, which would make Prisma the more valuable.

I agree with the point that there are great validation libs out there which should be used in Prisma to hit the ground running instead of reinventing the wheel.

It would be amazing if we could just import the validation rules from joi (or otherwise) and use the same rules for frontend form validation and backend database validation.

@MentalGear
Copy link

Just found out about Generators and community plugins. These might be what we are looking for:
https://www.prisma.io/docs/concepts/components/prisma-schema/generators#community-generators

Especially:

Would be great if the guide would have an explicit page on how to convert validation to be used for front end form validation using generate and community plugins. @matthewmueller

@collinscangarella
Copy link

Here's how apollo is tackling the exact same problem: https://www.apollographql.com/blog/backend/validation/graphql-validation-using-directives/

@millsp
Copy link
Member

millsp commented Aug 31, 2022

Hey everyone, we just shared a proposal about Prisma Client Extensions. While this isn't going to perform validations via the schema, it will provide a way for you to store custom validation functions on your models (and perhaps create a generic one). Here's how it would work:

const prisma = new PrismaClient().$extends({
	$model: {
		User: {
			validate(user: object) {
				// ... Use Joi, or Yup here
			},
		},
	}
})

await prisma.user.validate(randomData)

I am interested in receiving your feedback and suggestions in the Prisma Client Extensions proposal. Thanks!

@MentalGear
Copy link

Good to see Prisma is taking this issue seriously!
I'm sure omar-dulaimi, maker of various validation generators, has some thoughts here. https://github.com/omar-dulaimi

@omar-dulaimi
Copy link

Thanks for the shoutout @MentalGear

Recently, I have been thinking about doing validations in Prisma like this:

generator client {
  provider            = "prisma-client-js"
  validationsProvider = "joi"
  validationsOutput   = "../src/validations"
  validationsMode     = "AUTO_RUN" // or GENERATE_ONLY
}

When generating the client, it also generates full crud schemas of the provider specified above, in the output directory also specified.

Once they get generated, we can then incorporate them inside the suggested model methods way to do validations. It would be as simple as importing and executing them.

Relying on known libraries like Joi, Zod, Yup, etc helps reduce the amount of work required to redo what they have been doing for years. So I see it as a plus for the Prisma team.

Possible structure

package.json
src => 
      validations =>
                        index.ts
                        User =>
                                index.ts
                                createUser.schema.ts
                        Post =>
                                index.ts
                                updatePost.schema.ts

AUTO_RUN Mode

When this mode is selected all generated schemas will be run automatically before actions
with the same name get executed. This of course means that the user does not really need
or depend on the result of the validations; they just need to make sure data are correct before getting inserted/queried.

Which helps abstracting the entire validation layer away from the user and let them focus on the business logic instead.

GENERATE_ONLY Mode

This mode does exactly what it means; generate the validations schemas and stop there.
It will be then the users' responsibility to manually run them:

// does not throw
const { result, error } = await prisma.user.validate(randomData);

Because this user either wants access to validation results or to customize/add more schemas.

This can be done using the validate method described in the proposal:

import { UserCreateOneSchema } from "./validations"

const prisma = new PrismaClient().$extends({
  $model: {
    User: {
      async validate(user: object, opName: string) {
        try {
          if (opName === "createSuperCustomOne") {
            const result = await UserCreateOneSchema.validateAsync(user);
            return { result, error: null };
          }
        } catch (error) {
          return { result: null, error };
        }
      },
    },
  }
})

const { result, error } = await prisma.user.validate(randomData)

The example above should not completely replace the validate method defined by Prisma under the hood; it's just meant to add to whatever already there. So, default actions won't be affected unless overriding them was intentional. Maybe it would make more sense to rename the method to attach, add or something more appropriate. Or even push to an array.

Also, the Prisma client or the generator in charge of generating the validation schemas, should not delete any custom schemas made by the user.

Would love to hear your thoughts on this.

@yukimotochern
Copy link

yukimotochern commented Sep 17, 2022

Would be better if the prisma client can export both type and jsonschema (something like typebox), such that the user can choose their own library, like ajv or so, for validation. The query method can be configured such that the returned values are validated.

@MentalGear
Copy link

Any updates / feedback on validation, @millsp ?

@miketromba
Copy link

miketromba commented Oct 22, 2022

A bunch of runtime validation API ideas:

Model-level validation

prisma.User.$useValidation({
    name: (value) => {
        // Custom validation logic here (Use whatever... zod, joi, etc.)
        // Throw with error message if invalid or return if valid
    }
    // ...optionally define other fields
})

Support async validation

prisma.User.$useValidation({
    name: async (value) => {
        // Perform async queries before making a decision
    }
})

Native integrations with popular validation libraries for even better DX

For inspiration, see how tRPC supports this feature https://trpc.io/docs/v10/procedures#with-zod

// E.g. with zod
import { z } from 'zod'

prisma.User.$useValidation({
    name: z.string().min(2).max(100)
})

// Alternative
prisma.User.$useValidation(
    z.object({
        name: z.string().min(2).max(100)
    })
)

Bonus (prob overkill): support user-injected context object

// Example use case: you might inject a request object
// ...or maybe your validation of one field depends on other the value of another field...
prisma.User.$useValidation({
    name: async (value, ctx) => {
        // Same as above
    }
})

// Then when calling a method optionally inject context
await prisma.User.update({
    // ...specify update inputs
    context: { req } // E.g. include context object
})

Schema-level validation declarations

An even better solution might be to support schema-level validation declarations like so:

model User {
  id      Int      @id @default(autoincrement())
  email   String   @unique @validate('isEmail') @validate('min', 2) @validate('max', 100) // Declare runtime validations
  email2  String   @unique @validate('isEmail', ['min', 2], ['max', 100]) // Or combined syntax
  name    String?

  // Support compound validations ??
  @@validate('emailsHaveSameDomain', [email, email2])
}

This way, you get closer to having a single source of truth.

Then, when instantiating the client, the user must provide these definitions or the PrismaClient constructor will throw

const prisma = new PrismaClient({
    validations: {
        isEmail: value => {
            // Throw with error message if invalid
        },
        // OR with first-class support for popular validation libraries (e.g. zod)
        isEmail: z.string().email()
    }
})

// Alternate registration syntax
PrismaClient.useValidator('isEmail', () => {})

In other words, allow runtime validation declarations to exist on the schema, and leave implementation details to the end user at the time of client instantiation.

@tmtron
Copy link

tmtron commented Oct 23, 2022

Bonus (prob overkill): support user-injected context object

IMHO this is not a bonus, but very important for i18n - e.g. when you have a multilingual application you must somehow pass information about the current user: i.e. the language, date-time format, number format (decimal separator, etc.).
e.g. in class-validator #169 there is still no simple way to translate validation messages

@shawnmuzick
Copy link

The problem I've run into mostly, is that I have to manually redefine classes/objects. I can write my own validation checks, but I don't think it makes sense to manually define a class or object to validate against. If the Prisma types that are exposed were classes or objects instead of interfaces, I'd be able to make comparisons against it. As it stands now, I'd have to manually create an object that mimicks the model/interface, and update it whenever the schema changes, thus losing the "single source of truth".

@Marcjazz
Copy link

Marcjazz commented Feb 17, 2023

Problem

Before saving data to the database, it's common to perform validation.

When using Prisma to save user data (in an API), Prisma will perform runtime validation before saving to the database and throw an error if the user data doesn't match the type.

Even with TypeScript, these checks cannot happen at compile time because the user data is not known.

Since there are situations where you want to validate the data as a separate step to saving to the database, it'd be nice if Prisma exposed the same validation functionality as a standalone method on models.

Suggested solution

Provide a validate function for models which is based on the Prisma schema:

model User {
  id        Int     @default(autoincrement()) @id
  email     String  @unique
  firstName String?
  lastName  String?
  social    Json?
  isAdmin   Boolean @default(false)
}

API ideas:

const { isValid, error } = prisma.user.validate(user)

The idea is that If the input is valid, then the error will be undefined. If the input is invalid, error is assigned an Error object providing more information.

OR

const user = { firstName: 'Laura' }
try {
  const validatedUser = await prisma.user.validate(user)
} catch (error) {
  // handle error
}

Additional context

For inspiration, Joi's validation errors might be useful (albeit more extensive as Joi has richer validation rules).

class-validator and class-transformer packages combine together will eventually solve any runtime type validation and just in case you're using NestJS framework check https://docs.nestjs.com/openapi/mapped-types for more complexes types.

@Manubi
Copy link

Manubi commented Feb 17, 2023

Can we please make it in a way that I write the validation once and I can use it on the DB, the resolvers, and in the frontend? Meaning one single source of truth that can and should be used for validation everywhere. Thank you!

@Marcjazz
Copy link

Can we please make it in a way that I write the validation once and I can use it on the DB, the resolvers, and in the frontend? Meaning one single source of truth that can and should be used for validation everywhere. Thank you!

Actually in a mono repo workspace you can write a single library for your type validation and use it on all apps found within the workspace

@yosietserga
Copy link

I think you could use this from the documentation https://www.prisma.io/docs/concepts/components/prisma-client/client-extensions/model#add-a-custom-method-to-all-models-in-your-schema

@olivierwilkinson
Copy link

olivierwilkinson commented Apr 2, 2023

Hi 👋 ,

I've just released a middleware called prisma-validation-middleware that validates data when creating or updating records, including when doing so through a relation. For example it's possible to validate the Comment data used in the connectOrCreate below:

await client.post.update({
  where: { id: 1 },
  data: {
    comments: {
      connectOrCreate: {
        where: { id: 2 },
        create: [comment data you want to validate]
      },
    },
  },
});

It is built to support a javascript validation function and supports using Zod, Superstruct or other 3rd party validation libraries. There are some examples for Zod and Superstruct in the readme.

While this does mean that the Prisma schema is not the only source of truth, it does enable global validation of data while we wait for the $nestedOperations API to land in client extensions.

I hope people find it useful! Please don't hesitate to raise any issues / feature requests. 🙏

p.s. If you are curious how it is handling nested write operations using middleware check out prisma-nested-middleware which this is built upon 😄

@kavyantic
Copy link

What's the status of this request. Has it been implemented?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/feature A request for a new feature. team/client Issue for team Client. topic: extend-client Extending the Prisma Client topic: prisma-client
Projects
None yet
Development

No branches or pull requests