amazon-web-servicesaws-appsyncaws-appsync-resolver

How to share data between sibling field resolvers in AWS AppSync


I have a GraphQL like following -

type File {
  name: String
  size: Int
}

type FileStorage {
  files(page_id: String): [File]
  continuation_token: String
}

Both files and continuation_token need to call the same backend API (e.g., /s3files) that returns a response like:

{
  "files": [
    { "name": "file1.txt", "size": 123 },
    { "name": "file2.txt", "size": 456 }
  ],
  "continuation_token": "abc123"
}

But in GraphQL, I don't want a nested shape like:

{
  "FileStorage": {
    "files": {
      "files": [...],
      "continuation_token": "abc123"
    }
  }
}

Instead, I want a flat response like this:

{
  "FileStorage": {
    "files": [...],
    "continuation_token": "abc123"
  }
}

Is there a way to call the backend API only once and share its result between files and continuation_token, while keeping the GraphQL response flat (not nested)?


Solution

  • Yes, you can achieve this using a resolver pattern in GraphQL where you fetch the data once at the parent level and then share it across child resolvers.

    // Type definitions
    const typeDefs = `
      type File {
        name: String
        size: Int
      }
    
      type FileStorage {
        files(page_id: String): [File]
        continuation_token: String
      }
    `;
    
    // Resolvers
    const resolvers = {
      FileStorage: {
        // Parent resolver that fetches the data
        __resolveReference: async (_, { page_id }) => {
          const response = await fetch(`/s3files?page_id=${page_id}`);
          const data = await response.json();
          // Store the full response as a context for child resolvers
          return {
            _s3Response: data // Internal field to store API response
          };
        },
    
        // Child resolver for files
        files: (parent) => {
          // Use the stored API response
          return parent._s3Response.files;
        },
    
        // Child resolver for continuation_token
        continuation_token: (parent) => {
          // Use the stored API response
          return parent._s3Response.continuation_token;
        }
      }
    };
    

    Another approch will be

    const resolvers = {
      Query: {
        fileStorage: async (_, { page_id }, context) => {
          const response = await fetch(`/s3files?page_id=${page_id}`);
          const data = await response.json();
          // Store the response in the resolver context
          return { _s3Response: data };
        }
      },
    
      FileStorage: {
        files: (parent) => parent._s3Response.files,
        continuation_token: (parent) => parent._s3Response.continuation_token
      }
    };
    

    This approach:

    1. Makes a single API call at the parent level

    2. Stores the complete response in the parent resolver

    3. Uses child resolvers to access specific parts of the stored response

    4. Maintains a flat response structure in the GraphQL output

    5. Is efficient as it avoids multiple API calls

    query {
      fileStorage(page_id: "some-id") {
        files {
          name
          size
        }
        continuation_token
      }
    }
    
    This will give you{
      "fileStorage": {
        "files": [
          { "name": "file1.txt", "size": 123 },
          { "name": "file2.txt", "size": 456 }
        ],
        "continuation_token": "abc123"
      }
    }
    

    The key points here are: