DEV Community

Cover image for What Prisma doesn’t have yet
JS for ZenStack

Posted on • Edited on

What Prisma doesn’t have yet

First of all, this is not a post trying to blame Prisma and promote some other alternatives. Instead, I bet Prisma would be the best Typescript ORM dominant in the full-stack world. That’s why I switched to Prisma from TypeORM, which I have used for many years.

All the missing features I will discuss are from the top(most comments) issues of the Prisma GitHub repository. Therefore if you once run into any of them, I suggest you participate in the discussion thread of that issue which you can benefit from:

  • Knowing some workaround/hack for this issue.
  • Understanding the whole picture of the issue, especially for things you haven’t thought about.
  • Seeing the pros and cons of different proposals and voting for the one you like best or even giving your own proposal.

Most importantly, let the community hear your voice and hope it will improve Prisma.

Custom Attribute

  • Issue

    Allow custom field attributes #3102

    Let's say I want to make sure, that an email is always valid before it gets created. It would be awesome, if I could add an @email directive like so:

    model User {
      id    Int @id
      email String @unique @email
    }
    Enter fullscreen mode Exit fullscreen mode

    While it makes sense, that @email is not allowed right now, if we would allow it, users could now add an email validation middleware. Allowing top-level @email probably doesn't make sense though to keep validation rather strict so that we can also help with typos.

    However, the same as in HTTP Headers, which can start with X- or HTML attributes, which can start with data-, we could add a namespace for custom directives, for example @custom.X.

    That would allow a whole world of extensions for Prisma, which can be dealt with through middlewares.

    And of course, this is just a wild idea, it could also just be trash and maybe we shouldn't do it. So let's have a discussion if this even makes sense :)

If I get to pick up only one to be implemented, it will be Custom Attribute, without a doubt. Not only because there are several issues that could be resolved by this below:

But also, this is like opening the door of the extension to the whole community. As Prisma advocates that we use the schema as the single source of truth for the model of our application, I think the prerequisite is that it has a good extension mechanism. Otherwise, Prisma would have to cover all the different cases by itself to achieve that universally.

Although Prisma Client Extensions feature has come out in version 4.7.0, which allows extending models programmatically in the client, it doesn’t support the advocating of the single source of the truth.

Since the issue has been open for two years since 2020, and there is not any update from the Prisma team yet, not even in the Prisma Roadmap, I’m afraid we won’t see it could come in a short time. I could understand that it would complicate the whole framework and add a burden for future updates. But isn’t that something you have to bear if you aim to build a prosperous ecosystem? Moreover, I think that’s really the power of the open-source community we could rely on. 😊

  • Workaround

You can use make use of the comments to put whatever you need there, which could be accessed in the AST of the schema file. You need to implement your own generator to process it. It would be like what prisma-nestjs-graphql does :

  model User {
      id String @id @default(cuid())
      /// @HideField()
      password String
      /// @HideField({ output: true, input: true })
      secret String
      /// @HideField({ match: '@(User|Comment)Create*Input' })
      createdAt DateTime @default(now())
  }
Enter fullscreen mode Exit fullscreen mode

Soft Delete

There was only one time I initiative upgrading the TypeORM version because of the official support of Soft Delete. See the below benefits I got from it for our SAAS product:

  • Restore the data accidentally deleted by the customer
  • Analyze customer behavior
  • History tracking and audit

Therefore Soft Delete is almost mandatory for me. Lots of people might have a different opinion considering the challenge of the “proper” implementation. That’s why I think it’s up to the ORM to implement it so that we can use it just as simple as what we do for the normal delete. See what TypeORM provided:

  @Entity()
  export class User {
    @DeleteDateColumn()
    deletedDate: Date;
  }

  await myDataSource.createQueryBuilder("users").softDelete().where("id = :id", { id: 1 }).execute();
Enter fullscreen mode Exit fullscreen mode
  • Workaround

I’m not sure whether it’s even a workaround because it’s the official document of Prisma to specify how to implement the soft delete using middleware. Just take a glance at how long the article is and how many lines of code it contains, and you will see why I treat it as a workaround, and also, I guess that’s the reason why this issue has not been closed yet.

BTW, I feel using the new feature Prisma Client Extensions aforementioned to implement it might be more straightforward and simpler. Care to try it? 😉

