GraphQL Resolver for getting item by name, not ID - graphql

I have a DynamoDB with ingredients. AWS Appsync created a resolver for me so I could get an ingredient by ID, but I need to be able to get an ingredient by name. I've tried writing a resolver for this but it doesn't work.
Eventually I need to write a resolver or API that takes a list of strings and returns the ingredients that match those strings, if they exist, but this is the first step and I'm hoping if I can do this I can create a batch version of it.
The resolver:
{
"version": "2017-02-28",
"operation": "GetItem",
"key": {
"name": $util.dynamodb.toDynamoDBJson($util.transform.toDynamoDBFilterExpression($ctx.args.filter))
}
}
The response mapping template
$util.toJson($ctx.result)
The Schema:
input CreateIngredientInput {
name: String!
vegan: Vegan
gf: GlutenFree
}
input DeleteIngredientInput {
id: ID!
}
enum GlutenFree {
GLUTENFREE
CONTAINSGLUTEN
UNKNOWN
}
type Ingredient {
id: ID!
name: String!
vegan: Vegan
gf: GlutenFree
}
type IngredientConnection {
items: [Ingredient]
nextToken: String
}
input ModelBooleanFilterInput {
ne: Boolean
eq: Boolean
}
input ModelFloatFilterInput {
ne: Float
eq: Float
le: Float
lt: Float
ge: Float
gt: Float
contains: Float
notContains: Float
between: [Float]
}
input ModelIDFilterInput {
ne: ID
eq: ID
le: ID
lt: ID
ge: ID
gt: ID
contains: ID
notContains: ID
between: [ID]
beginsWith: ID
}
input ModelIntFilterInput {
ne: Int
eq: Int
le: Int
lt: Int
ge: Int
gt: Int
contains: Int
notContains: Int
between: [Int]
}
enum ModelSortDirection {
ASC
DESC
}
input ModelStringFilterInput {
ne: String
eq: String
le: String
lt: String
ge: String
gt: String
contains: String
notContains: String
between: [String]
beginsWith: String
}
type Mutation {
createIngredient(input: CreateIngredientInput!): Ingredient
updateIngredient(input: UpdateIngredientInput!): Ingredient
deleteIngredient(input: DeleteIngredientInput!): Ingredient
}
type Query {
getIngredient(id: ID!): Ingredient
getIngredientByName(name: String!): Ingredient
listIngredients(filter: TableIngredientFilterInput, limit: Int, nextToken: String): IngredientConnection
}
type Subscription {
onCreateIngredient(
id: ID,
name: String,
vegan: Vegan,
gf: GlutenFree
): Ingredient
#aws_subscribe(mutations: ["createIngredient"])
onUpdateIngredient(
id: ID,
name: String,
vegan: Vegan,
gf: GlutenFree
): Ingredient
#aws_subscribe(mutations: ["updateIngredient"])
onDeleteIngredient(
id: ID,
name: String,
vegan: Vegan,
gf: GlutenFree
): Ingredient
#aws_subscribe(mutations: ["deleteIngredient"])
}
input TableBooleanFilterInput {
ne: Boolean
eq: Boolean
}
input TableFloatFilterInput {
ne: Float
eq: Float
le: Float
lt: Float
ge: Float
gt: Float
contains: Float
notContains: Float
between: [Float]
}
input TableIDFilterInput {
ne: ID
eq: ID
le: ID
lt: ID
ge: ID
gt: ID
contains: ID
notContains: ID
between: [ID]
beginsWith: ID
}
input TableIngredientFilterInput {
id: TableIDFilterInput
name: TableStringFilterInput
vegan: TableBooleanFilterInput
gf: TableBooleanFilterInput
}
input TableIntFilterInput {
ne: Int
eq: Int
le: Int
lt: Int
ge: Int
gt: Int
contains: Int
notContains: Int
between: [Int]
}
input TableStringFilterInput {
ne: String
eq: String
le: String
lt: String
ge: String
gt: String
contains: String
notContains: String
between: [String]
beginsWith: String
}
input UpdateIngredientInput {
id: ID!
name: String
vegan: Vegan
gf: GlutenFree
}
enum Vegan {
VEGAN
NONVEGAN
UNKNOWN
}
When I run this query:
query getIt {
getIngredientByName(name: "demerara") {
id
name
vegan
gf
}
}
I get the response:
{
"data": {
"getIngredientByName": null
},
"errors": [
{
"path": [
"getIngredientByName"
],
"data": null,
"errorType": "DynamoDB:AmazonDynamoDBException",
"errorInfo": null,
"locations": [
{
"line": 2,
"column": 3,
"sourceName": null
}
],
"message": "The provided key element does not match the schema (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ValidationException; Request ID: 58EKL6IO63VL44Q1DTG9JFNJB7VV4KQNSO5AEMVJF66Q9ASUAAJG)"
}
]
}
Though demerara is definitely an ingredient in my database.

