reactjsgraphqlaws-amplifyaws-appsync

Create dynamic GraphQL queries to AWS AppSync via Amplify in react


I am in the process of writing an application in React using AWS Appsync as my backend and bootstrapping it with AWS Amplify.

The use case is to retrieve a list of cars from a DynamoDB table, based on the current authenticated users group id. The schema looks as follows:

type Query {
    carsByUserGroup(userGroup: String!, limit: Int, nextToken: String): CarConnection
        @aws_cognito_user_pools
}

type CarConnection {
    items: [Car]
    nextToken: String
}

type Car {
    timestamp: String
    regno: String!
    country: String!
    type: String
    attributes: CarAttributes
    links: [Link]
    basic: BasicInfo
    inspection: InspectionInfo
    status: StatusInfo
    technical: TechnicalInfo
    uploadedBy: String
    userGroup: String
}

Note: some of the fields in type "Car" relates to other types, which are defined in the schema, but I haven't added here, to save space.

After downloading the new schema and putting into my schema.graphql in my React App I run "amplify codegen" in order to generate the new query. This is what I get in "queries.ts":

query CarsByUserGroup($userGroup: String!, $limit: Int, $nextToken: String) {
  carsByUserGroup(userGroup: $userGroup, limit: $limit, nextToken: $nextToken) {
    items {
      timestamp
      regno
      country
      type
      uploadedBy
      userGroup
      __typename
    }
    nextToken
    __typename
  }
}

This is where two issues occur:

  1. It generates some of the attributes in "Car" in the "items" field but not all of them for some reason? Can someone explain why this is?

  2. When I want to use the query in my React App I do it via this function:

export const fetchCarsByGroup = async (userGroup: string, limit = 10) => {
    try {
        const data = await client.graphql({
            query: queries.carsByUserGroup,
            variables: {
                userGroup: userGroup,
                limit: limit,
            }
        })
        console.log('Fetched cars:', data);
        // Handle the fetched data
    } catch (error) {
        console.error('Error fetching cars:', error);
        // Handle error
    }
};

The return of "fetchCarsByGroup" is an array matching with "userGroup", with the fields listed in the "CarsByUserGroup" in query.ts. However, my understanding of GraphQL and why it is so powerful, is that, depending on the use case, I can define which specific fields to return on a specific use case. This doesn't seem to be the case here?

I would assume that in my function "fetchCarsByGroup" I could define the fields to return in "variables" field, just like I can in the AWS Management Console in AppSync under "queries".

One solution that I read is to make a "custom-queries.ts" file and add the query for the specific use case.

If I make that and define the specific fields to return, then the next time I run "amplify codegen" after having edited something in my schema, the custom query in "custom-queries.ts" will not be synchronized to my Amplify setup. So in case I remove a field from "Car" I would have to do that manually in all the custom queries that fetches these fields.

Seeing that the app could scale up in functionality, this is a red flag for me.

So my question to you is, what have I misunderstood and is my assumption wrong in terms of defining which specific fields to return in the "fetchCarsByGroup" and have "CarsByUserGroup" as a global query that I can cherry pick from?


Solution

  • Don't trust codegen to generate queries for you. It doesn't know what you really want on the client. You should consider such queries as suggestive only.

    You can modify the query:

    query CarsByUserGroup($userGroup: String!, $limit: Int, $nextToken: String) {
      carsByUserGroup(userGroup: $userGroup, limit: $limit, nextToken: $nextToken) {
        items {
          timestamp
          regno
          country
          type
          uploadedBy
          userGroup
        }
        nextToken
      }
    }
    

    to include or exclude fields depending on client needs. For example:

    query CarsByUserGroup($userGroup: String!, $limit: Int, $nextToken: String) {
      carsByUserGroup(userGroup: $userGroup, limit: $limit, nextToken: $nextToken) {
        items {
          regno
          country
          type
          links {
            linkfield1
            linkfield2
            etc…
          }
          userGroup
        }
        nextToken
      }
    }
    

    will work just as well.

    GraphQL variables are the $-prefixed variables at the top. These can be passed in at runtime. Field selection is done in the query definition itself. You can build query definitions dynamically if you wish, they don't need to be static.

    On the server, queries (and mutations) are defined in terms of the variables they expect and the type they return, not the fields. Given that a query returns a single type or a list thereof [type] it's up to you to pick which fields from that type and possibly which fields from related types (given that fields in a type can point to other types).

    Note also that __typename is always included in the result even when you don't ask for it so don't feel like you need to ask for it.