amplify-swift icon indicating copy to clipboard operation
amplify-swift copied to clipboard

[Feedback]Make a GraphQL request for a nested query

Open mrtom opened this issue 4 years ago • 4 comments

Page: /lib/q/platform/ios

Feedback:

Hi there!

I'm trying to update a fairly straightforward app from the old Amplify SDK to the new Amplify Library. The docs don't have any migration guide sadly.

One thing that isn't at all clear is how you go about using custom queries with the API exposed by the library. The docs show how to fetch a single or list of a model, but not how to make a GraphQL request for, say, a nested query.

Please could you advise?

Many thanks!

mrtom avatar Jun 07 '20 21:06 mrtom

Thanks for clarifying the request @swaminator.

To add another (very related) query: By using custom queries I'm wanting to fetch nested objects with a single HTTP request but also wanting to avoid over-fetching of data by allowing engineers on the client side to specify which subset of fields they require.

Thanks!

mrtom avatar Jun 16 '20 20:06 mrtom

Hi @mrtom, we have some ideas to provide a migration strategy for customers using AppSync SDK to Amplify API plugin. It may involve exensions that translate the code generated classes in AppSync over to the Amplify's GraphQLRequest so I'm thinking that we can better track this if I transfer the issue over to the Amplify iOS repo.

lawmicha avatar Jul 22 '20 16:07 lawmicha

To add another (very related) query: By using custom queries I'm wanting to fetch nested objects with a single HTTP request but also wanting to avoid over-fetching of data by allowing engineers on the client side to specify which subset of fields they require.

I believe a custom query where you pass a custom selection set is the first step to getting a subset of fields required on the client side. Are you constructing a GraphQLRequest with the graphql document containing customized selection set?

Then what is the responseType used when deserializing the response data? Are you using the Model classes generated from amplify codegen models or are you using an hand crafted Codable types that contain a subset of the fields?

lawmicha avatar Jul 22 '20 16:07 lawmicha

I had imagined that a code gen step could/would be run that would generate models for a specific query, but in a strongly typed language like Swift it can be difficult to do well.

There are three approaches that I feel you could consider. Let's imagine you have a GraphQL schema like so, with a user and a post, and a many-to-many connection between users and posts.

type User
  @model
{
  id: ID!
  username: String!
  sub: String!
  posts: [UserPost] @connection(keyName: "byUser", fields: ["id"])
  createdAt: AWSDateTime!
}

type Post
  @model
{
  id: ID!
  associated: [UserPost] @connection(keyName: "byPost", fields: ["id"])
  title: String!
  body: String!
  createdAt: AWSDateTime!
}

type UserPost
  @model
  @key(name: "byUser", fields: ["userPostUserId", "userPostPostId"])
  @key(name: "byPost", fields: ["userPostPostId", "userPostUserId"])
{
  id: ID!
  user: User! @connection(fields: ["userPostUserId"])
  userPostUserId: ID!
  post: Post! @connection(fields: ["userPostPostId"])
  userPostPostId: ID!
  createdAt: AWSDateTime!
}

You may want to get a list of posts by created date or whatever and just render the the authors name next to each post. So you may create a GraphQL query like (haven't created this IRL, so may not be perfect):

query postsByCreatedAtQuery(input: { limit: 25} ) {
  posts {
    items {
       post {
         id
         title
         body
         createdAt
         associated {
           items {
             user {
               username
             }
           }
         }
       }
    }
  }
}

So here, you have a query that gets all the fields for the Post type, but only one field (the username) for the associated users.

I think you have three options:

  1. Codegen a type called postsByCreatedAtQuery_user, which is specific to the user fetched for this query and only gives the developer access to the username field on that type. This is essentially how the Amplify SDK worked I believe.
  2. Code gen a single User type but make all fields optional. This sucks for a bunch of reasons (not least that you're not respecting the optionality of types from the GraphQL schema). It is simpler for new developers to understand, but on a larger codebase can be tricky for people to figure out what data has been fetch and what has not.
  3. Create a new type, MUST_FETCH. And each code gen'd model now has fields that are a union type of some sort, i.e. username can be of type String | MUST_FETCH (a bit like a result type I suppose). You can now return your User model in this case, but all fields other than username are set to MUST_FETCH and if the code tries to read from them the code throws an error (in development at least), essentially indicating to the developer than they need to add the field to their GraphQL query.

You could also add some linting/static analysis to ensure that only fields which have been fetched are accessed. Relay does this for example, but requires adding fragments for each component. This doesn't work very well with UIKits programming model, but would work very well with SwiftUI.

mrtom avatar Jul 27 '20 11:07 mrtom

Hello,

We have an example to create custom GraphQL requests for nested data queries through the custom selection set feature. https://docs.amplify.aws/cli/migration/lazy-load-custom-selection-set/#custom-selection-sets

Let us know if this works for you.

thisisabhash avatar Jul 28 '23 18:07 thisisabhash

@thisisabhash - How would this look for TypeScript/JavaScript? I've been after this for so, so long!

armenr avatar Dec 23 '23 03:12 armenr

@armenr Please open an issue in the JS repo with your question: https://github.com/aws-amplify/amplify-js

thisisabhash avatar Dec 23 '23 03:12 thisisabhash

I'm good. I figured it out myself. Thanks for the quick instructions to open issues.

armenr avatar Dec 23 '23 05:12 armenr