Reputation: 3443
I am not using AWS AppSync for this app. I have created Graphql schema, I have made my own resolvers. For each create, query, I have made each Lambda functions. I used DynamoDB Single table concept and it's Global secondary indexes.
It was ok for me, to create an Book item. In DynamoDB, the table looks like this: .
I am having issue with the return Graphql queries. After getting the Items
from DynamoDB table, I have to use Map function then return the Items
based on Graphql type
. I feel like this is not efficient way to do that. Idk the best way query data. Also I am getting null both author and authors query.
This is my gitlab-branch.
This is my Graphql Schema
import { gql } from 'apollo-server-lambda';
const typeDefs = gql`
enum Genre {
adventure
drama
scifi
}
enum Authors {
AUTHOR
}
# Root Query - all the queries supported by the schema
type Query {
"""
All Authors query
"""
authors(author: Authors): [Author]
books(book: String): [Book]
}
# Root Mutation - all the mutations supported by the schema
type Mutation {
createBook(input: CreateBook!): Book
}
"""
One Author can have many books
"""
type Author {
id: ID!
authorName: String
book: [Book]!
}
"""
Book Schema
"""
type Book {
id: ID!
name: String
price: String
publishingYear: String
publisher: String
author: [Author]
description: String
page: Int
genre: [Genre]
}
input CreateBook {
name: String
price: String
publishingYear: String
publisher: String
author: [CreateAuthor]
description: String
page: Int
genre: [Genre]
}
input CreateAuthor {
authorName: String!
}
`;
export default typeDefs;
This is I created the Book Item
import AWS from 'aws-sdk';
import { v4 } from 'uuid';
import { CreateBook } from '../../generated/schema';
async function createBook(_: unknown, { input }: { input: CreateBook }) {
const dynamoDb = new AWS.DynamoDB.DocumentClient();
const id = v4();
const authorsName =
input.author &&
input.author.map(function (item) {
return item['authorName'];
});
const params = {
TableName: process.env.ITEM_TABLE ? process.env.ITEM_TABLE : '',
Item: {
PK: `AUTHOR`,
SK: `AUTHORS#${id}`,
GSI1PK: `BOOKS`,
GSI1SK: `BOOK#${input.name}`,
name: input.name,
author: authorsName,
price: input.price,
publishingYear: input.publishingYear,
publisher: input.publisher,
page: input.page,
description: input.description,
genre: input.genre,
},
};
await dynamoDb.put(params).promise();
return {
...input,
id,
};
}
export default createBook;
This is how query the All Book
import AWS from 'aws-sdk';
async function books(_: unknown, input: { book: string }) {
const dynamoDb = new AWS.DynamoDB.DocumentClient();
const params = {
TableName: process.env.ITEM_TABLE ? process.env.ITEM_TABLE : '',
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :hkey',
ExpressionAttributeValues: {
':hkey': `${input.book}`,
},
};
const { Items } = await dynamoDb.query(params).promise();
const allBooks = // NEED TO MAP THE FUNcTION THEN RETURN THE DATA BASED ON GRAPHQL //QUERIES.
Items &&
Items.map((i) => {
const genre = i.genre.filter((i) => i);
return {
name: i.name,
author: i.author,
genre,
};
});
return allBooks;
}
export default books;
This my Author query and Image of the console result
import AWS from 'aws-sdk';
import { Author, Authors } from '../../generated/schema';
async function authors(
_: unknown,
input: { author: Authors }
): Promise<Author> {
const dynamoDb = new AWS.DynamoDB.DocumentClient();
const params = {
TableName: process.env.ITEM_TABLE ? process.env.ITEM_TABLE : '',
KeyConditionExpression: 'PK = :hkey',
ExpressionAttributeValues: {
':hkey': `${input.author}`,
},
};
const { Items } = await dynamoDb.query(params).promise();
console.log({ Items }); // I can see the data but don't know how to returns the data like this below type without using map function
// type Author {
// id: ID!
// authorName: String
// book: [Book]!
// }
return Items; // return null in Graphql play ground.
}
export default authors;
Edit: current resolver map
// resolver map - src/resolvers/index.ts
const resolvers = {
Query: {
books,
authors,
author,
book,
},
Mutation: {
createBook,
},
};
Upvotes: 7
Views: 622
Reputation: 25769
TL;DR You are missing some resolvers. Your query resolvers are trying to do the job of the missing resolvers. Your resolvers must return data in the right shape.
In other words, your problems are with configuring Apollo Server's resolvers. Nothing Lambda-specific, as far as I can tell.
GraphQL doesn't know how to "resolve" an author's books, for instance. Add a Author {books(parent)}
entry to Apollo Server's resolver map. The corresponding resolver function should return a list of book objects (i.e. [Books]
), as your schema requires. Apollo's docs have a similar example you can adapt.
Here's a refactored author
query, commented with the resolvers that will be called:
query author(id: '1') { # Query { author } resolver
authorName
books { # Author { books(parent) } resolver
name
authors { # Book { author(parent) } resolver
id
}
}
}
Apollo Server uses the resolver map during query execution to decide what resolvers to call for a given query field. It's not a coincidence that the map looks like your schema. Resolver functions are called with parent, arg, context and info arguments, which give your functions the context to fetch the right records from the data source.
// resolver map - passed to the Apollo Server constructor
const resolvers = {
Query: {
books,
authors,
author,
book,
},
Author: {
books(parent) { getAuthorBooks(parent); }, // parent is the author - resolver should return a list of books
},
Book: {
authors(parent) { getBookAuthors(parent); }, // parent is the book - resolver should return a list of authors
},
};
It's not the author query resolver's job to resolve all the child fields. Apollo Server will call multiple resolvers multiple times during query execution:
You can think of each field in a GraphQL query as a function or method of the previous type which returns the next type. In fact, this is exactly how GraphQL works. Each field on each type is backed by a function called the resolver which is provided by the GraphQL server developer. When a field is executed, the corresponding resolver is called to produce the next value
Apollo Server calls this the resolver chain. The books(parent)
resolver will be invoked with Author
as its parent
argument. You can use the author id to look up her books.
Make sure your resolvers are returning data in the shape required by the schema. Your author
resolver is apparently returning a map {Items: [author-record]}
, but your schema says it needs to be a list.
(If I were you, I would change the author query signature from author(PK: String, SK: String): [Author]
to something more caller-friendly like author(id: ID): Author
. Return an Object, not a List. Hide the DynamoDB implementation details in the resolver function. Apollo Server has a ID
scalar type that is serialised as a String.)
Upvotes: 2