Grouping graphql mutations - graphql

I'm trying to group my mutations into second level types. The schema is parsed correctly, but resolvers aren't firing in Apollo. Is this even possible? Here's the query I want:
mutation {
pets: {
echo (txt:"test")
}
}
Here's how I'm trying to do it
Schema:
type PetsMutations {
echo(txt: String): String
}
type Mutation {
"Mutations related to pets"
pets: PetsMutations
}
schema {
mutation: Mutation
}
Resolvers:
...
return {
Mutation: {
pets : {
echo(root, args, context) {
return args.txt;
}
},
}

Assuming you're using apollo-server or graphql-tools, you cannot nest resolvers in your resolver map like that. Each property in the resolver map should correspond to a type in your schema, and itself be a map of field names to resolver functions. Try something like this:
{
Mutation: {
// must return an object, if you return null the other resolvers won't fire
pets: () => ({}),
},
PetsMutations: {
echo: (obj, args, ctx) => args.txt,
},
}
Side note, your query isn't valid. Since the echo field is a scalar, you can't have a subselection of fields for it. You need to remove the empty brackets.

Related

Pass parent resolver arguments in child resolver

I have a nested GraphQL query with the following structure
query{
parentMetrics(cityNames: ["Cali"], orderingDateFrom: "2021-01-01"){
sales
orders
childMetrics(cityNames: ["Cali"], orderingDateFrom: "2021-01-01"){
aov
}
}
}
As you can see I am repeating the arguments because in the backend we run different queries to get parentMetrics and childMetrices but they require the same set of args which is redundant.
Can I do something like this instead?
query{
parentMetrics(cityNames: ["Cali"], orderingDateFrom: "2021-01-01"){
sales
orders
childMetrics{
aov
}
}
}
I am using github.com/graphql-go/graphql and currently this is what my code looks like
"parentMetrics": &graphql.Field{
Type: partnerGQL.ParentMetrics,
Args: graphql.FieldConfigArgument{
"cityNames": &graphql.ArgumentConfig{
Type:graphql.NewList(graphql.String),
}
"orderingDateFrom":&graphql.ArgumentConfig{
Type: graphql.String,
}
},
Resolve: partnerResolver.ResolveOrderItemMetrics,
}
The parentMetrics type has the nested Resolver for childMetrics
When you return a parent metric object, include the arguments of parentMetrics call in that object.
The resolver for childMetrics will get the parent object and will be able to read its arguments from there.
A quick example in Javascript:
const resolvers = {
parentMetrics: (parent, args) => {
const metric = getParentMetric(args);
return {
...metric,
args: args,
};
},
childMetrics: (parent) => {
return getChildMetric(parent.args);
}
}

Apollo Client: How to query rest endpoint with query string?

I'm using Apollo to call a rest endpoint that takes variables from query string:
/api/GetUserContainers?showActive=true&showSold=true
I'm having trouble figuring out how to pass variables to the query, so it can then call the correct url. From looking at apollo-link-rest docs and a few issues I think I'm supposed to use pathBuilder but this is not documented and I haven't been able to get it working.
So far I've defined my query like this:
getUserContainersQuery: gql`
query RESTgetUserContainers($showActive: Boolean, $showSold: Boolean, $pathBuilder: any) {
containerHistory #rest(type: "ContainerHistoryResponse", pathBuilder: $pathBuilder) {
active #type(name: "UserContainer") {
...ContainerFragment
}
sold #type(name: "UserContainer") {
...ContainerFragment
}
}
}
${ContainerFragment}
`
and calling it in my component like this, which does not work:
import queryString from 'query-string'
// ...
const { data } = useQuery(getUserContainersQuery, {
variables: {
showActive: true,
showSold: false,
pathBuilder: () => `/api/GetUserContainers?${queryString.stringify(params)}`,
},
fetchPolicy: 'cache-and-network',
})
The only way I got this to work was by passing the fully constructed path to the query from the component:
// query definition
getUserContainersQuery: gql`
query RESTgetUserContainers($pathString: String) {
containerHistory #rest(type: "ContainerHistoryResponse", path: $pathString) { // <-- pass path here, instead of pathBuilder
// same response as above
}
}
`
// component
const params = {
showActive: true,
showSold: false,
}
const { data } = useQuery(getUserContainersQuery, {
variables: {
pathString: `/api/GetUserContainers?${queryString.stringify(params)}`,
},
fetchPolicy: 'cache-and-network',
})
These seems to me like a really hacky solution which I'd like to avoid.
What is the recommended way to handle this query string problem?
You shouldn't need to use the pathBuilder for simple query string params. You can pass your params directly as variables to useQuery then pass then directly into teh path using the {args.somearg} syntax. The issue I see is you've not defined the variables your using for you query containerHistory bu only in the query alias RESTgetUserQueries. If updated is should look like this:
// query definition
getUserContainersQuery: gql`
query RESTgetUserContainers($showActive: Boolean, $showSold: Boolean) {
// pass the variables to the query
containerHistory(showActive:$showActive, showSold:$showSold) #rest(type: "ContainerHistoryResponse", path:"/api/GetUserContainers?showActive={args.showActive}&showSold={args.showTrue}") {
//.. some expected reponse
}
}
`
// component
const params = {
showActive: true,
showSold: false,
}
const { data } = useQuery(getUserContainersQuery, {
variables: {
showActive,
showSold
},
fetchPolicy: 'cache-and-network',
})

GraphQL resolvers only resolving the first type

so im working on a project of mine and i seem to hit a dead end with my abilities.
I am working on a GraphQL backend that is supposed to fetch some data from a MySQL database. I already got the resolvers working so i can fetch all users etc. but i am not able to fetch nested types. For example:
query {
ways {
id
distance
duration
purpose
User {
id
dob
}
}
}
This only returns all the ways from my database but the User returns null
schema.ts
export const typeDefs= gql`
schema {
query: Query
}
type Query {
ways: [Way]
users: [User]
getUser(id: String!): User
getWay(id: String!):Way
}
type Way{
id:String
distance:Float
duration:Float
stages:[Stage]
purpose:String
User:User
}
type User{
id:String
firstname:String
lastname:String
sex:String
dob:String
annualTicket:Boolean
Ways:[Way]
}
resolver.ts
export const resolvers = {
Query: {
ways: async(parent, args, context, info) => {
console.log("ways")
const answer=await getAllWays();
return answer;
},
users: async(parent, args, context, info) => {
console.log("users")
const answer=await getAllUser();
return answer;
},
getUser: async(parent, args, context, info) =>{
console.log("getUser")
const answer=await getUserPerID(args.id);
return answer;
},
getWay:async(parent, args, context, info) =>{
console.log("getWay")
const answer=await getWayPerID(args.id);
return answer;
}
},
User:async(parent, args, context, info) =>{
console.log("User: id="+args.id)
ways: User => getWaysPerUserID(args.id)
},
Way:async(parent, args, context, info) => {
console.log("Way: id="+parent.userID)
user: Ways => getUserPerID(parent.userID)
}
I would expect the outcome to include the user and its data too, using the above mentioned query.
Any advice is much appreciated.
Only fields, not types, can have resolvers. Each key in your resolvers object should map to another object, not a function. So instead of
Way:async(parent, args, context, info) => {
console.log("Way: id="+parent.userID)
user: Ways => getUserPerID(parent.userID)
}
You need to write:
Way: {
// We capitalize the field name User because that's what it is in your schema
User: (parent) => getUserPerID(parent.userID)
}

Apollo Graphql Resolver for a Nested Object

So I have a type like this:
type CardStatus {
status: String
lastUpdated: String
}
type CardCompany {
cardStatus: CardStatus
}
type ExternalAccounting {
cardCompany: CardCompany
}
type User {
balance: String
externalAccounting: ExternalAccounting
}
And my resolver looks something like this
const User = {
balance: (root, args, context) => getBalance().then((res)=>res)
cardStatus: (??)
}
I want to use a resolver to set the nested cardStatus field in the user object.
Balance is the direct field of an object, it's easy- I just run a resolver and return the result to get balance. I want to run a cardStatus api call for the deeply nested cardStatus field, but I have no idea how to do this. I've tried something in my resolver like this:
const User = {
balance: {...}
externalAccounting: {
cardCompany: {
cardStatus: (root) => { (...) },
},
},
}
But it doesn't work in that it does not set the cardStatus nested field of the user object. Seems like it should be relatively easy but I can't find an example online..
You should define the cardStatus resolver under the type CardCompany.
When defining types, they should all be on the same level in the resolver map. Apollo will take care of resolving the query's nested fields according to the type of each nested field.
So your code should look something like this:
const User = {
balance: (root, args, context) => getBalance().then((res)=>res)
}
const CardCompany = {
cardStatus: (root) => { (...) },
}
And I'm not sure how it is connected to your executable schema but it should probably be something similar to:
const schema = makeExecutableSchema({
typeDefs,
resolvers: merge(User, CardCompany, /* ... more resolvers */ )
})

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