Top 9 AWS AppSync Features You Didn’t Know About

AWS AppSync has been in general availability since April of 2018. Since the release of the service, there have been many new features added as the service has matured over the past few months. In this post, I’ll touch on the ones I like the most & that the developers I have talked to are most excited about.

AWS AppSync is a service that allows developers to easily build apps with real-time and offline capabilities. To learn more, check out the documentation.

1. GraphQL CodeGen

When building AWS AppSync GraphQL APIs using the AWS Amplify CLI, there is a new feature that automatically generates all of your API code & type annotations for you!

This means you don’t have to write your client side mutations, subscriptions, or queries, the CLI will create all of this code for you & it will be ready to use as soon as your API has been created or updated:

Above, the CLI walks you through the creation process. Then, the code below is created in your project (mutations.js. queries.js, subscriptions.js, schema.json).

If you’d like a video walkthrough, check this out:

To learn more about GraphQL Codegen, check out the docs here.

2. Auto-complete in the Resolver editor

The resolver editor now has autocomplete built in! This is great for people who are new to building resolvers using VTL. This will guide you along the way showing you all of the available options and context available in the mapping templates.

3. Guided API builder

The guided API builder enables you to define a data model by using a simple web form. It automatically creates the GraphQL schema, Amazon DynamoDB data table, and resolvers that are required for your app backend.

4. GraphQL Transformer with AWS Amplify CLI

One of the most powerful features that has been added over the past few months is actually a feature of the AWS Amplify toolchain. It is the GraphQL Transform library.

When creating a GraphQL API, there are 3 main parts: the schema, the resolvers, & the data sources. Typically it is fairly cumbersome to create each of these items individually & wire them all together. Most APIs have somewhat similar operations though, like creating an item, fetching an item, & fetching a list of items.

The GraphQL Transform library allows you to define your API using the GraphQL Schema Definition Language (SDL) and then the library will expand and transform the schema into a fully descriptive AWS AppSync API that includes things like authentication, data relationships, & data sources.

What does this look like in practice? Let’s take a look.

We can take a vary basic schema type definition like this:

type Person {
id: ID!
name: String!
address: String
age: Int
}

We can then update it with an @model directive like this:

type Person @model {
id: ID!
name: String!
address: String
age: Int
}

Once this is done, we can use the amplify CLI to create an AWS AppSync API that will have an entire schema with additional queries, mutations, subscriptions, input types & more. It will also generate a database (DynamoDB in this case) & resolvers that map between all queries, subscriptions & mutations & their data source.

There are also directives for setting up authorization, fine-grained access control, & relationships. For example, say that we wanted a slightly more complex initial schema that had a list of favorite movies for each user. We could create a schema with the @connection directive that would define the relationship & create the appropriate resources for this:

type Person @model {
id: ID!
name: String!
address: String
favoriteMovies: [Movie] @connection
age: Int
}
type Movie {
id: ID!
name: String!
producer: String
description: String
}

The above schema would scaffold an entire API along with the appropriate schema & resolvers to incorporate mutations & queries for the relationships!

To learn more about this feature, check out this tutorial by Adrian Hall, my tutorial here, or check out the documentation here.

5. Resolver utilities

In the resolver (the glue between the schema & your data sources) there are all types of utilities now available. You can do things like pattern matching, date / time creation, automatic uuid creation, error throwing, returning unauthorized & more. Let’s take a look at a couple of use cases.

To see the full documentation around the latest available utilities, check out the docs here.

Generating an id on the server
When creating a item that needs a unique identifier, you may not want to create an id manually at the client level, & instead depend on the server to do so. You can do so easily using $util.autoId(). This type of ID is actually built in when you create an API using the autamtic API builder, but sometimes you may need it in other circumstances (like when you manually need to create or update a resolver.).

{
"version": "2017-02-28",
"operation": "PutItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($util.autoId()),
},
"attributeValues": $util.dynamodb.toMapValuesJson($ctx.args.input)
}

Time Helpers
Many times we would like to automatically generate time stamps when create & update an item.

There are tons of different time helpers available:

$util.time.nowISO8601() => 2018-02-06T19:01:35.749Z
$util.time.nowEpochSeconds() => 1517943695
$util.time.nowEpochMilliSeconds() => 1517943695750
$util.time.nowFormatted("yyyy-MM-dd HH:mm:ssZ") => 2018-02-06 19:01:35+0000
$util.time.nowFormatted("yyyy-MM-dd HH:mm:ssZ", "+08:00") => 2018-02-07 03:01:35+0800
$util.time.nowFormatted("yyyy-MM-dd HH:mm:ssZ", "Australia/Perth") => 2018-02-07 03:01:35+0800

We can use them in a mapping template like this:

$util.qr($context.args.input.put("updatedAt", $util.time.nowISO8601()))

The utils can also do conversion:

