Appsync & GraphQL: how to filter a list by nested value - graphql

I have an Appsync API generated by Amplify from a basic schema. On the Article model, a category field is nested within a metadata field. I want to build a Query that provides a list of Articles filtered by category. It is not clear to me how to filter on a nested value... I have seen similar questions but the analogous answer has not worked.
AWS GraphQL Transform Schema
type Article #model {
id: ID!
title: String!
description: String!
text: String!
metadata: ArticleMetadata!
}
type ArticleMetadata {
category: Category!
lastModified: String!
creationDate: String!
}
enum Category {
javascript
java
ruby
python
haskell
}
Generated List Query
export const listArticles = `query ListArticles(
$filter: ModelArticleFilterInput
$limit: Int
$nextToken: String
) {
listArticles(filter: $filter, limit: $limit, nextToken: $nextToken) {
items {
id
title
description
text
metadata {
category
lastModified
creationDate
}
}
nextToken
}
}
`;
Failing filter query
query listArticlesByCategory($category: String!) {
listArticles(filter: {category: {eq: $category}}) {
items {
title
description
text
metadata {
category
creationDate
lastModified
}
}
}
}
The Appsync console error states that the category in filter: {category: ... } is an unknown field.

By default the Amplify codegen only will operate on top-level filters. You can extend this to include filters for the attributes nested in ArticleMetadata.
You will need to augment the ModelArticleFilterInput type to include the category field. Assuming that the metadata field in the article table is backed by a DynamoDB map, you can filter based on the map value. You will need to modify the listArticles resolver's Request Mapping Template VTL to add the filter expression that contains something like metadata.category = :category when there is a value for cateogry in the filter argument.

Related

GraphQL query with multiple nested resolvers and mapping fields to arguments

From GraphQL Client's perspective, how do I perform a query with multiple nested resolvers where the fields from the parent are passed as arguments to the child resolver?
Here is a minimal example:
GraphQL Schema:
type Author {
id: ID!
name: String!
}
type Book {
id: ID!
title: String!
releaseDate: String!
}
type Query {
// Returns a list of Authors ordered by name, 'first' indicates how many entries to return
getAllAuthors(first: Int!): [Author]!
// Returns a list of Books ordered by releaseDate, 'first' indicates how many entries to return
getBooksByAuthorId(first: Int! authorId: ID!): [Book]!
}
Is it possible to write a query to get all authors and their last released book? Something around the lines:
query GetAuthorsWithLastBook($first: Int!) {
getAllAuthors(first: $first) {
authorId: id
name
lastBook: getBooksByAuthor(1, authorId) {
title
}
}
}
In the example above, I attempted to alias getAllAuthors.id as authorId and pass the alias down as argument to getBooksByAuthor(...) but that didn't work.
The key aspect of the problem is that I don't know the authorIds beforehand. I could fetch the authors first and build a query to fetch their last book but that will result in multiple queries and that is something I would like to avoid.
Update
A Java Kickstarter example is available here: https://www.graphql-java-kickstart.com/tools/schema-definition/
yes, on the graphql definition, you need to add lastBook in the Author
type Author {
id: ID!
name: String!
lastBook: [Book]
}
Next up u need to write the resolver for the lastBook
const resolvers = {
Query: {
Author {
lastBook: (parent, args) {
const userId = parent.id;
return getBooksByAuthor(userId, 1);
},
}
}
};

How to create a document in FaunaDB with a given _id using GraphQL instead of accepting the auto-generated one

It is possible to do it using FQL, but I can't find any example of a GraphQL mutation that creates a new document in FaunaDB where the _id is defined by the application and not by FaunaDB itself.
Of course I can add my application-generated ID (an uuidv4 for instance) as an additional attribute in the document and create an index for it, but it doesn't seem right considering the document will have another primary key anyway.
You can use resolver directive, associate with a user defined function (UDF) to accept _id as a parameter and then use Create.
Here is an example,
Schema file
type Order {
customerName: String!
price: Float!
}
input OrderInput {
orderID: Int!
customerName: String!
price: Float!
}
type Query {
CreateOrder(data: OrderInput!): Order! #resolver
}
UDF (CreateOrder)
Update(
Function("CreateOrder"),
{
body: Query(Lambda(["orderdata"],
Create(Ref(Collection("Order"), Select(["orderID"],Var("orderdata"))),
{
data: {
customerName: Select(["customerName"], Var("orderdata")),
price: Select(["price"], Var("orderdata"))
}
}
)))})
GraphQL Query

Query Appsync graphql with 3 different combinations

