Deploy AppSync API's in minutes using this Serverless plugin.
Be sure to check out all that AWS AppSync has to offer. Here are a few resources to help you understand everything needed to get started!
- Mapping Templates - Not sure how to create Mapping Templates for DynamoDB, Lambda or Elasticsearch? Here's a great place to start!
- Data Sources and Resolvers - Get more information on what data sources are supported and how to set them up!
- Security - Checkout this guide to find out more information on securing your API endpoints with AWS_IAM or Cognito User Pools!
Install the plugin via Yarn
yarn add serverless-appsync-plugin
or via NPM
npm install serverless-appsync-plugin
Add serverless-appsync-plugin
to the plugins section of serverless.yml
plugins:
- serverless-appsync-plugin
Add the following config to the custom section of serverless.yml
and update it accordingly to your needs
custom:
appSync:
name: # defaults to api
# apiKey # only required for update-appsync/delete-appsync
# apiId # if provided, will update the specified API.
authenticationType: API_KEY or AWS_IAM or AMAZON_COGNITO_USER_POOLS or OPENID_CONNECT
schema: # schema file or array of files to merge, defaults to schema.graphql (glob pattern is acceptable)
# Caching options. Disabled by default
# read more at https://aws.amazon.com/blogs/mobile/appsync-caching-transactions/
# and https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-apicache.html
caching:
behavior: FULL_REQUEST_CACHING # or PER_RESOLVER_CACHING. Required
ttl: 3600 # The TTL of the cache. Optional. Default: 3600
atRestEncryption: # Bool, Optional. Enable at rest encryption. disabled by default.
transitEncryption: # Bool, Optional. Enable transit encryption. disabled by default.
type: 'T2_SMALL' # Cache instance size. Optional. Default: 'T2_SMALL'
# if AMAZON_COGNITO_USER_POOLS
userPoolConfig:
awsRegion: # defaults to provider region
defaultAction: # required # ALLOW or DENY
userPoolId: # required # user pool ID
appIdClientRegex: # optional
# if OPENID_CONNECT
openIdConnectConfig:
issuer:
clientId:
iatTTL:
authTTL:
apiKeys:
- name: john # name of the api key
description: 'My api key'
expiresAfter: 30d # api key life time
- name: jane
description: "Jane's api key"
expiresAt: '2021-03-09T16:00:00+00:00'
# Array of additional authentication providers
additionalAuthenticationProviders:
- authenticationType: API_KEY
- authenticationType: AWS_IAM
- authenticationType: OPENID_CONNECT
openIdConnectConfig:
issuer:
clientId:
iatTTL:
authTTL:
- authenticationType: AMAZON_COGNITO_USER_POOLS
userPoolConfig:
awsRegion: # defaults to provider region
userPoolId: # required # user pool ID
appIdClientRegex: # optional
logConfig:
loggingRoleArn: { Fn::GetAtt: [AppSyncLoggingServiceRole, Arn] } # Where AppSyncLoggingServiceRole is a role with CloudWatch Logs write access
level: ERROR # Logging Level: NONE | ERROR | ALL
excludeVerboseContent: false
defaultMappingTemplates: # default templates. Useful for Lambda templates that are often repetitive. Will be used if the template is not specified in a resolver
request: my.request.template.tpl # or, e.g: false for Direct lambdas
response: my.response.template.tpl # or e.g.: false for Direct lambdas
mappingTemplatesLocation: # defaults to mapping-templates
mappingTemplates:
- dataSource: # data source name
type: # type name in schema (e.g. Query, Mutation, Subscription, or a custom type e.g. User)
field: getUserInfo
# kind: UNIT (default, not required) or PIPELINE (required for pipeline resolvers)
functions: # array of functions if kind === 'PIPELINE'
- # function name
request: # request mapping template name | defaults to `defaultMappingTemplates.request` or {type}.{field}.request.vtl
response: # response mapping template name | defaults to `defaultMappingTemplates.response` or {type}.{field}.response.vtl
# When caching is enaled with `PER_RESOLVER_CACHING`,
# the caching options of the resolver.
# Disabled by default.
# Accepted values:
# - `true`: cache enabled with global `ttl` and default `keys`
# - an object with the following keys:
# - ttl: The ttl of this particular resolver. Optional. Defaults to global ttl
# - keys: The keys to use for the cache. Optionnal. Defaults to a hash of the
# $context.arguments and $context.identity
caching:
keys: # array. A list of VTL variables to use as cache key.
- "$context.identity.sub"
- "$context.arguments.id"
ttl: 1000 # override the ttl for this resolver. (default comes from global config)
# When versioning is enabled with `versioned` on the datasource,
# the datasync options of the resolver.
# Disabled by default.
# Accepted values:
# - `true`: sync enabled with default ConflictDetection VERSION
# - an object with the following keys:
# - conflictDetection: The Conflict Detection strategy to use.
# - functionName: The function name in your serverless.yml. Ignored if lambdaFunctionArn is provided.
# - lambdaFunctionArn: The Arn for the Lambda function to use as the Conflict Handler.
# - conflictHandler: The Conflict Resolution strategy to perform in the event of a conflict.
sync:
conflictDetection: VERSION # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appsync-resolver-syncconfig.html
conflictHandler: OPTIMISTIC_CONCURRENCY # when not using lambda conflict handler choose The Conflict Resolution strategy to perform in the event of a conflict. OPTIMISTIC_CONCURRENCY / AUTOMERGE / LAMBDA
functionName: graphql # The function name in your serverless.yml. Ignored if lambdaFunctionArn is provided.
lambdaFunctionArn: "arn:aws:lambda:{REGION}:{ACCOUNT_ID}:myFunction"
- ${file({fileLocation}.yml)} # link to a file with arrays of mapping templates
functionConfigurationsLocation: # defaults to mappingTemplatesLocation (mapping-templates)
functionConfigurations:
- name: # function name
dataSource: # data source name
request: # request mapping template name | defaults to {name}.request.vtl
response: # reponse mapping template name | defaults to {name}.response.vtl
dataSources:
- type: NONE
name: none
- type: AMAZON_DYNAMODB
name: # data source name
description: # DynamoDB Table Description
config:
tableName: { Ref: MyTable } # Where MyTable is a dynamodb table defined in Resources
serviceRoleArn: { Fn::GetAtt: [AppSyncDynamoDBServiceRole, Arn] } # Where AppSyncDynamoDBServiceRole is an IAM role defined in Resources
iamRoleStatements: # custom IAM Role statements for this DataSource. Ignored if `serviceRoleArn` is present. Auto-generated if both `serviceRoleArn` and `iamRoleStatements` are omitted
- Effect: "Allow"
Action:
- "dynamodb:GetItem"
Resource:
- "arn:aws:dynamodb:{REGION}:{ACCOUNT_ID}:myTable"
- "arn:aws:dynamodb:{REGION}:{ACCOUNT_ID}:myTable/*"
# Versioned DataSource configuration
versioned: false # (default, not required)
# When you enable versioning on a DynamoDB data source, you specify the following fields
# read more at https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appsync-datasource-deltasyncconfig.html
# deltaSyncConfig:
# baseTableTTL: 0 # (default, not required) # The amount of time (in minutes) items should be kept in the base table when deleted. Set to 0 to delete items in the base table immediately
# deltaSyncTableName: { Ref: MyTableDelta } # required # The Delta Sync table name
# deltaSyncTableTTL: 60 # (default, not required) # The amount of time (in minutes) the delta sync table will keep track of changes
region: # Overwrite default region for this data source
- type: RELATIONAL_DATABASE
name: # data source name
description: # data source description
config:
dbClusterIdentifier: { Ref: RDSCluster } # The identifier for RDSCluster. Where RDSCluster is the cluster defined in Resources
awsSecretStoreArn: { Ref: RDSClusterSecret } # The RDSClusterSecret ARN. Where RDSClusterSecret is the cluster secret defined in Resources
serviceRoleArn: { Fn::GetAtt: [RelationalDbServiceRole, Arn] } # Where RelationalDbServiceRole is an IAM role defined in Resources
databaseName: # optional database name
schema: # optional database schema
iamRoleStatements: # custom IAM Role statements for this DataSource. Ignored if `serviceRoleArn` is present. Auto-generated if both `serviceRoleArn` and `iamRoleStatements` are omitted
- Effect: "Allow"
Action:
- "rds-data:DeleteItems"
- "rds-data:ExecuteSql"
- "rds-data:ExecuteStatement"
- "rds-data:GetItems"
- "rds-data:InsertItems"
- "rds-data:UpdateItems"
Resource:
- "arn:aws:rds:{REGION}:{ACCOUNT_ID}:cluster:mydbcluster"
- "arn:aws:rds:{REGION}:{ACCOUNT_ID}:cluster:mydbcluster:*"
- Effect: "Allow"
Action:
- "secretsmanager:GetSecretValue"
Resource:
- "arn:aws:secretsmanager:{REGION}:{ACCOUNT_ID}:secret:mysecret"
- "arn:aws:secretsmanager:{REGION}:{ACCOUNT_ID}:secret:mysecret:*"
region: # Overwrite default region for this data source
- type: AMAZON_ELASTICSEARCH
name: # data source name
description: 'ElasticSearch'
config:
domain: # a reference to a resource of type `AWS::Elasticsearch::Domain`
endpoint: # required if `domain` not provided. Ex: "https://{XXX}.{REGION}.es.amazonaws.com"
serviceRoleArn: { Fn::GetAtt: [AppSyncESServiceRole, Arn] } # Where AppSyncESServiceRole is an IAM role defined in Resources
iamRoleStatements: # custom IAM Role statements for this DataSource. Ignored if `serviceRoleArn` is present. Auto-generated if both `serviceRoleArn` and `iamRoleStatements` are omitted
- Effect: "Allow"
Action:
- "es:ESHttpGet"
Resource:
- "arn:aws:es:{REGION}:{ACCOUNT_ID}:{DOMAIN}"
- type: AWS_LAMBDA
name: # data source name
description: 'Lambda DataSource'
config:
functionName: graphql # The function name in your serverless.yml. Ignored if lambdaFunctionArn is provided.
lambdaFunctionArn: { Fn::GetAtt: [GraphqlLambdaFunction, Arn] } # Where GraphqlLambdaFunction is the lambda function cloudformation resource created by serverless for the serverless function named graphql
serviceRoleArn: { Fn::GetAtt: [AppSyncLambdaServiceRole, Arn] } # Where AppSyncLambdaServiceRole is an IAM role defined in Resources
iamRoleStatements: # custom IAM Role statements for this DataSource. Ignored if `serviceRoleArn` is present. Auto-generated if both `serviceRoleArn` and `iamRoleStatements` are omitted
- Effect: "Allow"
Action:
- "lambda:invokeFunction"
Resource:
- "arn:aws:lambda:{REGION}:{ACCOUNT_ID}:myFunction"
- "arn:aws:lambda:{REGION}:{ACCOUNT_ID}:myFunction:*"
- type: HTTP
name: # data source name
description: 'Http endpoint'
config:
endpoint: # required # "https://{DOMAIN}/{PATH}"
- ${file({dataSources}.yml)} # link to a file with an array or object of datasources
substitutions: # allows to pass variables from here to velocity templates
# ${exampleVar1} will be replaced with given value in all mapping templates
exampleVar1: "${self:service.name}"
exampleVar2: {'Fn::ImportValue': 'Some-external-stuff'}
xrayEnabled: true # Bool, Optional. Enable X-Ray. disabled by default.
wafConfig:
enabled: true
name: AppSyncWaf
defaultAction: Allow # or Block. Defaults to Allow
description: 'My AppSync Waf rules'
rules:
- throttle: 100
- disableIntrospection
- name: UsOnly
action: Block # Allow, Block, or Count
statement:
NotStatement:
Statement:
GeoMatchStatement:
CountryCodes:
- US
tags: # Tags to be added to AppSync
key1: value1
key2: value2
Be sure to replace all variables that have been commented out, or have an empty value.
If you already have an API created in AppSync through the UI or from a different CF stack and want to manage it via Serverless then the plugin can also support that.
There is an optional apiId parameter that you can use to specify the ID of an existing AppSync API:
custom:
appSync:
# ...
apiId: 1234abcd
# ...
Without apiId parameter the plugin will create a different endpoint with the same name alongside the original one.
You can find the apiId value in the AppSync console, just open your existing AppSync API and go to Settings.
In that case, the plugin will not attempt to create a new endpoint for you, instead, it will attach all newly configured resources to the existing endpoint.
The following configuration options are only associated with the creation of a new AppSync endpoint and will be ignored if you provide apiId parameter:
- name
- authenticationType
- userPoolConfig
- openIdConnectConfig
- additionalAuthenticationProviders
- logConfig
- tags
- xrayEnabled
- apiKeys
- wafConfig
So later, if you wanted to change the name of the API, or add some tags, or change the logging configuration, anything from the list above you would have to do that via a different method, for example from the UI.
If the existing API already contains schema and resolvers those will be completely replaced by the new schema and resolvers from the code.
If the existing API already contains data sources, those data sources will remain untouched unless they have the same names as the data sources in the code, in which case they will be replaced with the ones from the code.
Note: You should never set the apiId of an API that was previously deployed with the same serverless stack, otherwise, it would be deleted. That is because the resource would be removed from the stack.
Only use the apiId parameter if you know what you are doing.
If you have multiple APIs and do not want to split this up into another CloudFormation stack, simply change the appSync
configuration property from an object into an array of objects:
custom:
appSync:
- name: private-appsync-endpoint
schema: AppSync/schema.graphql # or something like AppSync/private/schema.graphql
authenticationType: OPENID_CONNECT
openIdConnectConfig:
...
serviceRole: AuthenticatedAppSyncServiceRole
dataSources:
...
mappingTemplatesLocation: ...
mappingTemplates:
...
- name: public-appsync-endpoint
schema: AppSync/schema.graphql # or something like AppSync/public/schema.graphql
authenticationType: API_KEY
serviceRole: PublicAppSyncServiceRole
dataSources:
...
mappingTemplatesLocation: ...
mappingTemplates:
...
Note: CloudFormation stack outputs and logical IDs will be changed from the defaults to api name prefixed. This allows you to differentiate the APIs on your stack if you want to work with multiple APIs.
Amazon supports direct lambda resolvers
With a direct lambda resolver, no VTL mapping template is required for either request or response. This can be an option if you would like to avoid usage of the Apache VTL langauge or require a complex resolver. You can enable direct Lambda resolvers by setting false
as the request
and/or response
value.
Example:
custom:
appsync:
mappingTemplates:
- type: Query
request: false
response: false
dataSource: myLambdaSource
field: getMyData
Furthermore, direct resolution can be enabled separately for the request and response templates.
Amazon supports pipeline resolvers
They allow you to perform more than one mapping template in sequence, so you can do multiple queries to multiple sources. These queries are called function configurations ('AWS::AppSync::FunctionConfiguration') and are children of a resolver.
Here is an example of how to configure a resolver with function configurations. The key here is to provide a 'kind' of 'PIPELINE' to the mapping template of the parent resolver. Then provide the names of the functions in the mappingTemplate to match the names of the functionConfigurations.
custom:
appSync:
mappingTemplates:
- type: Query
field: testPipelineQuery
request: './mapping-templates/before.vtl' # the pipeline's "before" mapping template, defaults to {type}.{field).request.vtl
response: './mapping-templates/after.vtl' # the pipeline's "after" mapping template, defaults to {type}.{field}.response.vtl
kind: PIPELINE
functions:
- authorizeFunction
- fetchDataFunction
functionConfigurations:
- dataSource: graphqlLambda
name: 'authorizeFunction'
request: './mapping-templates/authorize-request.vtl' # defaults to {name}.request.vtl
response: './mapping-templates/common-response.vtl' # defaults to {name}.response.vtl
- dataSource: dataTable
name: 'fetchDataFunction'
request: './mapping-templates/fetchData.vtl' # defaults to {name}.request.vtl
response: './mapping-templates/common-response.vtl' # defaults to {name}.response.vtl
Since v1.5.0, api keys management is supported. You can pass one or more api keys configuration as an array in the appSync.apiKeys
property.
The keys can either be a string (name of the key with defaults) or an object of the following shape:
property | default | description |
---|---|---|
name | auto-generated | Name of the key. This is used under the hood to differentiate keys in the deployment process. Names are used in the Cfn resource name. Please, keep them short and without spaces or special characters to avoid issues. Key names are case sensitive. |
description | name of the key | A short description for that key |
expiresAfter | 365d | Expiration time for the key. Can be expressed in hours or in "human" format (As in momentjs add). eg: 24 , 30d , 1M , 2w , 1y Min: 1d, max: 1y |
expiresAt | one year from now | A specific expiration date in ISO 8601 format. Or as a unix timestamp |
apiKeyId | undefined |
the id if the api to update. Useful for when an api key has been created manually in the AWS console. |
If both expiresAfter
and expiresAt
are specified, expiresAfter
takes precedence.
When naming keys, you need to be aware that changing the value will require the replacement of the api key.
Unnamed keys are named automatically sequentially Key1, Key2, Key3 and so forth.
๐ก If you have already deployed and an api key was previously auto-generated for you (either in version <1.5.0 or if you deployed without specifying the apiKeys
property), you can add it to your yml template by naming it Default
(case sensitive!!). Starting from there, you can add additional API keys.
๐ก If you want to revoke a key, delete it, or rename it.
๐ก If a key expires, or you have manually deleted it from the cosole, subsequent deployments will fail (after 60 days in the case it expires). You can fix that by simply removing the key from your yml file, or by renaming it (in which case, a new key will be generated).
Example:
apiKeys:
- name: Default # default API key. Use this name if you already have an auto-generated API key
description: Default api key
expires: 1y # 1 year timelife
- Mark # inline named key, with defaults (1 year duration)
- name: John
description: John api key
expires: 30d
- name: Jane
expires: 2d
- description: Unnamed key # first unnamed key (Key1)
- expires: 30d # second unnamed key (Key2)
- name: ThrottledAPIKey
wafRules:
- throttle # throttle this API key to 100 requests per 5 min
- name: GeoApiKey
description: Us Only
# Disallow this Api key outsite the US
wafRules:
- action: Block
name: UsOnly
statement:
NotStatement:
Statement:
GeoMatchStatement:
CountryCodes:
- US
๐ก Finally, if you dont't want serverless to handle keys for you, just pass an empty array:
# Handle keys manually in the aws console.
apiKeys: []
AppSync supports WAF. WAF is an Application Firewall that helps you protect your API against common web exploits.
This plugin comes with some handy pre-defined rules that you can enable in just a few lines of code.
Throttling will disallow requests coming from the same ip address when a limit is reached within a 5-minutes period.
There are several ways to enable it. Here are some examples:
wafConfig:
enabled: true
rules:
- throttle # limit to 100 requests per 5 minutes period
- throttle: 200 # limit to 200 requests per 5 minutes period
- throttle:
limit: 200
priority: 10
aggregateKeyType: FORWARDED_IP
forwardedIPConfig:
headerName: 'X-Forwarded-For'
fallbackBehavior: 'MATCH'
Sometimes, you want to disable introspection to disallow untrusted consumers to discover the structure of your API.
wafConfig:
enabled: true
rules:
- disableIntrospection # disables introspection for everyone
You can use AWS Managed Rules using this configuration:
wafConfig:
enabled: true
rules:
- name: MyRule1 # this is your rule name
overrideAction:
none: {}
statement:
managedRuleGroupStatement:
vendorName: AWS
name: AWSManagedRulesCommonRuleSet # this is the name of the managed rule
Managed rules require overrideAction
set and action
not set.
For more information view the AWS Managed Rule Groups List.
In some cases, you might want to enable a rule only for a given API key only. You can specify wafRules
under the apiKeys
configuration. The rules will apply only to the api key under which the rule is set.
apiKeys:
- name: MyApiKey
expiresAfter: 365d
wafRules:
- throttle # throttles this API key
- disableIntrospection # disables introspection for this API key
Adding a rule to an API key without any statement will add a "match-all" rule for that key. This is usefull for example to exclude api keys from high-level rules. In that case, you need to make sure to attribute a higher priority to that rule.
Example:
- Block all requests by default
- Add a rule to allow US requests
- Except for the
WorldWideApiKey
key, that should have worldwide access.
wafConfig:
enabled: true
defaultAction: Block # Block all by default
rules:
# allow US requests
- action: Allow
name: UsOnly
priority: 5
statement:
geoMatchStatement:
countryCodes:
- US
apiKeys:
- name: Key1 # no rule is set, the top-level rule applies (Us only)
- name: Key1 # no rule is set, the top-level rule applies (Us only)
- name: WorldWideApiKey
wafRules:
- name: WorldWideApiKeyRule
action: Allow
priority: 1 # Make sure the priority is higher (lower number) to evaluate it first
The priorities don't need to be consecutive, but they must all be different.
Setting a priority to the rules is not required, but recommended. If you don't set priority, it will be automatically attributed (sequentially) according to the following rules:
First the global rules (under wafConfig.rules
), in the order that they are defined. Then, the api key rules, in order of api key definitions, then rule definition.
Auto-generated priorities start at 100. This gives you some room (0-99) to add other rules that should get a higher priority, if you need to.
For more info about how rules are executed, pease refer to the documentation
Example:
wafConfig:
enabled: true
rules:
- name: Rule1
# (no-set) Priority = 100
- name: Rule2
priority: 5 # Priority = 5
- name: Rule3
# (no-set) Priority = 101
apiKeys:
- name: Key1
wafRules:
- name: Rule4
# (no-set) Priority = 102
- name: Rule5
# (no-set) Priority = 103
- name: Key
wafRules:
- name: Rule6
priority: 1 # Priority = 1
- name: Rule7
# (no-set) Priority = 104
You can also specify custom rules. For more info on how to define a rule, see the Cfn documentation
Example:
wafConfig:
enabled: true
defaultAction: Block
rules:
# Only allow US users
- action: Allow
name: UsOnly
statement:
geoMatchStatement:
countryCodes:
- US
In some cases you want to enable usage of old-style comments (#) in appSync. setting the allowHashDescription
setting
to true, will enable this.
Example:
custom:
appSync:
name: # defaults to api
allowHashDescription: true
# ... other settings
GraphQlApiId
and GraphQlApiUrl
are exported to allow cross-stack resource reference using Fn::ImportValue
.
Output Exports are named with a ${AWS::StackName}-
prefix to the logical IDs. For example, ${AWS::StackName}-GraphQlApiId
.
Note: CloudFormation stack outputs and logical IDs will be changed from the defaults to api name prefixed. This allows you to differentiate the APIs on your stack if you want to work with multiple APIs.
This command will deploy all AppSync resources in the same CloudFormation template used by the other serverless resources.
- Providing the
--conceal
option will conceal the API keys from the output when the authentication type ofAPI_KEY
is used.
Validates your GraphQL Schema(s) without deploying.
This command will start a local graphql-playground server which is connected to your deployed AppSync endpoint (in the cloud). The required options for the command are different depending on your AppSync authenticationType.
For API_KEY, either the GraphQLApiKeyDefault output or the --apiKey option is required
For AMAZON_COGNITO_USER_POOLS, the -u/--username and -p/--password arguments are required. The cognito user pool client id can be provided with the --clientId option or directly in the yaml file (custom.appSync.userPoolConfig.playgroundClientId
)
For OPENID_CONNECT, the --jwtToken option is required.
The AWS_IAM authenticationType is not currently supported.
There are 2 ways to work with offline development for serverless appsync.
serverless-appsync-simulator is a wrapper of aws's amplify-cli for serverless and this plugin. Both are actively maintained.
serverless-appsync-offline is based on AppSync Emulator. Both these packages are currently unmaintained.
You can use serverless-plugin-split-stacks to migrate AppSync resources in nested stacks in order to work around the 200 500 resource limit.
- Install serverless-plugin-split-stacks
yarn add --dev serverless-plugin-split-stacks
or
npm install --save-dev serverless-plugin-split-stacks
-
Follow the
serverless-plugin-split-stacks
installation instructions -
Place
serverless-plugin-split-stacks
afterserverless-appsync-plugin
plugins:
- serverless-appsync-plugin
- serverless-plugin-split-stacks
- Create
stacks-map.js
in the root folder
module.exports = {
'AWS::AppSync::ApiKey': { destination: 'AppSync', allowSuffix: true },
'AWS::AppSync::DataSource': { destination: 'AppSync', allowSuffix: true },
'AWS::AppSync::FunctionConfiguration': { destination: 'AppSync', allowSuffix: true },
'AWS::AppSync::GraphQLApi': { destination: 'AppSync', allowSuffix: true },
'AWS::AppSync::GraphQLSchema': { destination: 'AppSync', allowSuffix: true },
'AWS::AppSync::Resolver': { destination: 'AppSync', allowSuffix: true }
}
- Enjoy ๐ป
If you have any questions, issue, feature request, please feel free to open an issue.
You are also very welcome to open a PR and we will gladely review it.
- AppSync Utils: A collection of snippets that make AppSync development easier
- AppSync Resolver Autocomplete: Autocomplete support for VTL template files.
Thanks goes to these wonderful people ๐
This project follows the all-contributors specification. Contributions of any kind welcome!