I figured out a way, albeit slightly a cheater way.
When I was creating the resource in the AppSync schema, there's a dropdown for "additional indexes" and if I make the initial index by ID, and then the second index "name" it'll create a query with a resolver for you. In my case the
queryIngredientsByNameIndex(name: String!, first: Int, after: String): IngredientConnection
query, with a resolver of
{
"version": "2017-02-28",
"operation": "Query",
"query": {
"expression": "#name = :name",
"expressionNames": {
"#name": "name",
},
"expressionValues": {
":name": $util.dynamodb.toDynamoDBJson($ctx.args.name),
},
},
"index": "name-index",
"limit": $util.defaultIfNull($ctx.args.first, 20),
"nextToken": $util.toJson($util.defaultIfNullOrEmpty($ctx.args.after, null)),
"scanIndexForward": true,
"select": "ALL_ATTRIBUTES",
}

I know this is old but I recently had a similar issue that I was able to solve by adding a #key directive to the schema property that I wanted to search by:
type User #model #key(name: "getUserByCognitoId", fields: ["cognitoId"], queryField: "getUserByCognitoId") {
id: ID!
cognitoId: ID!
...
}
Docs: https://docs.amplify.aws/cli/graphql-transformer/directives#key
This way AppSync generates the resolver for you and adds a sort key to make querying DynamoDB much more efficient

Related

graphQL Query: getting error "Expected value of type ..., found ..."

Suppose I have the following object types:
type Price {
currency: Currency!,
amount: Float!
}
type Attribute {
displayValue: String,
value: String,
id: String!
}
type AttributeSet {
id: String!,
name: String,
type: String,
items: [Attribute]
}
type Product {
id: String!,
name: String!,
inStock: Boolean,
gallery: [String],
description: String!,
category: String!,
attributes: [AttributeSet]
prices: [Price!]!,
brand: String!
}
type Category {
name: String,
products: [Product]!
}
type Currency {
label: String!,
symbol: String!
}
input CategoryInput {
title: String!
}
type Query {
categories: [Category],
category(input: CategoryInput): Category,
product(id: String!): Product,
currencies: [Currency]
}
And these are the Types for Category:
export enum Category {
all = 'all',
clothes = 'clothes',
tech = 'tech'
};
In graphQL Playground, I am trying to make a query to exhibit all the names and products/id of the elements with the category all. Here's my attempt:
{
category(input: "all") {
name
products {
id
}
}
}
But I'm getting the following error message:
"message": "Expected value of type \"CategoryInput\", found \"all\".",
I need help trying to understand what went wrong since all is a valid type. Thank you in advance.
Just found my mistake
CategoryInput is of type
input CategoryInput {
title: String!
}
So a proper query would be:
{
category(input: { title: "all" }) {
name
products {
id
}
}
}

Graphql React Dev Tools Missing field while writing result