I am using appsync with amplify and trying to figure out how to query based on two different selectors. Basically I need to either query all if neither county or facility are supplied, query with county while facility is empty, or query with facility while county is empty. I thought I could wrap this into 1 query but it doesn't seem like I can. My appsync schemas look like this.
type Client
#model
#key(name: "clientByCountyOrFacility", fields: ["county", "facility"], queryField: "getClientsByCountyOrFacility")
#searchable {
id: ID!
facility: String!
county: String!
products: [Product] #connection(name: "ClientProducts")
}
type Product
#model
#searchable {
id: ID!
client: Client #connection(name: "ClientProducts")
}
I can get this to work by using (below query) but I am worried this will run into the 100 scan limit because it uses the listClients query underneath. Possibly if there was an easy way to change that could be a solution but it seems the files in amplify are autogenerated.
query getClientsByCountyOrFacility($county: String = "", $facility: String = "") {
listClients(filter: {
county: {
contains: $county
}
facility: {
contains: $facility
}
}) {
items {
id
products {
items {
id
}
}
}
}
}
I added the #key to see if I could create an index but it doesn't like that and I'm at a lose for how to acquire the data. How do I go about building this schema and query to get the data back?

Sorting results in AWS Amplify GraphQL without filtering

Provided a very simple model in graphql.schema, how would I perform a simple sort query?
type Todo #model
id: ID!
text: String!
}
Which generates the following in queries.js.
export const listTodos = /* GraphQL */ `
query ListTodos(
$filter: ModelTodoFilterInput
$limit: Int
$nextToken: String
) {
listTodos(filter: $filter, limit: $limit, nextToken: $nextToken) {
items {
id
text
}
nextToken
}
}
`;
I have found multiple sources pointing me in the direction of the #key directive. This similar question addresses that approach (GraphQL with AWS Amplify - how to enable sorting on query).
While that may seem promising and successfully generates new queries I can use, all the approaches I have tried require that I filter the data before sorting it. All I want to do is sort my todo results on a given column name, with a given sort direction (ASC/DESC).
This is how I would perform a simple (unsorted) query:
const todos = await API.graphql(graphqlOperation(listTodos));
I would be looking to do something along the lines of:
const todos = await API.graphql(graphqlOperation(listTodos, {sortField: "text", sortDirection: "ASC"} )).
Decorate your model with the #searchable directive, like so:
type Todo #model #searchable
{
id: ID!
text: String!
}
After that, you can query your data with sorting capabilities like below:
import { searchToDos } from '../graphql/queries';
import { API, graphqlOperation } from 'aws-amplify';
const toDoData = await API.graphql(graphqlOperation(searchToDos, {
sort: {
direction: 'asc',
field: 'text'
}
}));
console.log(toDoData.data.searchToDos.items);
For more information, see
https://github.com/aws-amplify/amplify-cli/issues/1851#issuecomment-545245633
https://docs.amplify.aws/cli/graphql-transformer/directives#searchable
Declaring #searchable incurs pointless extra server cost if all you need is straight forward sorting. It spins up an EBS and an OpenSearch that will be about $20 a month minumum.
Instead you need to use the #index directive.
As per the documentation here: https://docs.amplify.aws/guides/api-graphql/query-with-sorting/q/platform/js/
In your model, add the #index directive to one of the fields with a few parameters:
type Todo #model {
id: ID!
title: String!
type: String! #index(name: "todosByDate", queryField: "todosByDate", sortKeyFields: ["createdAt"])
createdAt: String!
}
By declaring the queryField and the sortKeyField you will now have a new query available to once you push your amplify config:
query todosByDate {
todosByDate(
type: "Todo"
sortDirection: ASC
) {
items {
id
title
createdAt
}
}
}
The field you declare this directive on can not be empty (notice the ! after the field name)
This is a much better way of doing it as opposed to #searchable, which is massively overkill.
I've accepted MTran's answer because it feels to me it is the nearest thing to an actual solution, but I've also decided to actually opt for a workaround. This way, I avoid adding a dependency to ElasticSearch.
I ended up adding a field to my schema and every single entry has the same value for that field. That way, I can filter by that value and still have the entire table of values, that I can then sort against.

How do I retrieve all items in an ArangoDB collection using GraphQL

I am using the ArangoDB FOXX GraphQL Example to retrieve data from a couple of collections (each one named the same as the type). Everything is fine when I want to retrieve one item using the _key property, but when I try to retrieve all items in a collection (see diagnosticItems) I receive an error. My schema definition is:
export const typeDefs = [`
enum Status {
up
down
}
type DiagnosticItem {
_key: String!
title: String!
status: Status
locationKey: String
lastUpdatedDate: String
location: Location #aql(exec: "FOR location in Location filter location._key == #current.locationKey return location")
}
type Location {
_key: String!
title: String!
}
type Query {
diagnosticItem(_key: String!): DiagnosticItem
diagnosticItems: [DiagnosticItem]
location(_key: String!): Location
}
`];
The full StackBlitz is here.

Resources