Serverless Appsync Plugin

Serverless Plugin to deploy AppSync GraphQL API

View on Github

Tests --- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --- All Contributors

Deploy AppSync API's in minutes using this Serverless plugin.

Getting Started

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!

Minimum requirements

Installation & Configuration

Install the plugin via Yarn

yarn add serverless-appsync-plugin

or via NPM

npm install serverless-appsync-plugin

Configuring the 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
    domain: # custom domain. See the Custom Domains section below
      name: api.example.com
      certificateArn: arn:aws:acm:us-east-1123456789:certificate/1c4e4c36-9a63-4685-94b7-e873402baca3
    # 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 or AWS_LAMBDA
    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 AWS_LAMBDA
    lambdaAuthorizerConfig:
      functionName: # The function name in your serverless.yml. Ignored if lambdaFunctionArn is provided.
      functionAlias: # optional, used with functionName
      lambdaFunctionArn: # required if functionName is not defined
      identityValidationExpression: # optional
      authorizerResultTtlInSeconds: # 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
      - authenticationType: AWS_LAMBDA
        lambdaAuthorizerConfig:
          functionName: # The function name in your serverless.yml. Ignored if lambdaFunctionArn is provided.
          functionAlias: # optional, used with functionName
          lambdaFunctionArn: # required if functionName is not defined
          identityValidationExpression: # optional
          authorizerResultTtlInSeconds: # 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 # Bool, Optional. Enable ExcludeVerboseContent. disabled by default(= Include verbose content in logs is default).
    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
        maxBatchSize: # maximum number of requests for BatchInvoke operations
        # 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
        maxBatchSize: # maximum number of requests for BatchInvoke operations
    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.

Working with existing APIs

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.

Multiple APIs

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.

Direct Lambda Resolvers

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.

Pipeline Resolvers

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

Managing API keys

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.

:warning: Be careful when removing unnamed keys!!!. For exemple, if you have 3 unnamed keys and you remove the second one in your list, Key3 will become Key2. As a result, it is former Key3 that will be removed. To workaround that, you could specify their auto-generated names before removing any unnamed keys (Key1, Key2 and Key3 in our example. Then remove Key2). As a rule of thumb, all keys should be named to avoid issues.

:bulb: 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.

:bulb: If you want to revoke a key, delete it, or rename it.

:bulb: 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

:bulb: 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: []

WAF Web ACL

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

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'

Disable Introspection

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

Using AWS Managed Rules

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.

Per Api Key rules

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

About priority

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

Advanced usage

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

Schema Comments

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

Stack Outputs & Exports

GraphQlApiIdand 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.

Cli Usage

serverless deploy

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 of API_KEY is used.

validate-schema

Validates your GraphQL Schema(s) without deploying.

serverless graphql-playground

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.

Offline support

There are 2 ways to work with offline development for serverless appsync.

serverless-appsync-simulator

serverless-appsync-simulator is a wrapper of aws's amplify-cli for serverless and this plugin. Both are actively maintained.

serverless-appsync-simulator (deprecated/unmaintained)

serverless-appsync-offline is based on AppSync Emulator. Both these packages are currently unmaintained.

Split Stacks Plugin

You can use serverless-plugin-split-stacks to migrate AppSync resources in nested stacks in order to work around the 200 500 resource limit.

  1. Install serverless-plugin-split-stacks
yarn add --dev serverless-plugin-split-stacks
 or
npm install --save-dev serverless-plugin-split-stacks
  1. Follow the serverless-plugin-split-stacks installation instructions

  2. Place serverless-plugin-split-stacks after serverless-appsync-plugin

plugins:
  - serverless-appsync-plugin
  - serverless-plugin-split-stacks
  1. 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 },
};
  1. Enjoy :beers:

Custom domains

AppSync supports custom domains.

You need to generate and provide a valid certificate ARN for the domain (Note: Taht certificate ust be in the us-east-1 region, no matter where you deploy your AppSync API).

example:

custom:
  appSync:
    domain:
      name: api.example.com
      certificateArn: arn:aws:acm:us-east-1123456789:certificate/1c4e4c36-9a63-4685-94b7-e873402baca3
      route53:
        hostedZoneId: ABCDEFGHIJKLMN # optional. If not provided, the plugin will try to find the best match from the domain name
        hostedZoneName: example.com # optional. If not provided, the plugin will try to find the best match from the domain name

Domains are managed trhough the CLI commands. This allows a better flexibility and control over your domains and APIs.

e.g. You can swap one API with another on a domain easily (for blue/green deployments)

Note: This is currently only supported for one API. If you have multiple APIs in your stack, the first one will be used.

Create/delete a domain

Before associating a domain to an API, you must first create it. You can do so using the following command.

sls appsync-domain create

And to delete it:

sls appsync-domain delete

If an API is associated to it, you will need to disassociate it first.

Associate/disassociate an API to the domain

sls appsync-domain assoc --stage dev

You can associate an API to a domain that already has another API attached to it. The old API will be replaced by the new one.

To disassociate an API from the domain, use

sls appsync-domain disassoc --stage dev

Create/delete a route53 record

If you use Route53 for your hosted zone, you can also manage the CNAME record for your custom domain easily using the following commands.

sls appsync-domain create-record
sls appsync-domain delete-record

Contributing

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.

Resources

VSCode extensions

Video tutorials

Blog tutorial

Contributors ✨

Thanks goes to these wonderful people :clap:


Benoît Bouré

🚧 💻

Siddharth Gupta

💻

Nik Graf

💻

Charles Killer

💻

jpstrikesback

💻

ZY

💻

Francis Upton IV

💻

Ilya Shmygol

💻

Maddi Joyce

💻

sebflipper

💻

Erez Rokah

💻

Akshay Kadam (A2K)

💻

Anton

💻

Burkhard Reffeling

💻

Dean Koštomaj

💻

Vincent Lesierse

💻

lulzneko

💻

thomas michael wallace

💻

Adnene KHALFA

💻

Alex Rozn

💻

Eric Chan

💻

Joseph

💻

Miha Eržen

💻

Mike Fogel

💻

Philipp Muens

💻

Toxuin

💻

Scott Rippee

💻

Yi Ai

💻

markvp

💻

Alex

💻

Alex Jurkiewicz

💻

Anas Qaderi

💻

Andreas Heissenberger

💻

mickael

💻

Brian Torres-Gil

💻

Cameron Childress

💻

Chris Chiang

💻

Esref Durna

💻

Hari

💻

Ivan Barlog

💻

John Veldboom

💻

Luca Bigon

💻

Lucas

💻

Mark Pollmann

💻

Maurice Williams

💻

Mike Chen

💻

asnaseer-resilient

💻

Neal Clark

💻

Nicky Moelholm

💻

Patrick Arminio

💻

Paul Li

💻

James Lal

💻

Sam Gilman

💻

Stefan Ceriu

💻

tsmith

💻

veloware

💻

Vladimir Lebedev

💻

Ryan Jones

💻

Vicary A.

💻

Brian Santarelli

🤔

Emilio Font

💻

Andriy Nastyn

💻 📖

MarcusJones

📖

h-kishi

💻

Dillon Browne

💻

Piotr Grzesik

💻

Aleksa Cukovic

💻

Scott Davey

💻

Mateus Holzschuh

💻

Nik Van Looy

💻

This project follows the all-contributors specification. Contributions of any kind welcome!