GraphQL | How to implement conditional nesting? - graphql

Please consider the following GraphQL schema:
type User {
id: ID!
events: [Event]
}
type Event {
id: ID!
user: User!
asset: Asset!
}
type Asset {
id: ID
price: Number!
name: String!
}
GraphQL is a fantastic framework for fetching nested objects, but I'm struggling to understand how conditional nesting is implemented.
Example:
I want to retrieve all events for a specific user where asset.price is greater than x.
Or
I want to retrieve all events for an asset that belongs to a list of users [].
Question: Is conditional nesting a concept in GraphQL and how is it implemented?
Side note: I use AWS AppSync and resolvers are fetching data from AWS DynamoDB.

You can define a filter/condition on any GraphQL query such as:
query {
users(permission: 'ADMIN') {
...
}
}
The permission param is passed to your resolver (say DynamoDb VTL template, Lambda etc) to be handled however you want - to GQL this is just another parameter.
You can carry this concept into nested field by creating an events resolver and you'd then call it like this:
query {
user(id: '123') {
name
events(minPrice: 200) {
nodes: {
id
eventName
eventDate
}
}
dob
...
}
}
In above case I am using a simple minPrice param but you could do more complex things such price ranges, even pass operators (eq, gt, ...). It's all irrelevant to GraphQL - all gets passed to the resolver.
How you implement that on backend depends on your setup. I use AppSync without Amplify and write my own VTL templates and build the DynamoDb request using the provided GQL fields.
Here is an SO post that shows how to create a date filter.

Related

How to retrieve (double) nested properties in AppSync

I'm trying to figure out how I can retrieve properties which are deeply nested in AppSync. I currently have defined the dailyEnergyUsage to retrieve these levels in a lambda resolver.
Consider the following schema I'm currently using:
query SomeQuery {
me {
energy {
dailyEnergyUsage { // The resolver function is only attached to the following type
...
}
}
}
}
The me top level query/object is supposed to group authenticated queries altogether and the energy does the same but then for the related functions related to energy usage etc.
DailyEnergyUsageResolver:
Type: "AWS::AppSync::Resolver"
Properties:
ApiId: !GetAtt AppSyncApi.ApiId
FieldName: "dailyEnergyUsage"
TypeName: "Energy"
DataSourceName: !GetAtt "DailyEnergyUsageDataSource.Name"
Currently I only have the DailyEnergyUsageResolver to retrieve the energy usage which works fine if I don't have the grouping in between. But now I wanted to introduce this kind of grouping and was expecting that this would be automatically handled by AppSync but unfortunately doesn't work and returns null at the me field.
type Query {
me: AuthenticatedQuery
}
type AuthenticatedQuery {
energy: Energy
}
type Energy {
dailyEnergyUsage: [DataPoint]
}
Now my question is how can I get the grouping working within AppSync using direct Lambda resolvers or Lambda as data source?
I've tried to check the documentation of AWS and checked this Stackoverflow thread but I don't know if it is the solution and how to get it to working.

Querying all objects in Amplify mock api and getting null

I am using Amplify in a simple use case to mock an existing frontend. I have a cutdown schema.graphql as follows:
input AMPLIFY { globalAuthRule: AuthRule = { allow: public } }
schema {
query: Query
}
type Query {
getAirports: [Airport]
}
type Airport #model {
id: Int! #primaryKey
code: String!
city: String!
country: String!
}
The getAirports query is intended to return all the airports. I run amplify mock api and it generates all the resolvers.
When I navigate to http://localhost:20002, I can see the option to use getAirports, however it returns null even when data is present in the mocked database. The response is
{"data":null,"errors":[{"message":"Cannot return null for non-nullable field Query.getAirports.","locations":[{"line":2,"column":3}],"path":["getAirports"]}]}
I'm curious how I can write the schema to have a getAirports query in a way that it returns data a full list of Airports similar to listAirports which is created by default.

How to get list of objects using an array in AWS App Sync?

The intention is to query a list of users using an array of User IDs passed into a contains filter. The schema below is my attempt at solving this, but the query does not return a list of users. Even passing only a single User ID results in an empty query result. This schema is being published to AWS AppSync.
Is it possible to query a list of users using an array of User IDs in AppSync?
schema.graphql
type User #model {
id: ID!
username: String!
friends: [String]
}
type Query {
getAllFriends(filter: ModelAllFriendsFilterInput): ModelGetAllFriends
}
type ModelGetAllFriends {
items: [User]
}
input ModelAllFriendsFilterInput {
id: ModelAllFriendsIDInput
}
input ModelAllFriendsIDInput {
contains: [ID]
}
GraphQL Query:
query MyQuery {
getAllFriends(filter: {id: {contains: "VALID-USER-ID-HERE"}}) {
items {
id
username
}
}
}
Query result:
{
"data": {
"getAllFriends": null
}
}
Yes, lists are valid inputs in GraphQl fields.
The "null" response indicates that (a) Appsync passed your query to the resolver, (b) your resolver returned a result without error and (c) Appsync accepted the result. If any of these were not true, Appsync would have given you an error message. In other words, I believe the problem to be your resolver returning a null result, not the schema.
By the way, in the case of a list field like contains: [ID], Appsync will accept a list or a scalar value (like your {contains: "VALID-USER-ID-HERE"} above) as valid input. If you pass a scalar value to a list field, Appsync will helpfully pass it as a list/array value ["VALID-USER-ID-HERE"] in the lambda resolver's handler function arguments.

