I have an existing GraphQL API that exposes a schema like this:
type AType {
id: Int!
name: String!
children: [BType]
}
type BType {
id: Int!
name: String!
}
type Query {
a(id: ID): A
as: [A]
}
plus some other omitted scalar fields in both. There are a couple of such API endpoints and I want to aggregate them all under a single AppSync instance, so that the front can query one endpoint with a single schema for all the data they need.
What I would like to do is if the front sends a query
as {
id,
name,
children {
id,
name
}
}
it would get relayed exactly to my existing GraphQL endpoint. The AWS examples linked on the AppSync page talk about GraphQL endpoints but the only linked code example is this, and it shows a resolver like this:
#**
Make an arbitrary HTTP call when this field is listed in a query selection set.
The "relativePath" is the resource path relative to the root URL provided when you
created the HTTP data source. Pass a "params" object to forward headers, body,
and query parameters to the HTTP endpoint.
*#
#if($context.identity.sub == $context.args.userId)
#set($payload = "query ListOrders {
listOrders(
userId: ""$context.args.userId""
orderDateTimeStatus: {
le : {
orderDateTime: ""$context.args.orderDateTime""
}
}
limit: 10
) {
items {
userId
status
orderDateTime
details
orderId
}
nextToken
}
}")
{
"version": "2018-05-29",
"method": "POST",
"resourcePath": "/graphql",
"params":{
"body": {
"query": "$util.escapeJavaScript($payload)"
},
"headers":{
"Content-Type": "application/json",
"x-access-token" : "$context.request.headers.x-access-token"
}
}
}
#else
$utils.unauthorized()
#end
This uses only the arguments passed into the query, not the query structure. If front requests a subset of fields of A, I only want to make a request for that subset of fields, not a request for all that then gets cut down by AppSync. Also it doesn't even contain nested objects -- would I have to fetch all the children every time just so that they get later ignored by AppSync?
Assume there are 4-5 microservices that have their own GraphQL endpoints that have different queries in their schemas. I'd like to map the requests 1-1 to them. How would I write such a resolver? AppSync doesn't really have a good playground environment where I could debug the resolver so I can't just look at the $ctx object and reverse-engineer the structure of the original query (AFAIK).
I found this question that asks just that, but the solution there doesn't work as intended, as mentioned by the author, and it seems to be dead.
Related
Implementing subscriptions for AWS AppSync I use the enhanced filter capability to filter out tasks, that does not belong to a specific user.
To distinguish between users an ID is used in the claims part of the verified JWT that is then parsed in the $context object in the VTL response mapping.
But subscribers will always receive all objects that are created without the filter taking effect.
Our graphql schema (simplified) is looking like this
type Mutation {
createTask(
done: Boolean!,
due: String!,
id: String!,
identityId: String!,
read: Boolean!,
note: String!,
): Task
}
type Subscription {
create: Task
#aws_subscribe(mutations: ["createTask"])
}
type Task #aws_iam
#aws_oidc {
identityId: String!
done: Boolean
due: String
id: String
read: Boolean
note: String
}
The datasource for the subscription resolver is a NONE datasource and the request and response mappings are the following:
Request:
{
"version": "2017-02-28"
}
Response:
$extensions.setSubscriptionFilter({
"filterGroup": [
{
"filters" : [
{
"fieldName" : "identityId",
"operator" : "eq",
"value" : $context.identity.claims.identityId
}
]
}
]
})
$util.toJson($context.result)
With this enhanced filter I expect AppSync to filter out all tasks where the identityId does not match the one in the token... but that does not work for any reason.
What do i miss?
After a long search and almost giving up, I found the solution myself.
It's all about the correct composition of the payload attribute in the request mapping.
Without the payload object one could not access the claims in the identity part of the context object. Or at least the filtering doesn't seem to work.
Finally my request mapping looks like this:
{
"version" : "2017-02-28",
"payload" : {
"resultList" : $util.toJson($context.result),
"idnId" : "$context.identity.claims.identityId"
}
}
And in the response mapping
$extensions.setSubscriptionFilter({
"filterGroup": [{
"filters" : [{
"fieldName" : "identityId",
"operator" : "eq",
"value" : $context.result.idnId
}]
}]
})
$util.toJson($context.result.resultList)
I can then access the two objects.
So the filtering now works as expected.
In the docs we see
directive #model(
queries: ModelQueryMap,
mutations: ModelMutationMap,
subscriptions: ModelSubscriptionMap
) on OBJECT
input ModelMutationMap { create: String, update: String, delete: String }
input ModelQueryMap { get: String, list: String }
input ModelSubscriptionMap {
onCreate: [String]
onUpdate: [String]
onDelete: [String]
level: ModelSubscriptionLevel
}
enum ModelSubscriptionLevel { off public on }
Generates
A single #model directive configures the following AWS
resources:
Up to 8 resolvers (create, update, delete, get, list, onCreate,
onUpdate, onDelete) but this is configurable via the queries,
mutations, and subscriptions arguments on the #model directive.
My Question
How is this configuration done? For example I would like to add two 'list' queries to my API one for the BaseQuery and the second for a DeltaQuery that will be used for DeltaSync?
Thanks in Advance
DynamoDB operates best with a single table per application (https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-general-nosql-design.html), yet AppSync by default breaks that rule by the way it auto-generates code from the GraphQL schema (that AWS recommends users allow the API to do). Therefore, to use AppSync with GraphQL while upholding DynamoDB's best practices (assuming DynamoDB is the sole data source for the GraphQL API), would this approach work?
First, create a blank DynamoDB table (TheTable in this example) and give it a partition key (partitionKey) and a sort key (sortKey).
Second, manually enforce every GraphQL type to be backed by that table (TheTable). This is where AppSync automatic code generation will go the other direction.
GraphQL schema:
type Pineapple {
partitionKey: String!
sortKey: String!
name: String!
}
# create varying types as long as they all map to the same table
type MachineGun {
partitionKey: String!
sortKey: String!
name: String!
}
input CreatePineappleInput {
partitionKey: String!
sortKey: String!
name: String!
}
type Mutation {
createPineapple(input: CreatePineappleInput!): Pineapple
}
Third, configure your own resolvers to handle the schema (again avoid auto-generated code):
Resolver:
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
"partitionKey": $util.dynamodb.toDynamoDBJson($ctx.args.input.partitionKey),
"sortKey": $util.dynamodb.toDynamoDBJson($ctx.args.input.sortKey),
},
"attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args.input),
}
And when we run the mutation in the AppSync console:
GraphQL operation:
mutation createPineapple($createPineappleInput: CreatePineappleInput!) {
createPineapple(input: $createPineappleInput) {
name
}
}
{
"createPineappleInput": {
"partitionKey": "attraction123",
"sortKey": "meta",
"name": "Looking OK"
}
}
We get the result we hoped for:
{
"data": {
"createPineapple": {
"name": "Looking OK"
}
}
}
Is there a reason why this wouldn't achieve single-table efficiency using AppSync?
I'm not sure this statement is true
DynamoDB operates best with a single table per application
Do you mind sharing where you saw this?
DynamoDB does indeed work best if the table schema is built based on the application access patterns. That does not necessarily mean you must fit everything in one table.
I try to subscribe to mutations in a DynamoDB table in AWS AppSync. The schema briefly looks like follows:
type Post {
id: ID!
userId: String!
title: String
body: String!
}
input UpdatePostInput {
id: ID!
title: String
body: String
}
type Mutation {
updatePost(input: UpdatePostInput!): Post
}
type Subscription {
onUpdatePost(id: ID!): Post
#aws_subscribe(mutations: ["updatePost"])
}
Given the ID of the post, when I want to get the changes in the body of that post I tried making use of that subscription above as:
subscription OnUpdatePost {
onUpdatePost(id: "some-id") {
id
body ## This line should make the trick, but it does not
}
}
The subscription is fired -which is fine. However, the result contains only the ID and __typename, NOT the body:
{
"data": {
"onUpdatePost": {
"id": "some-id",
"__typename": "Post"
}
}
}
Having body among the fields should be enough following the guide here.
Am I missing something with this subscription setup?
Note:
The mutation works i.e. the body can be updated in the table behind the scenes.
I did not attach a resolver to the subscription entry, but there is one for the mutation. It should be this way afaik.
Subscriptions in AWS AppSync are invoked as a response to a mutation. Subscriptions are triggered from mutations and the mutation selection set is sent to subscribers.
I suspect that you aren't returning body in your updatePost mutation selection set. Add that field and the subscription will contain body e.g.
mutation {
updatePost(input: { id: "some-id" }) {
id
body
}
}
Trying to use loopback framework for simulating a backend service. I need to retrieve an object using POST method. I know REST services typically allow POST to update/create a resource, but here, I cannot use GET with resource details for retrieving data.
In my case, POST data contains a few query fields that have to be used to query an object and send json back. Is this possible with loopback? I cannot use GET with query parms due to security restrictions with sending data as query parms in a GET URL.
here is post request data
[ { customer:"sam", city:"noWhere", } ]
the POST event should query by customer and city, then return matching customer object
[ { customer:"sam", postcode:"352345", city:"noWhere", country:"US" } ]
I think that what you need is an express http method override middleware: https://github.com/expressjs/method-override
And defining middleware in loopback:
http://docs.strongloop.com/display/LB/Defining+middleware
You can override default loopback endpoint, like this
// Define custom remote method
Customer.fetch = function(oRequest, fnResponseCb) {
/* Do staff to find customer and finally call fnResponseCb(null, oCustomer) */
}
// Override custom remote method
Customer.remoteMethod('fetch', {
accepts: {
arg: 'oRequest',
type: 'object',
http: { source: 'body' }
},
returns: {
type: 'object',
root: true
},
http: {
path: '/',
verb: 'POST'
},
description : 'Fetch Customer'
});