How to make subscription with arguments correctly in GraphQL? - amazon-web-services

In AWS AppSync I tried to test my GraphQL schema. I'm a little confused and need help. My subscription called watchMessages doesn't work when there is an argument. If I remove the argument from schema and test it, it works. But I only need to receive messages from a specific room. What am I missing?
input CreateMessageInput {
roomId: String!
messageText: String
messageAuthor: String
}
type CreateMessageOutput {
messageId: String!
messageCreatedDateTime: String!
messageText: String!
messageAuthor: String
}
type Mutation {
createMessage(input: CreateMessageInput): CreateMessageOutput
}
type Subscription {
watchMessages(roomId: String!): CreateMessageOutput
#aws_subscribe(mutations: ["createMessage"])
}
I make such subscription query:
subscription MySubscription {
watchMessages(roomId: "5d354323") {
messageId
messageCreatedDateTime
messageText
messageAuthor
}
}
I make such mutation query:
mutation MyMutation {
createMessage(input: {roomId: "5d354323", messageAuthor: "Bob", messageText: "Hello!"}) {
messageId
messageCreatedDateTime
messageText
messageAuthor
}
}

Finally, I found the solution. All subscription parameters must exist as a field in the returning type. This means that the type of parameter must also match the type of the field in the returning object. I added roomId field to the CreateMessageOutput type.
type CreateMessageOutput {
roomId: String!
messageId: String!
messageCreatedDateTime: String!
messageText: String!
messageAuthor: String
}

Related

graphql query with different output

I have an api with an endpoint which can takes different query parameters.
/toto?number=&shipments.tracking_number=&customer.email=. The thing is that to get the query, I'm using only one input on the frontend and depending on that input, I have to fill the corresponding query parameter. But, depending on the query the user is passing, the JSON I receive in response is different. For example if he provides his email, the duplicates field won't be present in the response or if he provides a tracking number, the total won't be there either. Is there a way to make a query that is dynamic to handle if a field is in the response or not ?
export const GET_ORDER = gql`
query GetOrder($query: String!) {
getOrder(query: $query) {
id
tokenValue
number
total
duplicates
shippingTotal
}
}`
You can do this with two subtypes as an example using unions and interfaces, you can easily extend to additional ones.
In your typeDefs:
interface CoreOrder {
id: ID!
tokenValue: String!
number: Int!
shippingTotal: Float!
}
Type Order1 implements CoreOrder {
id: ID!
tokenValue: String!
number: Int!
shippingTotal: Float!
total: Float!
}
Type Order2 implements CoreOrder {
id: ID!
tokenValue: String!
number: Int!
shippingTotal: Float!
duplicates: Int!
}
union Order = Order1 | Order 2
Now your query can be defined to return whichever fields are appropriate:
query GetOrder($query: String!) {
getOrder(query: $query) {
__typename
id
tokenValue
number
shippingTotal
... on Order1 {
total
}
... on Order2 {
duplicates
}
}
}
You'll need to implement:
a __resolveType resolver that figures out what type your query is returning
resolvers for CoreOrder to handle those 4 fields
an Order1 resolver for the total field
an Order2 resolver for the duplicates field
Your GraphQL server will then figure out which resolvers to call and in what order and package up the results for your client.

Passing variables to an external resolve reference

