Pass parent resolver arguments in child resolver - go

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);
}
}

Related

Query an array with unstructured objects on GraphQL

I'm trying to use GraphQL to query an unstructured array with objects in Gridsome. It is currently looking very messy and it feels like there should be a better way to do this.
The data that gets loaded into GraphQL from the CMS looks like this:
{
title: "Homepage",
top_image: "imgurl.jpg",
page_builder: [
{
type: "slider",
field: "data example",
different_field: "data example"
},
{
type: "call_to_action",
field_for_cta: "data example",
different_cta_field: "data example"
}
]
}
As you can see the objects in page_builder will have different fields depening on how the client is building this section.
When I try to query this in GraphQL. It will become very messy:
<page-query>
query {
data: pages(path: "/pages") {
title,
top_image,
page_builder {
type,
field,
different_field,
type,
field_for_cta,
different_cta_field
#this list will have way more fields depending on all the page builder elements
}
}
}
</page-query>
Is there a way to organize this fields by type and only return the fields of this specific type?
Assuming gridsome supports fragments, you can do something like this:
<page-query>
query {
data: pages(path: "/pages") {
title,
top_image,
page_builder {
...A #include(if: $includeA)
...B #include(if: $includeB)
...C #include(if: $includeC)
}
}
}
# Note: Replace PageBuilderType with appropriate type
fragment A on PageBuilderType {
# your fields here
}
fragment B on PageBuilderType {
# your fields here
}
fragment C on PageBuilderType {
# your fields here
}
</page-query>
You can then define the variables when calling createPage as shown here:
api.createPages(({ createPage }) => {
createPage({
path: '/my-page',
component: './src/templates/MyPage.vue',
queryVariables: {
includeA: someCondition,
includeB: someCondition,
includeC: someCondition,
},
})
})
}

GraphQL nested query returns null

I am trying to use a GraphQL nested query (I am 80% sure this is a nested query?) to get information on the listing and the chef (author) of the listing. I can get the listing info just fine, but I am unable to get the chef info.
I was under the impression that the default resolver (user) would fire when getListing(args) returned without a valid User object for the chef. But the default resolver does not appear to be firing.
How do I properly get the nested information?
For example, my query is:
query getListing($listingID: String!) {
getListing(listingID: $listingID) {
name
chef {
firstName
}
}
}
The query returns:
{
"data": {
"getListing": {
"name": "Test",
"chef": {
"firstName": null
}
}
}
}
The function getListing(args) queries the DB and returns:
{
name: 'Test',
chef: 'testUsername',
listingID: 'testListingID'
}
My Schema is:
type Listing {
uuid: String!
name: String!
chef: User!
}
type User {
username: String
firstName: String
}
type Query {
getUser(jwt: String!): User
getListing(listingID: String): Listing
}
And my resolvers are:
const resolvers = {
Query: {
getListing: async (parent, args, context, info) => {
console.log('GET_LISTING');
return getListing(args);
},
getUser: async (parent, args, context, info) => {
console.log('GET_USER');
return getUser(args);
},
},
User: async (parent, args) => {
console.log('USER RESOLVER');
return getUser(args);
},
};
Other Info:
I am using Apollo Server running on AWS Lambda integrating with DynamoDB on the backend.
Resolvers exist only at the field level. You can't resolve a type (i.e. User). You can only resolve a field that has that type (i.e. chef).
const resolvers = {
// ...
Listing: {
chef: (parent, args) => {
return getUser()
},
},
}
It's unclear what sort of parameters getUser accepts, so you'll need to modify the above example accordingly. You won't use args unless you actually specify arguments for the field being resolved in your schema. It looks like the returning listing has a chef property that's the name of the user, so you can access that value with parent.chef.

How get rid of redundant wrapper object of a mutation result?

When I'm making a request to my backend through a mutation like that:
mutation{
resetPasswordByToken(token:"my-token"){
id
}
}
I'm getting a response in such format:
{
"data": {
"resetPasswordByToken": {
"id": 3
}
}
}
And that wrapper object named the same as the mutation seems somewhat awkward (and at least redundant) to me. Is there a way to get rid of that wrapper to make the returning result a bit cleaner?
This is how I define the mutation now:
export const ResetPasswordByTokenMutation = {
type: UserType,
description: 'Sets a new password and sends an informing email with the password generated',
args: {
token: { type: new GraphQLNonNull(GraphQLString) },
captcha: { type: GraphQLString },
},
resolve: async (root, args, request) => {
const ip = getRequestIp(request);
const user = await Auth.resetPasswordByToken(ip, args);
return user.toJSON();
}
};
In a word: No.
resetPasswordByToken is not a "wrapper object", but simply a field you've defined in your schema that resolves to an object (in this case, a UserType). While it's common to request just one field on your mutation type at a time, it's possible to request any number of fields:
mutation {
resetPasswordByToken(token:"my-token"){
id
}
someOtherMutation {
# some fields here
}
andYetAnotherMutation {
# some other fields here
}
}
If we were to flatten the structure of the response like you suggest, we would not be able to distinguish between the data returned by one mutation from another. We likewise need to nest all of this inside data to keep our actual data separate from any returned errors (which appear in a separate errors entry).

Grouping graphql mutations

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.

How to define and execute GraphQL query with filter

So the thing i try to do is, retrieve filtered data from the Database (MongoDB in my situation) using GraphQL.
Speaking in "MySQL language" how to implement the where clause in GraphQL?
I followed this tutorial :
https://learngraphql.com/basics/using-a-real-data-source/5
Query with filter was defined like this :
const Query = new GraphQLObjectType({
name: "Queries",
fields: {
authors: {
type: new GraphQLList(Author),
resolve: function(rootValue, args, info) {
let fields = {};
let fieldASTs = info.fieldASTs;
fieldASTs[0].selectionSet.selections.map(function(selection) {
fields[selection.name.value] = 1;
});
return db.authors.find({}, fields).toArray();
}
}
}
});
The tricky part here is the info parameter in the resolve function. Some explanations i've found here : http://pcarion.com/2015/09/26/graphql-resolve/
So it is AST (Abstract Syntax Tree)
Can anyone please provide some basic real-life example code that would show how to define and execute the following query :
Get all authors where name == John
Thank you!
There is no need to examine the AST. That would be awfully laborious.
All you need to do is define an argument on the author field. This is the second paramenter of the resolver, so you can check for that arguement and include it in the Mongo query.
const Query = new GraphQLObjectType({
name: "Queries",
fields: {
authors: {
type: new GraphQLList(Author),
// define the arguments and their types here
args: {
name: { type: GraphQLString }
},
resolve: function(rootValue, args, info) {
let fields = {};
// and now you can check what the arguments' values are
if (args.name) {
fields.name = args.name
}
// and use it when constructing the query
return db.authors.find(fields, fields).toArray();
}
}
}
});

Resources