Type of Json Field

  • Issue

    Define type of content of Json field #3219

    Problem

    Right now if you have the following schema with Json field:

    model User {
      id               Int  @default(autoincrement()) @id
      name             String?
      extendedProfile  Json
    }
    Enter fullscreen mode Exit fullscreen mode

    You'll end up with a problem that you don't have strict type for extendedProfile field in .ts.

    const user = prismaService.user.findOne(...);
    user.extendedProfile // we don't know the type of extendedProfile
    Enter fullscreen mode Exit fullscreen mode

    The one way to fix it, is specify some interface in your code and use it like this:

    interface UserProfile {
        field1: string;
        field2: number;
    }
    
    const user = prismaService.user.findOne(...);
    (user.extendedProfile as UserProfile).field1; // now we have autocompletion
    Enter fullscreen mode Exit fullscreen mode

    But it's not really comfortable to use it like that each time.

    Also we can create some class and create instance of it like that:

    interface UserProfile {
        field1: string;
        field2: number;
    }
    
    class User {
        id: string;
        name?: string;
        extendedProfile: UserProfile;
    
        constructor(user: PrismaUser /* user object returned by prisma */) {
            // ... initialize
        }
    }
    
    const user = new User(prismaService.user.findOne(...));
    Enter fullscreen mode Exit fullscreen mode

    But this solution creates some overhead due to the creation of an additional object.

    Suggested solution

    Maybe we can specify type in schema.prisma file like that?

    json ExtendedUserProfileJson {
        field1  String
        field2  Int
    }
    
    model User {
      id               Int  @default(autoincrement()) @id
      name             String?
      extendedProfile  ExtendedUserProfileJson
    }
    Enter fullscreen mode Exit fullscreen mode

    Alternatives

    Alternatively, we can somehow manage this in the typescript.

    Since JSON data type has been the first citizen of MySQL, we have used it a lot. It’s kind of like adopting the benefit of NoSQL in the SQL world.

In TypeORM, it’s really quite easy to use as it’s really very convenient to use. You can even use inline type definition to get type safety like the below:

  @Entity()
  export class User {
    @PrimaryGeneratedColumn()
    id: number;

    @Column("simple-json")
    profile: { name: string; nickname: string };
  }
Enter fullscreen mode Exit fullscreen mode

In Prisma, of course, it’s not as easy as its schema first. But the suggested solution in the issue might be a way to go:

  json ExtendedUserProfileJson {
      field1  String
      field2  Int
  }

  model User {
    id               Int  @default(autoincrement()) @id
    name             String?
    extendedProfile  ExtendedUserProfileJson
  }
Enter fullscreen mode Exit fullscreen mode
  • Workaround

You would need manually cast it in your code like below:

  interface UserProfile {
      field1: string;
      field2: number;
  }

  const user = prismaService.user.findOne(...);
  (user.extendedProfile as UserProfile).field1;
Enter fullscreen mode Exit fullscreen mode

And the worse thing is that you don’t get type validation when writing.

Multiple Connections / Databases / Datasources

  • Issue

    Multiple Connections / Databases / Datasources #2443

    Problem

    An application may need to access different connections/databases. One use case could be having the exact same set of tables into different databases (multi-tenant approach). Another use case could be having to access punctually some specific data in a separate database/server.

    In any cases "a single connection" for the entire application is a really strong limitation. Any SQL driver or ORM is capable of connecting to multiple databases either by direct support (TypeOrm) or by creating 2 instances. Unless I missed something this is not possible with Prisma client.

    Suggested solution

    I believe that it can be easily achieved with the following approach that does not change much both on the schema and client side of things.

    datasource proj01 {
      provider = "postgresql"
      url      = env("DATABASE_URL_PROJ01")
      models = [Post, User]
    }
    
    datasource proj02 {
      provider = "postgresql"
      url      = env("DATABASE_URL_PROJ02")
      models = [Post, User]
    }
    
    datasource common {
      provider = "postgresql"
      url      = env("DATABASE_URL_COMMON")
      models = [Config]
    }
    
    
    generator client {
      provider = "prisma-client-js"
    }
    model Post {
      id        Int     @id @default(autoincrement())
      title     String
      content   String?
      published Boolean @default(false)
      author    User?   @relation(fields:  [authorId], references: [id])
      authorId  Int?
    }
    model User {
      id    Int     @id @default(autoincrement())
      email String  @unique
      name  String?
      posts Post[]
    }
    
    model Config {
      id    Int     @id @default(autoincrement())
      field string
    }
    Enter fullscreen mode Exit fullscreen mode

    You would then need to make it possible to use one connection/db or another:

    import { PrismaClient } from '@prisma/client'
    const proj01= new PrismaClient('proj01')
    const proj02= new PrismaClient('proj02')
    const proj01Users = await proj01.user.findMany()
    const proj02Users = await proj02.user.findMany()
    Enter fullscreen mode Exit fullscreen mode

    Additional context

    Please note that it has nothing to do with having a dynamic url for being able to point to a different database when in development and/or staging. This feature is also needed but it's a different matter. Note also that this is not exclusively to solve "multi-tenant" scenarios. I may need to access a database on another server whatever the reason.

    Update 01: different database per environment use case

    Thinking a bit more about it, this feature also provides a solution to the problem of having a different database (sqlite vs other database in prod for instance) depending on the environment (prod, staging, dev).

    Adding on my previous example, you could have :

    datasource proj01 {
      provider = "postgresql"
      url      = env("DATABASE_URL_PROJ01")
      models = [Post, User]
    }
    
    datasource proj01-dev {
      provider = "sqlitel"
      url      = env("DATABASE_URL_PROJ01")
      models = [Post, User]
    }
    Enter fullscreen mode Exit fullscreen mode

    And then depending on the environment:

    import { PrismaClient } from '@prisma/client'
    const proj01 = ( process.env.NODE_ENV === 'prod')  ? 
                                new PrismaClient('proj01')  :
                                new PrismaClient('proj01-dev')
    Enter fullscreen mode Exit fullscreen mode



    If somehow you have to access some specific data punctually in a separate database/server, this is like a MUST for you.

  • Workaround

