AWS AppSync GraphQL API using CloudFormation

May 30th, 2018 676 Words

Amazon recently announced CloudFormation support for AppSync and all its features. Together with the Serverless Application Model it’s now dead simple to deploy a GraphQL API and custom Lambda resolvers without using the API at all. The GraphQL RSS Proxy example project is a serverless GraphQL API using AppSync, with an AWS Lambda function as a custom Query Resolver writting in Go.

Update: If you enjoy GraphQL and AppSync, you should see the updated guide and example project about using appsync-resolvers, CloudFormation and SAM. The setup can be cleaned up a little bit more and uses a generic resolver handling.

Update: Read how you can add a custom domain for your GraphQL API with AppSync, CloudFront, Route53, and CloudFormation.

Introduction

Some weeks ago, I published an example project to deploy a GraphQL API using AWS AppSync. Back in the days, Amazon did not support AppSync in CloudFormation, this luckily has changed now. I was able to remove large parts of the Makefile and the flow to deploy the projects is now even more simple.

Dependencies

To use the Serverless Application Model, you will need the awscli. On a macOS system, just install it with homebrew:

$ > brew install awscli

Install Go packages

To build the Go function, you will need two dependencies. Use dep to install them.

# make install

$ > dep ensure

Configure Environment

For using the awscli, you must have correct environment settings with an access key and secret.

Deploy

Thanks to the Serverless Application Model, deploying your AWS Lambda function and all CloudFormation resources is dead simple these days:

# Create S3 Bucket
$ > make configure

# Build the binary for Lambda
$ > make build-lambda

# Upload data to S3 Bucket
$ > make package

# Deploy CloudFormation Stack
$ > make deploy

# Get GraphQL information
$ > make outputs

[
  {
    "OutputKey": "APIKey",
    "OutputValue": "da2-nuyzhanm5bcptga6yilkj7zluy",
    "Description": "API Key"
  },
  {
    "OutputKey": "GraphQL",
    "OutputValue": "https://5qemu9oyli88sho8tl5rh0ejpil.appsync-api.eu-west-1.amazonaws.com/graphql",
    "Description": "GraphQL URL"
  }
]

Use the GraphQL API

You can use curl to test your API or use a GraphQL UI. Just make sure to configure the needed authentication header with your API Key and use the correct URL:

$ > curl \
    -XPOST https://5qemu9oyli88sho8tl5rh0ejpil.appsync-api.us-east-1.amazonaws.com/graphql \
    -H "Content-Type:application/graphql" \
    -H "x-api-key:da2-nuyzhanm5bcptga6yilkj7zluy" \
    -d '{ "query": "query { feed { title url } }" }' | jq

{
  "data": {
    "feed": [
      {
        "title": "Deploy Golang Lambda with AWS Serverless Application Model",
        "url": "https://sbstjn.com/golang-lambda-with-aws-sam-serverless-application-model.html"
      },
      {
        "title": "68% Mechanical Keyboard with 68Keys.io",
        "url": "https://sbstjn.com/build-your-own-mechanical-keyboard.html"
      }

      [...]
  }
}

That’s awesome, right? No third-party frameworks, no large amount of dependencies or needs to bootstrap the project. Pure vendor tools, of course vendor-locked, but who cares?

AppSync with CloudFormation

And the best part, everything is configured using CloudFormation in the template.yml resource file:

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Resources:
  Lambda:
    Type: AWS::Serverless::Function
    Properties:
      Handler: dist/handler_linux
      Runtime: go1.x
      Tracing: Active
      Environment:
        Variables:
          URL: https://sbstjn.com/feed.xml

  Role:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: appsync.amazonaws.com
            Action:
              - sts:AssumeRole
      Policies:
        - PolicyName: allow-access-to-lambda-from-appsync
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: lambda:invokeFunction
                Resource:
                  - !GetAtt [Lambda, Arn]
                  - !Join ["", [!GetAtt [Lambda, Arn], ":*"]]

  AppSyncAPI:
    Type: AWS::AppSync::GraphQLApi
    Properties:
      Name: !Join [-, [!Ref ParamProjectName, !Ref ParamENV]]
      AuthenticationType: API_KEY

  AppSyncSchema:
    Type: AWS::AppSync::GraphQLSchema
    Properties:
      ApiId: !GetAtt [AppSyncAPI, ApiId]
      Definition: !Ref ParamSchema

  AppSyncDataSource:
    Type: AWS::AppSync::DataSource
    Properties:
      ApiId: !GetAtt [AppSyncAPI, ApiId]
      Name: source
      Type: AWS_LAMBDA
      LambdaConfig:
        LambdaFunctionArn: !GetAtt [Lambda, Arn]
      ServiceRoleArn: !GetAtt [Role, Arn]

  AppSyncResolver:
    Type: AWS::AppSync::Resolver
    Properties:
      ApiId: !GetAtt [AppSyncAPI, ApiId]
      TypeName: Query
      FieldName: feed
      DataSourceName: !GetAtt [AppSyncDataSource, Name]
      RequestMappingTemplate: '{ "version" : "2017-02-28", "operation": "Invoke", "payload": $util.toJson($context.arguments) }'
      ResponseMappingTemplate: "$util.toJson($context.result)"

  AppSyncAPIKey:
    Type: AWS::AppSync::ApiKey
    Properties:
      ApiId: !GetAtt [AppSyncAPI, ApiId]
      Expires: !Ref ParamKeyExpiration

Parameters:
  ParamProjectName:
    Type: String
  ParamSchema:
    Type: String
  ParamENV:
    Type: String
  ParamKeyExpiration:
    Type: Number

Outputs:
  APIKey:
    Description: API Key
    Value: !GetAtt [AppSyncAPIKey, ApiKey]

  GraphQL:
    Description: GraphQL URL
    Value: !GetAtt [AppSyncAPI, GraphQLUrl]

What’s your setup to deploy and maintain a GraphQL API using AWS AppSync? Let me know on Twitter what you this about this approach and if you know of any way to already do this with CloudFormation!


View on GitHub Source code is published using the MIT License.