#set( $nowEpochMillis = 1517943695758 ) $util.time.epochMilliSecondsToSeconds($nowEpochMillis) => 1517943695

& parsing:

$util.time.parseISO8601ToEpochMilliSeconds("2018-02-01T17:21:05.180+08:00") => 1517476865180

Error Handling
We can also take advantage of the $util methods for handling errors & unauthenticated users. For example, if we wanted to return a custom error from our resolver we could do something like this:

#if ($ctx.args.input.age < 18)
$util.error("User must be 18 years of age to execute this operation")
#else
{
"version": "2017-02-28",
"operation": "PutItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($util.autoId()),
},
"attributeValues": $util.dynamodb.toMapValuesJson($ctx.args.input)
}
#end

We can also pass in a second argument for the error type:

$util.error("User must be 18 years of age to execute this operation", "age-restricted")

This will populate the errorType field in the error message.

Pattern Matching
You can also do pattern matching pretty easily in the resolver using $util.matches(String, String). For example, let’s say we had an app that restricted users from creating an account if they lived in New York state. we could accomplish this with the following code:

#if ($util.matches("New York", $context.args.input.address)) 
$util.error("You can't use this app if you live in New York")
#else

Unauthorized
There are many authorization & fine-grained access control use-cases where you’ll be checking to see if the user requesting data is the user that is supposed to have access to the data. One easy way to handle unauthenticated requests is in the resolvers by using the $util.unauthorized helper:

#if ($context.identity.sub != $context.result.userId)
$util.unauthorized()
#else
$util.toJson($context.result)
#end

There are more than 80 utility methods so it’s impossible for me to cover them all here, but if you’re interested in learning more check them out in the reference located here in the documentation.

6. Test/Debug in the Resolver editor of the console (essentially a built-in IDE)

You can now test your resolvers directly in the resolver editor. This is especially useful for when you’re getting started with VTL & you may be creating variables, maps & arrays (or updating existing data structures) & want to log out the values you are working with. Check out the video below for a demo of how this works.

7. Fine Grained Authorization with Subscriptions

When AWS AppSync was released, it was not possible to do fine-grained access control on subscriptions. This limited developers to only being able to do authorization in queries & mutations but not in subscriptions.

You can now apply Fine Grained Access Controls to GraphQL subscriptions at the time a client makes a subscription! This is done by attaching a resolver to the subscription field, at which point you can query data from a data source and perform conditional logic in either the request or response mapping template. You can also return additional data to the client, such as the initial results from a subscription, as long as the data structure matches that of the returned type in your GraphQL subscription.

To learn more, check out the documentation.

8. Offline helpers for JavaScript Apollo cache

If you’ve ever worked with GraphQL in a client application, you’ve probably written many GraphQL mutations with an optimistic UI as well as GraphQL subscriptions that update your UI as the updates come through.

These operations usually require quite a bit of repetitive logic, but for the most part do the exact same thing. For example, in a basic todo app, to create a mutation & provide an optimistic response you may have written the following code:

graphql(CreateTodo, {
options: {
update: (dataProxy, { data: { createTodo } }) => {
const query = ListTodos
const data = dataProxy.readQuery({ query })
data.listTodos.items.push(createTodo)
dataProxy.writeQuery({ query, data })
}
},
props: props => ({
createTodo: todo => props.mutate({
variables: todo,
optimisticResponse: () => ({
createTodo: { id: uuidV4(), ...todo, __typename: 'Todo', version: 1 }
}),
})
})
}
)

With the latest release of the AWS AppSync SDK, we’ve added a couple of new helpers to help reduce this boilerplate: graphqlMutation & buildSubscription.

So now, the above code can be reduced to this:

graphqlMutation(CreateTodo, ListTodos, 'Todo')

You can also use the buildSubscription helper to abstract away the need to write subscription logic to update your store & UI with just a couple of lines of code:

this.props.data.subscribeToMore(
buildSubscription(SubscribeTodos, ListTodos)
)

To learn more about these offline helpers, check out the documentation here.

9. CloudFormation support

AWS has a feature called CloudFormation. The simplest way to describe what CloudFormation is is that it is a tool from AWS that allows you to easily create AWS resources & services using template files. You define the resources you would like AWS to spin up in a document, click a button, and AWS creates it everything for you. The philosophy behind CloudFormation is also sometimes referred to as Infrastructure as Code.

CloudFormation now supports AWS AppSync, so you can create new AWS AppSync resources using CloudFormation templates! This includes definitions of schema, data sources, resolvers, & authentication types.

To learn more about how this works, check out this tutorial by Adrian Hall.
You can also use the Serverless framework to deploy & manage AWS AppSync APIs. Check out
this post to learn more about it.

Conclusion

The AppSync team is continuously improving & iterating on the service. Keep an eye out for more cool new stuff coming down the pipe!

To learn more about AWS AppSync, check out the documentation or the Awesome AWS AppSync repo here.