How to implement mutations with optional arguments in GraphQL? - graphql

I am learning about graphql, and went through the https://www.howtographql.com/graphql-js/3-a-simple-mutation/ tutorial, and was interested in what the implementation of the updateLink mutation as follows would look like.
type Query {
# Fetch a single link by its `id`
link(id: ID!): Link
}
type Mutation {
# Update a link
updateLink(id: ID!, url: String, description: String): Link
}
The reason I am asking this is that every other mutation implementation I have seen uses only NON-optional parameters. I am curious if there is a community-agreed-upon pattern for extracting and applying only the provided non-null arguments(url, description) from the given context and applying them to relevant the database record.
I have considered checking if each variable is null as follows, but this approach looks way messier than I would expect compared to the rest of the 'magic' and simplicity that Graphql provides.
updateLink(root, args, context) {
if (args.url == null && args.description == null){
return null
} else if (args.url == null) {
return context.prisma.updateLink({
id: args.id,
description: args.description
})
} else {
return context.prisma.updateLink({
id: args.id,
url: args.url
})
}
}
Please let me know if you found a cleaner way to extract and apply the optional arguments(url, description).
Another consideration I had was to make two separate update mutations as follows.
type Query {
# Fetch a single link by its `id`
link(id: ID!): Link
}
type Mutation {
# Update a link
updateLinkURL(id: ID!, url: String!): Link
updateLinkDescription(id: ID!, description: String!): Link
}
The thinking here was with limited arguments and a declarative mutation name, one could force the arguments to be Non-Null. The main issue here is that one can have many update methods for tables with many columns, this would also start to look messy.
FYI I am using prisma as my ORM.

const resolvers = {
Query: {
info: () => `This is the API of a Hackernews Clone`,
feed: () => links,
link: (parent, args) => {
// console.log(args)
return links.find((link) => link.id === args.id)
}
},
Link: {
id: (parent) => parent.id,
description: (parent) => parent.description,
url: (parent) => parent.url,
},
}

Related

starting with graphql-problems with queries

Hi I have just started with graphql so bear with me and be very explicit.
I have a database in MySQL with a list of departments with the attributes of id, name and array of users. I have explicited that in the schema in this way
type Department {
id: ID!
name: String!
user: [User]
}
then I have created a couple of queries, ie. allDepartments and getDepartment in this way
type Query {
departments: [Department!]!
getDept(id: ID!): Department!
}
the resolver functions are those ones
Query: {
departments: async (parent, args, context) => {
return context.prisma.department.findMany();
},
getDept: async (parent, args, context) => {
return context.prisma.department.findUnique({
where: { id: args.id },
});
},
},
however
when I query the alldepartments, I get the list but the user array is empty (whereas there is at least one person each)
with getDept I manage to get the department only if I hardcode the id in the where obj. how can I implement a query where I can pass the id as argument and have the findunique to catch that particular one? can I use the name of the department instead of the id?
the args.id needs to be parsed as int
findUnique is not the right method since it has to be used with fields that are marked as "unique" in the schema

How to chain mutations in apollo-client while using a form component