I am running a graphql query to create a person object. The query works fine and the person is created. However I am receiving the following error in the console
react_devtools_backend.js:2842 Missing field 'create_person' while writing result {
"__typename": "PersonResponse",
"error": null,
"person": {
"__typename": "Person",
"hire_date": "2020-10-01",
"name": {
"__typename": "Name",
"first_name": "Joe",
"last_name": "Doe",
"middle_name": ""
},
"person_id": {
"__typename": "PersonId",
"id_": "44df8f7c-d019-410c-89b4-be602f631055"
},
"preferred_name": {
"__typename": "PreferredName",
"first_name": "J",
"last_name": "Dee",
"middle_name": null
}
},
"status": 201
}
This error seems to be saying that my query is missing a field create_person however create_person is not a field it is the name of the query. My first thought was that the message is due to the null fields (even though they are not required). I tried removing these fields from the schema and I still get the error. I am using React Dev Tools chrome extension but still not sure why I get this error.
As requested the gql schema:
const graphqlSchema = buildSchema(`
type Query {
people(person_id: String): [Person]!
emergency_contacts(
person_id: String!
emergency_contact_id: String
): [EmergencyContact]!
person_success: PersonResponse!
person_not_found: PersonResponse!
person_deleted: PersonResponse!
emergency_contact_success: EmergencyContactResponse!
}
scalar Datetime
type PersonId {
id_: String!
}
type Name {
first_name: String!
last_name: String!
middle_name: String
}
input NameInput {
first_name: String!
last_name: String!
middle_name: String
}
input UpdateNameInput {
first_name: String
last_name: String
middle_name: String
}
type ExemptStatus {
code: String!
description: String!
}
type PreferredName {
first_name: String
last_name: String
middle_name: String
}
input PreferredNameInput {
first_name: String
last_name: String
middle_name: String
}
type Relationship {
code: String!
display: String!
description: String!
}
type Address {
line_1: String!
line_2: String!
city: String!
state: String!
zip_code: String!
}
type Person {
person_id: PersonId!
name: Name!
hire_date: Datetime!
preferred_name: PreferredName
contact_info: ContactInfo
exempt_status: ExemptStatus
hired_hours_per_week: Float
hired_hours_per_two_weeks: Float
emergency_contacts: [EmergencyContact]
}
type PersonResponse {
status: Int
error: String
person: Person
}
input PersonInput {
name: NameInput!
hire_date: Datetime!
preferred_name: PreferredNameInput
contact_info: ContactInfoInput
exempt_status_code: String
hired_hours_per_week: Float
hired_hours_per_two_weeks: Float
}
input AddressInput {
line_1: String!
line_2: String!
city: String!
state: String!
zip_code: String!
}
type ContactInfo {
primary_phone: String!
alternate_phone: String
email: String
address: Address
}
input ContactInfoInput {
primary_phone: String!
alternate_phone: String
email: String
address: AddressInput
}
type EmergencyContactId {
id_: String!
}
type EmergencyContact {
emergency_contact_id: EmergencyContactId!
name: Name!
relationship: Relationship!
contact_info: ContactInfo!
}
input EmergencyContactInput {
name: NameInput!
relationship_code: String!
contact_info: ContactInfoInput!
}
input UpdateEmergencyContactInput {
name: NameInput
relationship_code: String
contact_info: ContactInfoInput
}
type EmergencyContactResponse {
status: Int
error: String
person_id: PersonId
emergency_contact: EmergencyContact
}
type Mutation {
create_person(person_input: PersonInput): PersonResponse
update_name(person_id: String!, name_input: UpdateNameInput!): PersonResponse
update_hire_date(person_id: String!, hire_date: Datetime!): PersonResponse
update_preferred_name(
person_id: String!
preferred_name_input: UpdateNameInput!
): PersonResponse
delete_person(person_id: String!): PersonResponse
}
`);

How to pass nested variables to the GraphQL query in Apollo?

