Front-End Web & Mobile
AWS AppSync pipeline resolvers and functions now support additional array methods and arrow functions
AWS AppSync is a managed service that makes it easy to build scalable APIs that connect applications to data. Developers use AppSync every day to build GraphQL APIs that interact with data sources like Amazon DynamoDB, AWS Lambda, and HTTP APIs. With AppSync, developers can write their resolvers using JavaScript, and run their code on AppSync’s APPSYNC_JS runtime.
Today, we are adding functionality to the APPSYNC_JS runtime with support for the following higher-order functions on arrays:
- Array.prototype.forEach
- Array.prototype.map
- Array.prototype.flatMap
- Array.prototype.filter
- Array.prototype.reduce
- Array.prototype.reduceRight
- Array.prototype.find
- Array.prototype.some
- Array.prototype.every
- Array.prototype.findIndex
- Array.prototype.findLast
- Array.prototype.findLastIndex
With this update, APPSYNC_JS now also supports arrow functions, which can be defined at any level within our resolver or function code. This allows to write code like this:
// filter the array to return odd numbers only
export function response() {
return [1,2,3,4,5].filter(x => x % 2)
}
or
// return the array sum
export function response() {
return [1,2,3,4,5].reduce((sum,x) => sum + x)
}
and
export function response() {
// define a test function
const test = (x) => x > 42
// return the first match (or null)
return [12, 34, 56, 9, 75].find(test)
}
With arrow functions and the new Array function support, we can now compactly write our business logic. This makes it easier to write code to solve common problems with arrays and objects, and we can also use them to write more complex functionality and utilities. We give a couple of examples below.
Writing a library to update an item in a DynamoDB table
Updating an item is a common task when working with data in Amazon DynamoDB tables. We can use the newly introduced Array functions to write a helper that creates an UpdateItem request. Given the schema
We can define the following APPSYNC_JS function to attach to our resolver. We define an update
function that uses Array.reduce
to iterate over the list of values and create an UpdateItem
request.
import { util } from '@aws-appsync/utils'
export function request(ctx) {
const { input: { id, ...values } } = ctx.args
return update({ id }, values)
}
export function response(ctx) {
return ctx.result
}
// build an UpdateItem request to SET or DELETE attributes
// of an item identified by `key`.
function update(key, values) {
const exp = Object.entries(values).reduce(
(prev, [key, value]) => {
// expression attribute name is a placeholder that you use in an
// Amazon DynamoDB expression as an alternative to an actual attribute name.
prev.names[`#${key}`] = key
if (value) {
// if a value exist:
// Use the SET action in an update expression to add one or
// more attributes to an item.
prev.sets.push(`#${key} = :${key}`)
// Expression attribute values in Amazon DynamoDB are substitutes
// for the actual values that you want to compare
prev.values[`:${key}`] = value
} else {
// if the value is null, add it to a list and:
// Use the REMOVE action in an update expression to remove
// one or more attributes from an item in Amazon DynamoDB
prev.removes.push(`#${key}`)
}
return prev
},
{ sets: [], removes: [], names: {}, values: {} }
)
// create the update expression
let expression = exp.sets.length ? `SET ${exp.sets.join(', ')}` : ''
expression += exp.removes.length ? ` REMOVE ${exp.removes.join(', ')}` : ''
return {
operation: 'UpdateItem',
key: util.dynamodb.toMapValues(key),
update: {
expression,
expressionNames: exp.names,
expressionValues: util.dynamodb.toMapValues(exp.values),
},
}
}
After attaching the function to our pipeline resolver for the updatedUser
mutation, we can update the user’s name and delete their status with this operation:
Handling single table design queries
AppSync allows to define DynamoDB datasources that use a single-table design or a multi-table approach. In a single-table design, different data types are stored in one DynamoDB table. With single-table design, we can retrieve all of our items in a single query from a single table. For example, we can update our schema to retrieve a user and their orders in a single request. First, we update our schema as show below. A user has orders, and each order is made up of zero or more items.
The data is stored in a DynamoDB table using a composite key:
- the partition key
id
- a sort key called
SK
To fetch data about a user and their orders and items, we query the table for any items where the partition key is equal to the user id. Each item in the table has a __typename
attributes that specifies the item type (e.g.: User
, Order
, or Item
).
We attach the following APPSYNC_JS function to the getUserAndOrderDetails
resolver:
import { util } from "@aws-appsync/utils";
export function request(ctx) {
const { id } = ctx.args;
let query = { id: { eq: id } };
query = JSON.parse(util.transform.toDynamoDBConditionExpression(query));
return { operation: "Query", query };
}
export function response(ctx) {
let { items } = ctx.result;
// find the user
const user = items.find((item) => item.__typename === "User");
// if no user is found, return null
if (!user) {
console.log("could not find user in reponse items");
return null;
}
const orders = {};
const orderItems = [];
// identify each `Order` and `OrderItem` in the returned items
items.forEach((item) => {
switch (item.__typename) {
case "Order":
orders[item.SK] = { ...item, id: item.SK, items: [] };
break;
case "Item":
orderItems.push({ ...item, id: item.SK });
break;
default:
break;
}
});
// associated each order item with an order
orderItems.forEach((item) => orders[item.orderId].items.push(item));
// finally, assign the orders to the user
user.orders = Object.values(orders);
return user;
}
In the response, we find the User
item using the Array.find
method. We then iterate through the items using Array.forEach
to find each order and order item. We then associate each item with its order. We can now use a query to get our data in the appropriate shape for clients:
Conclusion
In this post, we went over the new Array functions and arrow function notation introduced in APPSYNC_JS. We saw how these updated features allows to easily work with arrays and objects. We also saw how you can use them to solve common complex problems when working with data in resolvers. You can get started today in any region when AppSync is available, by reviewing APPSYNC_JS’s supported built-in objects and functions, and by reading the DynamoDB JavaScript resolvers tutorial for AppSync.