You need to create multiple data models like below:

In the schema

  • prisma/schema1.prisma

    datasource db {
      provider = "postgres"
      url      = env("DB1_URL")
    }
    
    generator client {
      provider = "prisma-client-js"
      output   = "./generated/client1"
    }
    
    model Model1 {
      id         Int @id @default(autoincrement())
      model1Name String
    }
    
  • prisma/schema2.prisma

    datasource db {
      provider = "postgres"
      url      = env("DB2_URL")
    }
    
    generator client {
      provider = "prisma-client-js"
      output   = "./generated/client2"
    }
    
    model Model2 {
      id         Int @id @default(autoincrement())
      model2Name String
    }
    

In the code

  import { PrismaClient as PrismaClient1 } from "../prisma/generated/client1";
  import { PrismaClient as PrismaClient2 } from "../prisma/generated/client2";

  const client1 = new PrismaClient1();
  const client2 = new PrismaClient2();
Enter fullscreen mode Exit fullscreen mode

In the CLI

  prisma generate --schema prisma/schema1.prisma
  prisma generate --schema prisma/schema2.prisma
Enter fullscreen mode Exit fullscreen mode

BTW, if you are using PostgresSQL and would like to query across multiple schemas, you are lucky that they have just released a preview feature for it. Check it out below:

https://www.prisma.io/docs/guides/database/multi-schema#learn-more-about-the-multischema-preview-feature

And leave your feedback about it in the issue:

Preview feature feedback: Prisma multi schema support (`multiSchema`) #15077

Here are details on how the preview feature for multi schema support currently works in Prisma: https://github.com/prisma/prisma/issues/1122#issuecomment-1231773471

Please share your feedback about the multiSchema functionality released in v4.3.0 in this issue.

  • If you encounter a bug, please open a bug report in this repo.
  • If the feature is working well for you, please share this in a comment below or leave a 👍 on this issue.

If you have any questions, don't hesitate to ask them in the #prisma-client channel in the Prisma Slack.

Real Time API

  • Issue

    Subscriptions/real-time API support #298

    Unfortunately at the current point of time Prisma 2 doesn't yet support subscriptions (real-time API features). Prisma 1 followed a different architecture which provided support for subscriptions. We might be planning to bring back real-time/subscriptions functionality in the future based on a standalone "events engine" (which will use CDC under the hood).

    This issue tracks the progress for this feature. Please feel free to share your specific requirements and wishes for this feature. 🙏

    I personally didn’t need this until now, but I guess it would be necessary if your business needs some real-time collaboration. I guess another reason why it gets talked about a lot is that it’s the only feature that got missed from Prisma 1.
  • Workaround

I guess you have to implement it by yourself or find another solution for this.


Introducing ZenStack:a toolkit that supercharges Prisma ORM. We have managed to resolve the first two issues. Check out the posts I wrote for it:


Top comments (0)