Prisma Beta.2 and RWJS: Limited Generator Support for Relations (with workarounds)

Prisma’s 2.0.0-beta.2 is now included in RedwoodJS v0.5.0. One of the major Prisma updates is a new relation syntax with support for complex schema relations, which is covered in full with examples in the Prisma 2.0 Documentation.

As long as you are writing your own GraphQL SDL code, you can use all Prisma supported features in RedwoodJS today!

Using RWJS Generators with Prisma Beta.2

However, there are limitations to the Prisma relation support when using Redwood’s generators, specifically for 1) yarn rw g scaffold ... and 2) yarn rw g sdl ... and 3) yarn rw g service ....

Note: the Scaffold generator uses the SDL and Service generator. And the SDL generator uses the Service generator.

These generators will run correctly. However, when you try to use the associated CRUD UI (or your own UI, if applicable), you will encounter errors when trying to “save”.

Here’s what is going on.

Redwood supports relationships in SDL files the way you’d expect. For example, you can write queries like this:

posts {
  id
  title
  body
  user {
    name
    email
  }
}

And the Redwood SDL generator, which calls to the Service generator, will make this work.

But when it comes to relationships between models in schema.prisma, Prisma doesn’t allow you to save the foreign key field on any Scaffolds that you generate.

Example Schema Using @relation

With Prisma Beta.2, the schema.prisma now supports these models (Note the @relation on post.user below):

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  posts Post[]
}

model Post {
  id     Int     @id @default(autoincrement())
  title  String  @unique
  body   String
  user   User    @relation(fields: [userId], references: [id])
  userId Int
}

Using Redwood’s generators to build a CRUD scaffold for Post, you can successfully run yarn rw g scaffold post. But when you run yarn rw dev, and then try to create a new post and save from the UI, you’ll get an error.

Looking at the Service file the Redwood generator created, api/src/service/posts.js, here’s what the mutation looks like to create a new post:

export const createPost = ({ input }) => {
  return db.post.create({
    data: input,
  })
}

And the corresponding posts.sdl.js:

  type Post {
    id: Int!
    title: String!
    body: String!
    user: User!
    userId: Int!
  }

The issue is with Redwood’s use of userID. We are unable to create a new record by using the foreign key of another table. In this case, where Post has a userId column, we cannot just set the userId and save the record.

We have opened a GitHub issue with Prisma to request support for our Generator use case. You can learn more and follow along here.

A Workaround

'Cause we know you’re still going to use Generators…

If you would still like to use Redwood’s generators for this type of schema, our very own @rob has devised a workaround, aka a Handy-Dandy-Hack™. You’ll need to use the following to modify your create and update functions in your Redwood generated Services by running input through this:

const foreignKeyReplacement = (input) => {
  let output = input
  const foreignKeys = Object.keys(input).filter((k) => k.match(/Id$/))
  foreignKeys.forEach((key) => {
    const modelName = key.replace(/Id$/, '')
    const value = input[key]
    delete output[key]
    output = Object.assign(output, {
      [modelName]: { connect: { id: value } },
    })
  })
  return output
}

Applied to your own posts.js, your code would look like this:

// api/src/services/posts/posts.js

import { db } from 'src/lib/db'

// super hacky workaround function by @rob 🚀
const foreignKeyReplacement = (input) => {
  let output = input
  const foreignKeys = Object.keys(input).filter((k) => k.match(/Id$/))
  foreignKeys.forEach((key) => {
    const modelName = key.replace(/Id$/, '')
    const value = input[key]
    delete output[key]
    output = Object.assign(output, {
      [modelName]: { connect: { id: value } },
    })
  })
  return output
}

...

// create Post example using the workaround 
export const createPost = ({ input }) => {
  return db.post.create({
    data: foreignKeyReplacement(input),
  })
}

...

This is only a limited example for Post create. To fully implement this workaround, you will need to add this to all cases where you used the Generator on a model resulting in Service files with ‘create’ and ‘update’ functions.

Generators Now Support Forms Using Int Type

Previously, if your schema contained an Int field and you used the Scaffold generator, you would encounter a GraphQL error when submitting the corresponding form including the Int input. The GraphQL API would be expecting type Int, however, the input form would return a type string (even though it was validating the input for Int).

Now the Redwood generators correctly cast the type to Int on form submit. For example:

const onSubmit = (input) => {
  const castInput = Object.assign(input, { userId: parseInt(input.userId) })   
  createPost({ variables: { input: castInput } })
}

If you are manually creating forms, we recommend you cast to the required type in the onSubmit form handler.

Hope this helps! Any questions?

7 Likes