omar-dulaimi / prisma-joi-generator

Prisma 2+ generator to emit Joi schemas from your Prisma schema
MIT License
43 stars 9 forks source link

FrontEnd Form Validation #1

Open MentalGear opened 2 years ago

MentalGear commented 2 years ago

Thanks for your work, I've been looking for a way to have one source of truth for both frontend and backend validation from Prisma and this looks very promising!

Would it be possible to have a "frontend mode" where all the validation rules are put into one file / one file per Model, to be used for form validation? 👍

omar-dulaimi commented 2 years ago

Hey @MentalGear, thanks for the feedback.

So a single file with all schemas included. Okay, I'll work on it now! On a side note though, I hope that you use the predefined issue template next time.

Should be released today hopefully :)

MentalGear commented 2 years ago

Thanks for the super quick response time, Omar!

A single file would be great, or maybe even better the option for a file per Database Table/Model.

Really excited about this, since having a "single source of truth" for both backend and frontend form validation has been a full-stack ambition of mine for a long time, and now with Prisma and your awesome community generator this seems to be almost too good to be true.

PS: I'd normally stick to the issue template, but since this was more of a feature request than an issue report, I took the liberty of adapting.

PPS: Feel free to add a tipping jar! :)

omar-dulaimi commented 2 years ago

@MentalGear I think your file per model option is the best one here. If you have run this generator locally, you must have noticed that there is a folder called objects. It contains all schema objects used in model schemas. These objects currently are in separate files. A lot of them are shared among the models. So, while trying to get them all inside a single file, it would cause an issue of order; meaning that some schemas have to come before others, and sometimes it get's long to assure this in case of bigger prisma schemas.

So, I was thinking of keeping the objects directory as it is, but apply your idea on the model schemas.

The directories should look like this after it's done:

-> generated
   -> objects
   -> models
       -> User.schema.ts
       -> Post.schema.ts
   -> index.ts

index.ts will re-export everything inside the models, which will make it easier to import them later. Unless you have something else in mind, please let me know.

Regarding the use of the generated schema from Prisma as a frontend form validator, do you see any issues or limitations?

I honestly don't know of any limitations this could have. It should work fine.

PS: I'd normally stick to the issue template, but since this was more of a feature request than an issue report, I took the liberty of adapting.

No problem at all.

PPS: Feel free to add a tipping jar! :)

Not sure how to do that, do you happen to know a service like that?

MentalGear commented 2 years ago

Sorry for the delayed reply @omar-dulaimi.

I had some thinking to do regarding how my vision of a unified data model usable on both client and server could be archived. I came to the conclusion that probably the inverse of Prisma's generator function would be required.

E.g.: Given a single source of truth file (that contains model structure, datatype, and validation rules), a Prisma File as well as a validation file (not sure yet for which validation lib) will be generated automatically.

I'm sorry to have invoked you, since this is not something your library is meant to do.

However, you have already done a lot of work on Prisma as well as the various validation lib generators.

Maybe you would be interested to work together on such a single source of truth model?

To put it into more concrete terms, I would imagine a model syntax either with:

Personally, I would tend towards the js chaining syntax (yup), as it feels the most readable.

Maybe it would also be possible to just write an (extended) yup file, and have the Prisma file generated from it. Thinking about it, most of the attributes should be able to get inferred into Prisma attributes, like all the primitives of course, but also .oneOf = ENUM, @decorators like @default() = .default(), even linking to another model = .ref('$x'). However @decorators like id, unique, etc would have to be otherwise filled, maybe with custom functions if yup allows for it in their format.

Looking forward to hear your thoughts since you definitely have more experience transforming types from Prisma.

MentalGear commented 2 years ago

Not sure how to do that, do you happen to know a service like that?

Ex: https://www.buymeacoffee.com

omar-dulaimi commented 2 years ago

I'm sorry to have invoked you, since this is not something your library is meant to do.

No problem at all, I like to hear other people's thoughts. This helps in building better software, that people would use.

I came to the conclusion that probably the inverse of Prisma's generator function would be required. E.g.: Given a single source of truth file (that contains model structure, datatype, and validation rules), a Prisma File as well as a validation file (not sure yet for which validation lib) will be generated automatically.

So basically you want to have a single file in which you define all your models using either Yup, Classes or JSON schema. The problem with this approach is that it will go out of sync with Prisma and the databases Prisma supports very quickly and very repetitively. Since the tools you mentioned can not represent a Prisma schema 100% with all of it's attributes and special usages specific to each database type. You won't be able to represent native database types(as they are a lot and databse-specific), and they're important in order to build a performant database, that does not rely on default data types.

We have the Prisma schema, it's an up to date single source of truth. The only thing it misses according to your requirements is the validations.

So, I was thinking of instead specifiying the valdiation rules inside the schema itself, like this:

model User {
  id      Int      @id @default(autoincrement())
/// @MinLength(10)
/// @MaxLength(50)
  name    String?  @db.VarChar(255)
/// @Email()
  email   String   @unique @db.VarChar(255)
}

This way the Prisma generator(whatever it was) can use such meta data in all schemas it generates. So, you get the same behavior in all of your logic; not just one schema you manually define.

Building and maintaining a new source of truth that also generates a Prisma schema, is a big project. In case you haven't heard of it before, there's a project that got popular recently, it's called KeystoneJS. They have their own source of truth, from which they build your Prisma schema and also your dashboard. Check it out, it's really cool.

Also, I have created my Buy Me a Coffee account. Seems promising. I'll add it to my projects' READMEs.

Thank you for your thoughts, and for the website recommendation.

omar-dulaimi commented 2 years ago

Hey @MentalGear

Have you thought about what I added to the discussion the last time? Also, i'm thinking of implementing the 1 file per model change you requested.

Please let me know.

MentalGear commented 2 years ago

I like to hear other people's thoughts. This helps in building better software, that people would use.

Great attitude! Much in line with my own.

Thanks for the hint on KeystoneJS, which seems to be a good solution if you want to run your own server (I try to stay serverless), and only need backend validation within the same file. However, it doesn't satisfy my expectation of having 1 file for db, front and backend validation.

So, I was thinking of instead specifiying the valdiation rules inside the schema itself, like this:

Integrating validation rules into a Prisma schema using compiler (generator) hints with the /// notion seems the better idea, however I think parsing might get difficult, there's no type safety and no syntax highlighting.

At the moment, I'm thinking about writing my own structure from which Prisma and validation schemes can be generated. It's a bigger project, but I hope not too big. Prisma has 4 main components in their schema (name, type, operator, attributes), which should stay constant.

Thank you for keeping this issue active and your interest in general, and feel free to go ahead with the "one file" output if you like, but personally I won't need it.

If you like, I can let you know once I have something presentable for my approach - would be interested in your thoughts and maybe we can even work together on it if you like it.

omar-dulaimi commented 2 years ago

Integrating validation rules into a Prisma schema using compiler (generator) hints with the /// notion seems the better idea, however I think parsing might get difficult, there's no type safety and no syntax highlighting.

Yes, it's just been requested as a feature on the Yup generator repo, so I'm going to give it a go there soon. I will learn more info about the process, since I haven't done this before in any of my generators.

At the moment, I'm thinking about writing my own structure from which Prisma and validation schemes can be generated. It's a bigger project, but I hope not too big. Prisma has 4 main components in their schema (name, type, operator, attributes), which should stay constant.

Actually it's going to be too big, because you're in a way building a new schema syntax. Prisma currently supports a lot of different database types, and each have their own special attributes and mappings. Not to mention the need for a reliable way to model relationships in your syntax (Yup or Joi), on which side, what fields, self relations, etc.

Thank you for keeping this issue active and your interest in general, and feel free to go ahead with the "one file" output if you like, but personally I won't need it.

I'm implementing something similar in order to shorten imports paths and number, so all schema imports will be done with a single line. Hopefully this change will be applied to all generators.

If you like, I can let you know once I have something presentable for my approach - would be interested in your thoughts and maybe we can even work together on it if you like it.

Yes, please do. I'm interested to see where this goes.

MentalGear commented 2 years ago

Yes, it's just been requested as a feature on the Yup generator repo, so I'm going to give it a go there soon. I will learn more info about the process, since I haven't done this before in any of my generators.

👍

Actually it's going to be too big, because you're in a way building a new schema syntax. Prisma currently supports a lot of different database types, and each have their own special attributes and mappings. Not to mention the need for a reliable way to model relationships in your syntax (Yup or Joi), on which side, what fields, self relations, etc.

Well, I was thinking about using prisma format for the validation, and only transpile my own custom "super-structure" to the prisma format.

Thank you for keeping this issue active and your interest in general, and feel free to go ahead with the "one file" output if you like, but personally I won't need it.

Yes, please do. I'm interested to see where this goes.

I have now a typescript/js object super-structure that is transformed into prisma schema and a validation schema. It has type checking and autocomplete, however, I'm not very happy with the readability.

 const models: modelsSchema = {
    User: {
        config: {
            idComposite: ['id', 'email'],
        },
        fields: {
            id: {
                type: Type.Int,
                isId: true,
                default: 'autoincrement()',
            },
            username: {
                aliasInDB: 'userName',

                type: Type.String,
                typeInDB: 'varchar(255)',
                list: true,
                typeIsList: false,

                default: 'Jimmy',
                isId: true,

                nullable: false,
                unique: true,

                validation: string().required().min(3).max(255),

            },

            followers: {
                relToModel: 'User',
                relToField: 'count',
                relOwnFieldName: 'followersID',
                relName: 'followers',
            },

            lastUpdated: {
                type: Type.DateTimeAuto,
                isId: true,
                nullable: false,
                unique: false,
                default: 'now()',
            },
        },
    },
}

Prisma's format is just shorter and more compact. Maybe I go with a class decorator syntax, or the /// validation hints are the nicer way to go after all. Have you tried integrating these yet?

omar-dulaimi commented 2 years ago

@MentalGear I have been busy lately, I'm trying to get back on track with open source.

I don't like that structure to be honest. Stuff like string().required().min(3).max(255) won't work unless it's stringified. And if they are stringified, they become like 'now()', varchar(255) and 'autoincrement()'; which are not type-safe and could cause a lot of errors. You also lose auto-completion, which is something critical for developer experience.

Prisma's format is just shorter and more compact

Indeed.

Maybe I go with a class decorator syntax, or the /// validation hints are the nicer way to go after all. Have you tried integrating these yet?

Not yet honestly. I'll try to dedicate some time for that enhancement in the future.

MentalGear commented 2 years ago

Prisma is looking for comment on their validation implementation. I'm sure you have some thoughts! https://github.com/prisma/prisma/issues/3528#issuecomment-1233557205 

omar-dulaimi commented 2 years ago

Thank you for bringing it to my attention.