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)?
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:
Makes a single API call at the parent level
Stores the complete response in the parent resolver
Uses child resolvers to access specific parts of the stored response
Maintains a flat response structure in the GraphQL output
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:
Using a parent resolver to fetch and store the complete response
Using child resolvers to access specific parts of the stored data
Keeping implementation details (like _s3Response) internal to the resolver
Maintaining clean separation between the API response structure and GraphQL schema