-
Notifications
You must be signed in to change notification settings - Fork 28
Description
GraphQL has a built-in compute or runtime component where developers can customize their own business logic directly at the API layer. These components are called Resolvers, and they provide the logical glue between the data defined in the GraphQL schema and the data in the actual data sources. Using resolvers you can map an operation or even a single field of a type defined in the schema with a specific data source, which allows to retrieve data for different fields in different data sources with a single API call. They are called “Resolvers” because they are built-in functions in GraphQL that “resolve” types or fields defined in the GraphQL schema with the data in the data sources.
AppSync currently leverages VTL or Apache Velocity Templates to provide a lean and fast compute runtime layer to resolve GraphQL queries or fields. It uses VTL internally to translate GraphQL requests from clients into a request to a data source as well as translate back the response from the data source to clients.
However, if you’re not familiar with VTL you need to learn a new language to take full advantage of these benefits, which can potentially delay the implementation of a GraphQL project for your business. While there are toolchains such as the Amplify CLI (https://docs.amplify.aws/cli/) and the GraphQL Transformer (https://docs.amplify.aws/cli/graphql-transformer/overview) that can automatically generate VTL for AppSync APIs, customers have told us that sometimes they just want to write their own resolver logic in a language they are familiar with and the preferred runtime to do so is JavaScript.
As an alternative to VTL, we're evaluating adding support for JavaScript as a runtime for AppSync resolvers. Developers will be able to leverage all native JavaScript constructs (i.e. switches, maps, global libraries such as JSON and Math, etc) in a familiar programming model based on the latest ECMAScript specification. Ideal for simple to complex business logic where no external modules are required. Just like VTL all JavaScript Resolvers code is hosted, executed and managed internally by AppSync. Customers don’t have to manage their GraphQL API resolvers business logic in other AWS services.
We propose JavaScript Resolvers are stateless and don’t have direct internet/network access to data sources, network access is handled by the AppSync service. For instance, in order to access an external API the resolver needs to be setup as an HTTP Resolver with JavaScript. AppSync executes the business logic defined in the resolver then sends the transformed request to the data source and the data source only (DB or external API). There's no access to the local file system where the Resolver is executed either. Global objects such as JSON and Math are pre-loaded and available, async/await is supported however window() and API's such as fetch and XMLHttpRequest are not.
All the utilities currently provided by VTL ($util - https://docs.aws.amazon.com/appsync/latest/devguide/resolver-util-reference.html) will be available for JavaScript Resolvers unless there’s a related utility already available natively on JavaScript (i.e. JSON.stringify, JSON.parse). Just like JSON and Math, “util” is also a global object built-in to the resolvers and automatically available. Similar to VTL, all data sources information, including connection strings, endpoints, tables and permissions, are all baked into the resolver and managed by AppSync to securely connect to the data sources.
It’s not possible to import external Node.js modules in order to maintain a lean and optimized AppSync runtime layer specifically for GraphQL workloads. If developers want to import modules or do anything more complex, the recommended approach is to use Direct Lambda Resolvers.
Please comment on this thread if you have some thoughts or suggestions on this feature or if you think we’re missing any story points which you would love to see as a part of this feature.
Sample None Resolver:
function handleRequest(context) {
if (!context.arguments.title
|| !context.arguments.author) {
const earlyReturnData = {
"error": "Arguments title and author are required."
};
// util is a built-in global object
// The following line returns data immediately, bypassing handleRespnse
// if provided.
util.returnEarly(earlyReturnData);
// Alternatively, we can call util.error with our message and data
// util.error("Error message here", "Error type here", earlyReturnData);
}
const autoId = util.autoId();
return {
version: "2018-05-29",
payload: {
id: autoId,
title: context.arguments.title,
author: context.arguments.author,
content: justARandomString(36)
}
};
}
// handleResponse function is optional
// Here, we do not need to postprocess the result, so we just omit that function.
// If handleResponse function is not specified, context.result will be returned.
// Helper function
function justARandomString(length) {
var result = "";
for(var i=0; i < length; i++){
var r = Math.random()*16 | 0;
result += r.toString(16);
}
return result;
}
Sample DynamoDB Resolver:
function handleRequest(context) {
var requestData = {
"version": "2018-05-29",
"operation": "Query",
"query": {
"expression": "#author = :authorId AND postedAt > :postedAfter",
"expressionNames": {
"#author": "authorId"
},
"expressionValues": {
":authorId": {
"S": context.arguments.authorId
},
":postedAfter": {
"S": context.arguments.postedAfter
}
}
},
"index": "postedAtIdx",
"select" : "ALL_PROJECTED_ATTRIBUTES",
"consistentRead": true
}
if (context.arguments.filter) {
requestData.filter = {
"expression": "begins_with(#postId, :filter)",
"expressionNames": {
"#postId": "postId"
},
"expressionValues": {
":filter": {
"S": context.arguments.filter
}
}
};
}
return requestData;
}
// while handleRequest() is mandatory,
// handleResponse() is optional. If not present, it's a passthrough
function handleResponse(context) {
var result = [];
if (context.result.items) {
context.result.items.forEach(function(item) {
result.push(getIdAndAuthor(item));
})
}
return result;
}
// Helper function
function getIdAndAuthor(item) {
return {
"id": item.postId,
"author": item.authorId
// ignoring other fields of item
}
}