Passing variables to an external resolve reference - apollo

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
}
},
};

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.

AWS Amplify searchable field by geo distance (location)

I'm developing an application that will allow users to search for other users by ordering them by geo location.
According to the information found on the internet, to do this on amplify I have to perform the following steps:
create an elastic search mapping, indicating the type 'geo_point' on the lastPosition attribute (I would like to define it once in the project file and not at each build in the elastic search console)
create a custom query and a custom vtl resolver and set the sort by lastPosition (of type '_geo_distance') in it.
But I don't understand how to do these 2 steps, so I need some suggestion :(.
// my schema.graphql
type User
#model
#auth(rules: [{allow: owner}, {allow: private, operations: [read]}])
#searchable {
// ...other fields
id: ID!
lastPosition: Position
positionUpdatedAt: AWSDateTime
}
type Position {
lat: Float!
lon: Float!
}
// my custom searchUsers query in graphql/custom_queries.ts
export const searchUsersNearby = /* GraphQL */ `
query SearchUsersNearby(
$filter: SearchableUserFilterInput
$sort: [SearchableUserSortInput]
$location: PositionInput // current user position that i need to use in vtl resolver to sort users by distance,
$limit: Int
$nextToken: String
$from: Int
$aggregates: [SearchableUserAggregationInput]
) {
searchUsersNearby(
filter: $filter
sort: $sort
limit: $limit
nextToken: $nextToken
from: $from
aggregates: $aggregates
) {
items {
id
// ...other fields
lastPosition
positionUpdatedAt
createdAt
updatedAt
owner
}
nextToken
total
aggregateItems {
name
result {
... on SearchableAggregateScalarResult {
value
}
... on SearchableAggregateBucketResult {
buckets {
key
doc_count
}
}
}
}
}
}
`;
I use amplify v8.1.0 with transformer v2.
Thank you guys

Calling AWS AppSync graphql API from Lambda

I am trying to update the value of a table using the AWS-app sync graphql API,
I am able to create data and add it in a table using graphql mutation in lambda
but when I am trying to update the data its not working.
I am calling this lambda service from an API Gateway.
I am referring this article to code
https://cloudonaut.io/calling-appsync-graphql-from-lambda/
I would like to mentioned git no error in cloud watch log
Here is the schema for my graphql
type Mutation {
createLib_content(input: CreateLib_contentInput!): lib_content
#aws_iam
updateLib_content(input: UpdateLib_contentInput!): lib_content
#aws_iam
deleteLib_content(input: DeleteLib_contentInput!): lib_content
}
input CreateLib_contentInput {
content: String
userId: String
}
input UpdateLib_contentInput {
content: String
id: ID!
}
Create Mutation
graphqlData = await clientDetails.mutate({
mutation: gql(`
mutation CreateLibContent($input: CreateLib_contentInput!) {
createLib_content(input: $input) {
id
content
}
}`),
variables: {
input: {
content : {},
userId : identitiesDetails.userId
}
},
});
Update Mutation
const mutation = gql(`
mutation UpdateLibContent($input: UpdateLib_contentInput!) {
updateLib_content(input: $input) {
userId
content
}
}`);
await clientDetails.mutate({
mutation,
variables: {
input: {
id : "2947c37e-6f76-40d8-8c10-4cd6190d3597",
content : JSON.stringify(event)
}
}
}).promise;
Thanks to #cppgnlearner your guess were right.
I just removed the .promise from my update code
And it started working.
can't believe such a small thing took my whole day.

AWS appsync query resolver

Currently I have my resolver as a lambda function :
import boto3
from boto3.dynamodb.conditions import Key
def lambda_handler(event, context):
list = []
for device in event['source']['devices'] :
dynamodb = boto3.resource('dynamodb')
readings = dynamodb.Table('readings')
response = readings.query(
KeyConditionExpression=Key('device').eq(device['device'])
)
items = response['Items']
list.extend(items)
return list
I would like to be able to have this as a VTL resolver on the dynamodb. My problem is that my table has a sort key
This means I can't use a batch resolver to query on a bunch of id's because I would also need to provide the sort key, and I just want all the results by primary partition key.
How do you query with a bunch of ids using VTL, basically replicating my lambda function in VTL. Is this even possible ?
Schema added, please excuse the mess it is a work in progress and am attempting many things. Still very new to graphQL
type Device {
id: String
device: String!
}
input DeviceInput {
id: String
device: String!
}
type DeviceReadings {
devices: [Device]
}
type Mutation {
createDevice(input: DeviceInput): Device
}
type PaginatedDevices {
devices: [Device]
readings: [Reading]
cows: [cow]
nextToken: String
}
type Query {
getAllUserDevices(nextToken: String, count: Int): PaginatedDevices
getAllDeviceReadings: DeviceReadings
getAllUserReadings: DeviceReadings
getAllReadings(deviceId: String!): Readings
getCowReadings(cowId: String!): UserCowReadings
}
type Reading {
device: String
time: Int
cow: Int
battery: String
}
type Readings {
items: [Reading]
}
type UserCowReadings {
devices: [Device]
readings: [Reading]
}
type cow {
id: Int
device: String
nait: String
}
schema {
query: Query
mutation: Mutation
}
Yes you can do this but you will need to tweak your schema a bit. In that lambda you are essentially saying "for each device do a DynamoDB query to get the most recent readings for that device". Conceptually I would say that devices have many readings. With this in mind, lets make a schema:
type Device {
id: ID!
name: String
# Get the most recent readings for this device.
# Do a Query where "device = $ctx.source.id"
readings(limit: Int, nextToken: String): ReadingConnection
}
type Reading {
# Use the source's device id in the table to fetch the real device
# GetItem where device = $ctx.source.device (and any sort key condition)
device: Device
time: Int
cow: Int
battery: String
}
type ReadingConnection {
items: [Reading]
nextToken: String
}
type DeviceConnection {
items: [Device]
nextToken: String
}
type Query {
getAllDevices(limit: Int, nextToken: String): DeviceConnection
}
You may then paginate through your devices and paginate through each devices readings separately:
query GetAllDevicesAndReadings {
getAllDevices(first: 10) {
items {
id
name
readings(limit: 10) {
time
cow
battery
}
}
}
}
I recommend using the drop down in the AppSync console's resolver page to get more ideas for what you can do with the resolver VTL to implement these GetItems and Queries. This is a good starting point. Let me know if you have trouble implementing the VTL.

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.