How to create GraphQL Child Resolvers with Lambda

I'm trying to create a mutation that calls a child resolver in addition to the parent resolver if an optional parameter is sent in.
I'm using AWS AppSync to sent my queries to Lambda. AppSync creates and sends an AppSyncEvent to my resolver file that looks something like this:
{
"info": {
"parentTypeName": "Mutation",
"selectionSetList": [
...
],
"selectionSetGraphQL": "...",
"fieldName": "updateUser",
"variables": {}
}
}
This event gets passed to my lambda function where, based on the fieldName and parentTypeName, I call my updateUser function.
I have the below schema
schema {
query: Query
mutation: Mutation
}
type Query {
getUser(id: ID!): User
}
type Mutation {
updateUser(name: String, email: String, bookRead: BookReadInput): User
}
type User {
name: String
email: String
booksRead: [Book]
}
type Book {
title: String
author: String
}
type BookReadInput {
title: String
author: String
}
I want that if the mutation gets passed bookRead then it will know to call a child resolver called addBook besides for the regular updateUser resolver.
I've seen various articles about implementing child resolvers but I can't figure out how they can work with lambda and the way my resolvers work.
The lambda could inspect the selectionSetList and decide what to do with the BookReadInput fields.
See https://aws.amazon.com/blogs/mobile/appsync-and-the-graphql-info-object/
You could also go with pipeline resolvers to first update the user, and then add the book.
I don't think there is a way to have it automated. You need to set it up, one way or the other.

can some one explain this code to me

Good day im newbie here and im tackling graphql and im having some problem on mutation can someone explain this block of code for me thank you
RootMutation: {
createAuthor: (root, args) => { return Author.create(args); },
createPost: (root, { authorId, tags, title, text }) => {
return Author.findOne({ where: { id: authorId } }).then( (author) => {
console.log('found', author);
return author.createPost( { tags: tags.join(','), title, text });
});
},
},
Sure, this is an example of two mutations in a GraphQL server. We can break it down to understand what is going on.
First let's look at the type system. A GraphQL schema normally has two root fields query and mutation (and sometimes subscription). These root fields are the root of your data hierarchy and expose the queries (GET requests) and mutations (POST, PUT, DELETE, etc requests) that you have access to.
By the looks of it you are implementing a schema with a root mutation type that looks like this:
type Mutation {
createAuthor: Author
createPost: Post
}
A type in GraphQL is made up of a set of fields each of which can have an associated resolver. Resolvers in GraphQL are like the event handlers you would attach to endpoints in REST.
The code that you have above is defining two resolvers that will handle the logic associated with the createAuthor and createPost mutations. I.E. the code in the createPost resolver is what will be run when I issue a query like this:
mutation CreatePost($post: CreatePostInput!) {
createPost(input: $post) {
id
title
tags
text
}
}
The GraphQL runtime parses the query and routes the operation to the correct resolver depending on the content of the query. In this example, it would see that I am calling the createPost mutation and would make sure to call the createPost resolver which in your case looks like this:
createPost: (root, { authorId, tags, title, text }) => {
return Author.findOne({ where: { id: authorId } }).then( (author) => {
console.log('found', author);
return author.createPost( { tags: tags.join(','), title, text });
});
},
To understand how a resolver works, let's look at the GraphQLFieldResovler type definition from graphql-js
export type GraphQLFieldResolver<TSource, TContext> = (
source: TSource,
args: { [argName: string]: any },
context: TContext,
info: GraphQLResolveInfo
) => mixed;
As you can see a GraphQLFieldResolver is a function that takes 4 arguments.
source: The source is the parent object of the current field. For example if you were defining a resolver for a field fullName on the User type, the source would be the full user object.
args: The args are any input arguments for that resolver. In my query above it would contain the value of the $post variable.
context: Context is a global context for a GraphQL execution. This is useful for passing information around that a resolver might need. For example, you include a database connection that you can use from your resolvers without importing it in every file.
info: The info object contains information about your GraphQL schema, the query, and other information such as the path to the current resolver being executed. This is useful in many ways. Here is one post talking about how you can use it to precompute queries: (https://scaphold.io/community/blog/querying-relational-data-with-graphql/)
This idea of having types and field resolvers is part of what makes GraphQL so powerful. Once you've defined you type system and the resolvers for their fields you can structure your schema however you want and GraphQL will always make sure to call the correct resolver no matter how deeply nested a query might be.
I hope this helps :)

Resources