6

I am following the GraphQL Prisma Typescript example provided by Prisma and created a simple data model, generated the code for the Prisma client and resolvers, etc.

My data model includes the following nodes:

type User {
  id: ID! @unique
  displayName: String!
}

type SystemUserLogin {
  id: ID! @unique
  username: String! @unique
  passwordEnvironmentVariable: String!
  user: User!
}

I've seeded with a system user and user.

mutation {
  systemUserLogin: createSystemUserLogin({
    data: {
      username: "SYSTEM",
      passwordEnvironmentVariable: "SYSTEM_PASSWORD",
      user: {
        create: {
          displayName: "System User"
        }
      }
    }
  })
}

I've created a sample mutation login:

login: async (_parent, { username, password }, ctx) => {
    let user
    const systemUser = await ctx.db.systemUserLogin({ username })
    const valid = systemUser && systemUser.passwordEnvironmentVariable && process.env[systemUser.passwordEnvironmentVariable] &&(process.env[systemUser.passwordEnvironmentVariable] === password)

    if (valid) {
      user = systemUser.user // this is always undefined!
    }

    if (!valid || !user) {
      throw new Error('Invalid Credentials')
    }

    const token = jwt.sign({ userId: user.id }, process.env.APP_SECRET)

    return {
      token,
      user: ctx.db.user({ id: user.id }),
    }
  },

But no matter what I do, systemUser.user is ALWAYS undefined!

This makes sense - how would the client wrapper know how "deep" to recurse into the graph without me telling it?

But how can I tell it that I want to include the User relationship?

Edit: I tried the suggestion below to use prisma-client.

But none of my resolvers ever seem to get called...

export const SystemUserLogin: SystemUserLoginResolvers.Type<TypeMap> = {
  id: parent => parent.id,
  user: (parent, args, ctx: any) => {
    console.log('resolving')
    return ctx.db.systemUserLogin({id: parent.id}).user()
  },
  environmentVariable: parent => parent.environmentVariable,
  systemUsername: parent => parent.systemUsername,
  createdAt: parent => parent.createdAt,
  updatedAt: parent => parent.updatedAt
};

And...

  let identity: UserParent;

  const systemUserLogins = await context.db.systemUserLogins({
    where: {
      systemUsername: user,
    }
  });
  const systemUserLogin = (systemUserLogins) ? systemUserLogins[0] : null ;

  if (systemUserLogin && systemUserLogin.environmentVariable && process.env[systemUserLogin.environmentVariable] && process.env[systemUserLogin.environmentVariable] === password) {
    console.log('should login!')

    identity = systemUserLogin.user; // still null
  }

Edit 2: Here is the repository

https://github.com/jshin47/annotorious/tree/master/server

tacos_tacos_tacos
  • 10,277
  • 11
  • 73
  • 126

2 Answers2

5

There are currently two ways to solve this problem:

  • Using the Prisma client as OP does at the moment
  • Using Prisma bindings as was suggested by @User97 in the accepted answer

You can learn more about the difference between Prisma client and Prisma bindings in this forum post.

As OP is currently using Prisma client, I'll use it for this answer as well!

Let's take a look at a statement OP made in the question:

This makes sense - how would the client wrapper know how "deep" to recurse into the graph without me telling it?

OP stated correctly that the Prisma client can't know how to deep to go into the graph and what relationships to fetch. In fact, unless explicitly told otherwise (e.g. using the $fragment API), the client will never fetch any relationships and will always only fetch the scalar values. From the Prisma docs:

Whenever a model is queried using the Prisma client, all scalar fields of that model are fetched. This is true no matter if a single object or a list of objects is queried.

So, how to properly resolve this situation? In fact, the solution is not to make changes to the way how the Prisma client is used, but to implement an additional GraphQL resolver function!

The point about resolvers is that they're fetching the data for specific fields in your schema. In OP's case, there currently is no resolver that would "resolve" the user relation that's defined on the SystemUserLogin type:

type SystemUserLogin {
  id: ID! @unique
  username: String! @unique
  passwordEnvironmentVariable: String!
  user: User! # GraphQL doesn't know how to resolve this
}

To resolve this situation, you need to implement a dedicated "type resolver" for it like so:

const resolvers = {
  SystemUserLogin: {
    user(parent, args, ctx) {
      return ctx.db.systemUserLogin({id: parent.id}).user()
    }
  } 
}

Full disclosure: I work at Prisma and we're working on adding better documentation and resources for that use case. Also check out this example where explicit resolvers for the author and posts relation fields are required for the same reason.

Hope that helps!

EDIT: We have also added a slightly more thorough explanation in the Prisma tutorial about Common resolver patterns.

jonschlinkert
  • 10,872
  • 4
  • 43
  • 50
nburk
  • 22,409
  • 18
  • 87
  • 132
  • Thank you for the detailed answer. I was wondering what the difference was! I have found the documentation to be wanting, so this really helps. – tacos_tacos_tacos Oct 10 '18 at 02:12
  • I tried your suggestion using `prisma-client` and it seems like my resolver is never actually called, so I am still unable to get it to work with `prisma-client` – tacos_tacos_tacos Oct 10 '18 at 05:06
  • Hmm this is strange! Did you double check that the resolvers are actually passed to your GraphQL server? Normally when a query is resolved, the resolvers for _all_ fields inside the query should be called! So if you're sending a query that uses the `user` of `SystemUserLogin` the `user` resolver should get called. If no, there might be an issue somewhere else! – nburk Oct 10 '18 at 08:00
  • Were you able to resolve the issue in the meantime @tacos_tacos_tacos? Is the resolver still not called? – nburk Oct 11 '18 at 06:32
  • The resolver is still not called... tonight when I get home I will copy and paste the code from the entry point on down... Any ideas? – tacos_tacos_tacos Oct 11 '18 at 20:28
  • Do you maybe have a link to a GitHub repo so I can reproduce the issue? Currently it's difficult for me to tell where the error is since my understanding is that the resolver _should_ be called. So I believe the point we need to investigate is why it is not called. – nburk Oct 12 '18 at 07:49
  • any idea about what's going wrong with my example? I provided a link to repo – tacos_tacos_tacos Oct 19 '18 at 06:34
1

Second parameter of prisma binding functions accept GraphQL query string. Changing following line from

const systemUser = await ctx.db.query.systemUserLogin({ username })

to

const systemUser = await ctx.db.query.systemUserLogin({ username }, `{id username user {id displayName}}`)

will give you the data of user.

Prisma binding will return only direct properties of model in case second parameter is not passed to it.

Raeesaa
  • 3,267
  • 2
  • 22
  • 44
  • I was really hoping this sort of thing would work, but I tried and it doesn't, and the method's interface suggests that it only accepts one parameter anyway: `systemUserLogin: (where: SystemUserLoginWhereUniqueInput) => SystemUserLogin;` – tacos_tacos_tacos Oct 09 '18 at 07:50
  • And `ctx.db.query` is `undefined` – tacos_tacos_tacos Oct 09 '18 at 07:50
  • Are you setting `db` in context while initialising your server? – Raeesaa Oct 09 '18 at 08:04
  • Yes, but I was importing the wrong `Prisma`, I guess... `import {Prisma} from "./generated/prisma";` works, `import {Prisma} from "./generated/prisma-client";` doesnt – tacos_tacos_tacos Oct 09 '18 at 08:09
  • There are two ways to query data from Prisma. You can either use `prisma-binding` or `prisma-client`. My answer uses `prisma-binding`. For `prisma-client`, this is how relationships are queried: https://www.prisma.io/docs/prisma-client/basic-data-access/reading-data-JAVASCRIPT-rsc2/#relations – Raeesaa Oct 09 '18 at 08:15
  • Sorry for the confusion around Prisma client and Prisma bindings. Hope my answer helps – nburk Oct 09 '18 at 09:18