Apollo-Client 2.0. I am using chained Mutation components. I am trying to pass a returned value from the first Mutation to the second Mutation. I execute the mutations when an onSubmit button is clicked on a form component. The returned value from first mutation is not being passed as one of the "variables" in second mutation
I reviewed solutions in two very similar posts: How to wrap GraphQL mutation in Apollo client mutation component in React and How to chain together Mutations in apollo client. I think my use of a form is adding some additional complexity to my solution. Although the passed value (competitionId) is visible in the handleOnSubmit function (if I console log after createCompetition() in handleOnSubmit), it is not getting passed as a variable in the second Mutation which is called in the handleOnSubmit. The result is a successful execution of the first Mutation and a 400 error on the second mutation: “errors”:[{“message”:“Variable \“$competitionId\” of required type \“ID!\” was not provided.” To be more specific, the value of CompetitionId DOES get passed to the second mutation after the first mutation runs, but it does not get passed as a "variables" to the createMatch function passed as an argument to the handleOnSubmit. It looks like the "variables" passed along with the createMatch function to the handleOnSubmit, only include the variables that are available when the submit button is clicked. The competitionId, is generated after the submit button is clicked and the first mutation returns it as a result.
handleOnSubmit = async(event, createCompetition, createMatch) => {
event.preventDefault();
await createCompetition();
await createMatch();
this.clearState();
this.props.history.push('/home');
}
render () {
const {location, name, action, caliber, rifleName, dateOf,competitionScore} = this.state;
const { matchNumber, targetNumber, relay, distanceToTarget, matchScore} = this.state;
return (
<div className="App">
<h2 className="App">Add Competition</h2>
<Mutation
mutation={CREATE_COMPETITION}
variables={{location, name, action, caliber, rifleName, dateOf, competitionScore}}
refetchQueries={() => [
{ query: GET_ALL_COMPETITIONS, variables:{name: name}}
]}
update={this.updateCache}>
{(createCompetition, {data, loading, error}) => {
if(loading) return <div>loading competition...</div>
if(error) return <div>error: {error}</div>
let competitionId;
if(data) {
competitionId = data.createCompetition._id;
}
return (
<Mutation
mutation={CREATE_MATCH}
variables={{competitionId, matchNumber, targetNumber, distanceToTarget, matchScore}}>
{(createMatch, {_, loading, error}) => {
if(loading) return <div>loading match...</div>
return (
<form
className="form"
onSubmit={event => this.handleOnSubmit (event, createCompetition, createMatch)}>
<label> remaining form deleted for brevity
I expected the value of the CompetitionId to be passed as a variable to the createMatch function called in the handleOnSubmit method. It is not provided.
Seems what you needs is nested mutations :thinkingface;
Q: Are you using prisma?
Well, in GraphQL you can create nodes by a single mutation, this is pretty simple if your Types are related, so I assume this is your case.
And should looks something like this:
datamodel.graphql
type Competition {
id: ID! #unique
name: String!
match: Match! #relation(name: "CompetitionMatch")
}
type Match {
id: ID! #unique
name: String!
campetition: Competition! #relation(name: "CompetitionMatch")
}
So, now in your schema.graphql should looks like this:
type Mutation {
createCompetition (name: String! match: MatchInput): Competition
}
input MatchInput {
name: String!
}
and now when you call your createCompetition mutation, you have to send the match data, like so:
mutation createCompetition (
name: 'Loremp competition'
match: { name: 'child match'}
) {
id
name
match {
id
name
}
}
Ref: https://www.graph.cool/docs/reference/graphql-api/mutation-api-ol0yuoz6go/#nested-create-mutations
Hope this help!
regards
Where the if (data) is, is where you should return the 2nd mutation

Unable to use Fragments on GraphQL-yoga with Primsa

I am using graphql-yoga with Prisma and Prisma-Bindings
I'm trying to add a fragment to my resolver so that a specific field (id in this situation) is always fetched when the user asks for a custom field, costsToDate.
This is so i can make some additional queries needed to build the result for that field, and i need the ID of the object for that.
Unfortunatley i can't seem to get it to work, and the documentations seems a little lacking on the specifics with graphql-yoga and Prisma.
Here is the definition of the type:
type Job {
id: ID!
projectNumber: String!
client: Client!
name: String!
description: String
quoteNumber: String
workshopDaysQuoted: String!
quoted: String!
targetSpend: String!
costs: [JobCost!]!
estimatedCompletion: DateTime
completed: Boolean!
costTotal: String
workshopDaysUsed: String
costsToDate: String
}
And here is the resolver for the query:
const jobs = {
fragment: `fragment description on Jobs { id }`,
resolve: jobsResolver
}
async function jobsResolver(root, args, context, info) {
await validatePermission(args,context,info,['admin','user','appAuth'])
const {showCompleted} = args
const completedFilter = (typeof showCompleted === 'boolean') ? { completed: showCompleted } : {}
const jobIDS = await context.db.query.jobs({ where: completedFilter }, `{ id }`)
//console.log(jobIDS);
let jobs = await context.db.query.jobs({
where: completedFilter
}, info)
return await getAllJobCostsToDateList(jobs)
}
I am applying the the fragmentReplacements as per below.
const fragmentReplacements = extractFragmentReplacements(resolvers)
console.log(fragmentReplacements)
const port = process.env.PORT || 3010
const graphQLServer = new GraphQLServer({
typeDefs: './src/schema.graphql',
resolvers,
resolverValidationOptions: {
requireResolversForResolveType: false
},
context: req => ({
...req,
db: new Prisma({
typeDefs: `src/generated/prisma.graphql`,
fragmentReplacements,
endpoint: PRISMA_ENDPOINT,
secret: PRISMA_KEY,
debug: false
})
})
})
If i console.log the fragmentReplacements object i get the following, so it does seem to be picking up the fragments.
[ { field: 'job', fragment: 'fragment costsToDate on Job { id }' },
{ field: 'jobs',
fragment: 'fragment costsToDate on Jobs { id }' } ]
So my expectation here is that if i make a query against jobs or job that asks for the costsToDate field that it will also fetch the id for the job/each job.
However if i make the following query.
query{
jobs{
description
costsToDate
}
}
But i see no id fetched, and nothing in the root parameter on the resolver function.
Apologies as i am probably barking up completely the wrong tree here, seems like a somewhat simple requirement, but i can't quite work it out. Sure i'm missing something fundamental.
Thanks!
Gareth
A fragment is used to always retrieve given fields on a given type.
It follows the following format:
fragment NameOfYourFragment on YourType { ... }
You currently can't apply a given fragment conditionally as it is always applied.
Moreover, you specified a fragment on Jobs, but the type name used by Prisma is Job (even if you have the job and jobs resolvers)
You probably only need the following resolver:
const job = {
fragment: `fragment JobId on Job { id }`,
resolve: jobsResolver
}

Include relationship when querying node using Prisma generated wrapper

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
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.
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.

Enumerating all fields from a GraphQL query

Given a GraphQL schema and resolvers for Apollo Server, and a GraphQL query, is there a way to create a collection of all requested fields (in an Object or a Map) in the resolver function?
For a simple query, it's easy to recreate this collection from the info argument of the resolver.
Given a schema:
type User {
id: Int!
username: String!
roles: [Role!]!
}
type Role {
id: Int!
name: String!
description: String
}
schema {
query: Query
}
type Query {
getUser(id: Int!): User!
}
and a resolver:
Query: {
getUser: (root, args, context, info) => {
console.log(infoParser(info))
return db.Users.findOne({ id: args.id })
}
}
with a simple recursive infoParser function like this:
function infoParser (info) {
const fields = {}
info.fieldNodes.forEach(node => {
parseSelectionSet(node.selectionSet.selections, fields)
})
return fields
}
function parseSelectionSet (selections, fields) {
selections.forEach(selection => {
const name = selection.name.value
fields[name] = selection.selectionSet
? parseSelectionSet(selection.selectionSet.selections, {})
: true
})
return fields
}
The following query results in this log:
{
getUser(id: 1) {
id
username
roles {
name
}
}
}
=> { id: true, username: true, roles: { name: true } }
Things get pretty ugly pretty soon, for example when you use fragments in the query:
fragment UserInfo on User {
id
username
roles {
name
}
}
{
getUser(id: 1) {
...UserInfo
username
roles {
description
}
}
}
GraphQL engine correctly ignores duplicates, (deeply) merges etc. queried fields on execution, but it is not reflected in the info argument. When you add unions and inline fragments it just gets hairier.
Is there a way to construct a collection of all fields requested in a query, taking in account advanced querying capabilities of GraphQL?
Info about the info argument can be found on the Apollo docs site and in the graphql-js Github repo.
I know it has been a while but in case anyone ends up here, there is an npm package called graphql-list-fields by Jake Pusareti that does this. It handles fragments and skip and include directives.
you can also check the code here.

Resources