I'm using Apollo Federation for 2 months but I'm actually stuck. I've no idea how to pass a variable between my two graphql services.
I've got a website (website graphql service) which have orders (orders graphql service).
I have a query to find websites and for these websites I want some stats of orders for a date range. Here the typedef (website) :
type Query {
websites(orderFilter: OrderFilterInput): [Website!]
}
type Website #key(fields: "id") {
id: ID!
name: String!
url: String!
orderSummary(orderFilter: OrderSummaryFilterInput): OrderSummary
}
input OrderSummaryFilterInput {
beginDate: Date
endDate: Date
}
extend type OrderSummary #key(fields: "websiteId") {
websiteId: String! #external
}
The resolver :
orderSummary: (website, { orderSummaryFilter }) => {
console.log("orderSummaryFilter", orderSummaryFilter); // filters are OK
// HOW CAN I PASS orderFilterSummary to my order graphql service here ????
return { __typename: "OrderSummary", websiteId: website.id };
}
And Order graphql service
Typedef part :
type OrderSummary #key(fields: "websiteId") {
websiteId: String!
count: Int
amount: Int
}
Resolver part :
// order gql service
OrderSummary: {
__resolveReference(website, args, info) {
console.log("website id :", website.id); // I ve got my website ID
// HOW TO GET OrderSummaryFilter here ????
},
},
How can I access to order summary filter variable in order graphql resolver ? Thank you.
From what I am aware of, it is not possible to send variables from one service to another other then the ID. But there is a solution to this.
If you want to pass in variables, extend your Website type in your order service instead of extending order type in website service.
Order typedef:
extend type Website #key(fields: "id") {
id: ID! #external
orderSummary(orderFilter: OrderSummaryFilterInput): OrderSummary #requires(fields:"id")
}
Order resolver:
Website: {
orderSummary: async (website, { orderFilter }) => getOrderSummary(orderFilter) //get orderSummary with orderFilter
},
So I want to expound on the previous (and I believe correct) answer:
In Federation, you almost never should have to expose a field called somethingId (userId, websiteId, etc). That is often either a left-over from Schema Stitching, or you simply got your type origins backward. Instead of using somethingId, you should be able to just use the object. Often, moving the #extend to the other service will get rid of the somethingId field, and get rid of the type of problem you're currently facing:
Website Service:
type Query {
websites(orderFilter: OrderFilterInput): [Website!]
}
type Website #key(fields: "id") {
id: ID!
name: String!
url: String!
}
Order Service:
extend type Website #key(fields: "id") {
id: ID! #external
orderSummary(orderFilter: OrderSummaryFilterInput): OrderSummary
}
input OrderSummaryFilterInput {
beginDate: Date
endDate: Date
}
type OrderSummary {
website: Website!
count: Int
amount: Int
}
Resolvers:
const resolvers = {
Website: {
orderSummary(parent, args, context) {
const websiteId = parent.id;
// args is the data you wanted
}
},
};

appsync subscription with arguments