Trying to pass nested variables to the GraphQL query but my server gets only top-level variables (shopId), everything else is null.
I tried:
#1
const CALCULATE_PACKAGE_PRICE = gql`
query CalculatePackagePrice(
$shopId: String!
$address1: String
$zip: String
$city: String
$countryCode: String
) {
calculatePackagePrice(
where: {
shopId: $shopId
destination: {
address1: $address1
zip: $zip
city: $city
countryCode: $countryCode
}
}
) {
name
price
userErrors {
field
message
}
}
}
`
const [calculatePackagePrice, { loading, data }] = useLazyQuery(
CALCULATE_PACKAGE_PRICE,
{
variables: {
shopId: shopId,
destination: {
address1: "Example 123",
zip: "123",
city: "Test",
countryCode: "US",
},
},
}
)
And #2:
export function CALCULATE_PACKAGE_PRICE({ shopId, destination }) {
return gql`
query CalculatePackagePrice {
calculatePackagePrice(
where: {
shopId: "${shopId}"
destination: {
address1: "${destination.address1}"
zip: "${destination.zip}
city: "${destination.city}"
countryCode: "${destination.countryCode}"
}
}
) {
name
price
userErrors {
field
message
}
}
}
`
}
const [calculatePackagePrice, { loading, data }] = useLazyQuery(
CALCULATE_PACKAGE_PRICE({
shopId: shopId,
destination: {
address1: "Example 123",
zip: "123",
city: "Test",
countryCode: "US",
},
})
)
It works just fine when I hardcoded variables content to the queries. What I'm doing wrong?
Here is a helpful snippet from graphql docs,
All declared variables must be either scalars, enums, or input object types. So if you want to pass a complex object into a field, you need to know what input type that matches on the server.
You're correctly passing in the variables as strings, but then trying (perhaps successfully, but I've never seen the syntax before) to create the object in the gql template string. Instead, create an input type for destination and where.
input WhereInput {
shopId: String!
destination: DestinationInput!
}
input DestinationInput {
address1: String!
zip: String!
city: String!
countryCode: String!
}
then change the query on the client (and update the server definition),
const CALCULATE_PACKAGE_PRICE = gql`
query CalculatePackagePrice($where: WhereInput!) {
calculatePackagePrice(where: $where) {
name
price
userErrors {
field
message
}
}
}
`
then pass the variables like,
const [calculatePackagePrice, { loading, data }] = useLazyQuery(
CALCULATE_PACKAGE_PRICE,
{
variables: {
where: {
shopId,
destination: {
address1: "Example 123",
zip: "123",
city: "Test",
countryCode: "US",
},
},
}
}
)

Logical OR in SDL - field name 'OR' that is not defined for input object type

I am trying to return students of who are either mature or highscool
graphqlOperation(listStudents, {
filter: {
type: { eq: 'student' },
OR: [{ category: { eq: 'mature' }, { eq: 'highschool' }]
},
})
);
It looks like I need to add the logical OR in my schema
input TableStudentFilterInput {
ID: TableStringFilterInput
type: TableStringFilterInput
category: TableStringFilterInput
name: TableStringFilterInput
}
and my filter
input TableStringFilterInput {
ne: String
eq: String
le: String
lt: String
ge: String
gt: String
contains: String
notContains: String
between: [String]
}
How do I allow my schema to accept a logical operator?

How to write graphql query wiith custom objects

The server side of graphql is with nodejs and express. This is the schema for graphql. It has one query which accepts DateT object having from and to dates.
var schema = buildSchema(`
type Query {
courseWithDate(
timeFilter: DateT
): Course
},
type Course {
...
from: String
to: String
},
type DateT{
from : String
to : String
}
`);
and this is how I am getting courses
I am able to run the application with this url
localhost:4000/graphql
This is the query I am using
query courseWithDate($from: dateFrom, $to: dateTo) {
courseWithDate(timeFilter: {
from: "${dateFrom}"
to: "${dateTo}"
}) {
title
...
}
}
with these parameters
{
"from": "2019-10-10","to":"2019-10-10"
}
Exception message I get is related to the input type I am trying to pass.
{
"errors": [
{
"message": "The type of Query.courseWithDate(timeFilter:) must be Input Type but got: DateT.",
"locations": [
{
"line": 6,
"column": 25
}
]
}
]
}
I'm not sure, but probably this style looks more like best practice
type Course {
id: Int
title: String
author: String
from: String
to: String
description: String
topic: String
url: String
}
input DateInput {
dateFrom: String!
dateTo: String!
}
type Query {
courseWithDate(input: DateInput!, name: String!): Course
}
And Query on client side should be:
{
courseWithDate(input: {
dateFrom: "${dateFrom}"
dateTo: "${dateTo}"
}
name: "${name}")
{
id
name
}
}

Resources