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?
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
}
https://github.com/jshin47/annotorious/tree/master/server
Relational databases In SQL, you use a foreign key to create a relation between two tables. Foreign keys are stored on one side of the relation. Our example is made up of: A foreign key column in the Post table named authorId .
Prisma validates many-to-many relations in MongoDB with the following rules: The fields on both sides of the relation must have a list type (in the example above, categories have a type of Category[] and posts have a type of Post[] ) The @relation attribute must define fields and references arguments on both sides.
Prisma's main benefit is to provide an abstraction layer that makes you more productive compared to writing SQL. If you're a solo developer that is very comfortable with SQL, and you just want to be sure that your database layer is type-safe, a lower-level TypeScript database library might be better for you.
There are currently two ways to solve this problem:
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With