We are having huge troubles with subscriptions with arguments
to simplify the problem Here are the steps to reproduce
create a simpleSchema
type Mutation {
testSubMutation(param: String!): String
}
type Query {
testQuery: String
}
type Subscription {
testSubs(param: String): String
#aws_subscribe(mutations: ["testSubMutation"])
}
I attached a local resolver to the mutation which returns the timestamp.
in one window open the app sync query tab and make the subscription
subscription sub{
testSubs
}
in the other window make a mutation
mutation mut{
testSubMutation(param:"123")
}
works like a charm
now change the subscription to listen to a parameter
subscription sub{
testSubs(param:"123")
}
Does not work any more. :(
Any help is appreciated.
Subscriptions require the parameter you're filtering on to be in the response of the mutation. Could you try updating your mutation to this?
mutation mut{
testSubMutation(param:"123") {
param
}
}
I'm doing same as above for subscription but not getting response, It's only working with one argument room
mutation addMessage {
addMessage(input: {
room: "45a87f5b-ef9e-41cd-9cd7-f3e2f4946d31",
receiver: "3cea9c02-1cf5-4248-8ebe-3580a7a47b8b" }) {
id
room
receiver {
id
userName
}
}
}
subscription roomMessage {
roomMessage(room: "45a87f5b-ef9e-41cd-9cd7-f3e2f4946d31",
receiver: "3cea9c02-1cf5-4248-8ebe-3580a7a47b8b") {
id
room
receiver {
id
userName
}
}
}

How do I add union type in Apollo graphql

I created this question in case anyone was curious on how to add union / Polymorphic types in Apollo. Hopefully this will make it easier for them.
In this example I wanted the response to either be a Worksheet or ApiError
// typedefs.js
export default [`
schema {
query: Query
}
type Query {
worksheet(id: String!): Worksheet | Error
}
type Worksheet {
id: String!
name String
}
type ApiError {
code: String!
message: String!
}
`];
// resolvers.js
export default {
Query: {
worksheet(_, args, { loaders }) {
return loaders.worksheet.get(args.id).catch(() => {
// ApiError
return {
code: '1',
message: 'test'
}
});
}
}
};
// Express Server
import { graphqlExpress } from 'apollo-server-express';
import { makeExecutableSchema } from 'graphql-tools';
import typeDefs from './typedefs';
import resolvers from './resolvers';
...
app.post(
'/graphql',
graphqlExpress(req => ({
makeExecutableSchema({ typeDefs, resolvers }),
context: mkRequestContext(req.ctx, req.log),
formatError: formatGraphQLError(req.ctx, req.log)
}))
);
In GraphQL to add a union type in the typedefs you have to define the union
i.e union WorksheetOrError = Worksheet | ApiError
// typedefs.js
export default [
`
schema {
query: Query
}
type Query {
worksheet(id: String!): WorksheetOrError
}
union WorksheetOrError = Worksheet | ApiError
type Worksheet {
id: String!
name String
}
type ApiError {
code: String!
message: String!
}
`];
In the resolvers you have to define a resolver for the union type that has the property __resolveType. This will help tell the GraphQL executor which type the result is.
// resolvers.js
export default {
Query: {
worksheet() {
...
}
},
WorksheetOrError: {
__resolveType(obj) {
if (obj.id) {
return 'Worksheet';
}
if (obj.code) {
return 'ApiError';
}
return null;
}
},
};
To create a GraphQL Query in Apollo Client
// Your application code.
// This is my Worksheet Query in my React code.
const WorksheetQuery = gql`
query GetWorksheet($worksheetId: String!) {
worksheet(id: $worksheetId) {
... on Worksheet {
id
name
}
... on ApiError {
code
message
}
}
}
Now you can check the __typename to check what type is in the response.
Note: For those who are wondering why I'm not using GraphQL errors. It's because Apollo doesn't seem to handle errors well when it encounters a graphQL error. So for a work around I'm trying to return a custom ApiError in my response.
There a few reasons why using a union with an error type is nice.
Currently if you wanted a partial response with GraphQLError. Apollo does not cache the errors so if you wanted to re-use the cached response later you wouldn't have the complete response since the errors are removed. (Now you can't display the proper UI with errors)
Getting GraphQLError back in Apollo would return a flat list of errors with the path to where the error is in the data. So you would need to verify that which part of your schema did the error occur in. However if you follow the instructions above you would have the error within the schema already. That way you already know which part of the schema the error happened.

AWS AppSync - Directive "aws_subscribe" may not be used on FIELD_DEFINITION

I'm trying to get to grips with AWS AppSync. I'm quite new to GraphQL. I've got the following GraphQL:
type Mutation {
deleteParcel(geoHash: String!, type_id: String!): Parcel
addParcel(input: ParcelInput!): Parcel
batchAddParcels(parcels: [ParcelInput]): [Parcel]
}
type Parcel {
geoHash: String!
type_id: String!
}
type ParcelConnection {
items: [Parcel]
}
input ParcelInput {
geoHash: String!
type_id: String!
}
input ParcelsInput {
parcels: [ParcelInput]
}
type Query {
getNearbyParcels(geoHash: String!): ParcelConnection
}
type Subscription {
onAddParcel(geoHash: String, type_id: String): Parcel
#aws_subscribe(mutations: ["addParcel"])
onBatchAddParcels(geoHash: String): Parcel
#aws_subscribe(mutations: ["batchAddParcels"])
onDeleteParcel(geoHash: String, type_id: String): Parcel
#aws_subscribe(mutations: ["deleteParcel"])
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
All seems to be setup fine on AWS console. I get the schema.json and then run command:
aws-appsync-codegen generate AWSGraphQL.graphql --schema schema.json --output AppsyncAPI.swift
and get the response:
../SnatchHQ/snatch_appsync/AppSync/AWSGraphQL.graphql: Directive "aws_subscribe" may not be used on FIELD_DEFINITION.
.../SnatchHQ/snatch_appsync/AppSync/AWSGraphQL.graphql: Directive "aws_subscribe" may not be used on FIELD_DEFINITION.
.../SnatchHQ/snatch_appsync/AppSync/AWSGraphQL.graphql: Directive "aws_subscribe" may not be used on FIELD_DEFINITION.
error: Validation of GraphQL query document failed
Can anyone help?
If the file AWSGraphQL.graphql is your API GraphQL schema, then that explains the problem. What you need to do is define a *.graphql file that defines your query, mutation, and subscription operations based on your GraphQL API. For example, the following query definitions would match your schema
mutation AddParcel($geoHash: String!, $type_id: String!) {
addParcel(input: {
geoHash: $geoHash
type_id: $typeId
}) {
...Parcel
}
}
query GetNearbyParcels($geoHash: String!) {
getNearbyParcels(
geoHash: $geoHash
) {
...ParcelConnection
}
}
subscription OnAddParcel {
onAddParcel {
...Parcel
}
}
fragment Parcel on Parcel {
geoHash
type_id
}
fragment ParcelConnection on Parcel Connection {
items {
...Parcel
}
}
Assuming you named it something like parcels.graphql, you can then call the following to generate a Swift implementation of the AddParcel mutation, the GetNearbyParcels query, and the OnAddParcel subscription
aws-appsync-codegen generate parcels.graphql \
--schema schema.json \
--output AppSyncParcelsAPI.swift