AWS AppSync is a serverless GraphQL backend-as-a-service. Most of the articles online show its usage by going on AWS console and configuring via Web UI which is not code-centric approach. I would like to show more code-driven approach by employing a technique called Infrastructure as Code using AWS CloudFormation. CloudFormation allows you to specify all AWS resources in the form of code and automate your deployments via command line. In this way all infrastructure resources needed by your application become part of git repository - version controlled. You can reproduce whole stack (set of related resources) in the cloud with a single command instead of repeating steps manually via GUI. Once the initial stack is created, you can easily deploy changes in your infrastructure by modifying the template file and then executing commands via the command line. This approach gives you full direct control via code and it’s very flexible to integrate with the rest of the AWS ecosystem.
I will show step by step how to create and deploy production grade AppSync GraphQL API using DynamoDB all from the comfort of your editor and terminal.
Create a sample schema having one query to get contacts and one mutation to add a new contact and save the file as schema.graphql in the main project directory.
type Contact {
username: String!
fullName: String!
email: String!
phone: String
}
input ContactInput {
fullName: String!
email: String!
phone: String
}
type Query {
getContacts: [Contact!]!
}
type Mutation {
addContact(username: String! contact: ContactInput!): Contact
}
type Schema {
query: Query
mutation: Mutation
}
Resolver mapping template tells AppSync how to translate an incoming GraphQL request into instructions for your backend data source, and how to translate the response from that data source back into a GraphQL response. Create separate request and response mapping templates into different files for each resolver.
## request mapping template for addContact mutation which maps to PutItem request for DynamoDB.
{
"version": "2017-02-28",
"operation": "PutItem",
"key": {
"username": $util.dynamodb.toDynamoDBJson($ctx.args.username)
},
"attributeValues": $util.dynamodb.toMapValuesJson($ctx.args.contact)
}
## response mapping template for addContact mutation which returns newly added contact object.
$util.toJson($ctx.result)
## request mapping template for getContacts query which maps to Scan request for DynamoDB.
{
"version": "2017-02-28",
"operation": "Scan"
}
## response mapping template for getContacts query which returns all contacts.
$util.toJson($ctx.result.items)
Here is CloudFormation template in YAML format which is more readable than its JSON counterpart. It specifies details about AppSync API, API Key, GraphQL Schema, DynamoDB Table, Data Sources, GraphQL Resolvers & giving access to DynamoDB Table using IAM Role.
AWSTemplateFormatVersion: '2010-09-09'
Description: Create AppSync GraphQL API using DynamoDB
Parameters:
ApiName:
Type: String
Description: Name of the API - used to generate unique names for resources
MinLength: 3
MaxLength: 25
AllowedPattern: '^[a-zA-Z][a-zA-Z0-9\-]*$'
Resources:
GraphQLApi:
Type: AWS::AppSync::GraphQLApi
Properties:
Name: !Sub ${ApiName}-graphql-api
AuthenticationType: API_KEY
ApiKey:
Type: AWS::AppSync::ApiKey
Properties:
ApiId: !GetAtt GraphQLApi.ApiId
GraphQLSchema:
Type: AWS::AppSync::GraphQLSchema
Properties:
ApiId: !GetAtt GraphQLApi.ApiId
# path to graphql schema file
DefinitionS3Location: schema.graphql
ContactsTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: !Sub ${ApiName}-contacts
KeySchema:
- AttributeName: username
KeyType: HASH
AttributeDefinitions:
- AttributeName: username
AttributeType: S
BillingMode: PAY_PER_REQUEST
ApiRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- appsync.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: !Sub ${ApiName}-dynamo-access
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:DeleteItem
- dynamodb:UpdateItem
- dynamodb:Query
- dynamodb:Scan
- dynamodb:BatchGetItem
- dynamodb:BatchWriteItem
Resource: !GetAtt ContactsTable.Arn
ContactsTableDataSource:
Type: AWS::AppSync::DataSource
Properties:
Name: ContactsTableDataSource
Type: AMAZON_DYNAMODB
ServiceRoleArn: !GetAtt ApiRole.Arn
DynamoDBConfig:
AwsRegion: !Ref AWS::Region
TableName: !Ref ContactsTable
ApiId: !GetAtt GraphQLApi.ApiId
QueryGetContactsResolver:
Type: AWS::AppSync::Resolver
Properties:
TypeName: Query
FieldName: getContacts
DataSourceName: !GetAtt ContactsTableDataSource.Name
# path to request mapping template file for getContacts
RequestMappingTemplateS3Location: mapping-templates/getContacts.request.vm
# path to response mapping template file for getContacts
ResponseMappingTemplateS3Location: mapping-templates/getContacts.response.vm
ApiId: !GetAtt GraphQLApi.ApiId
MutationAddContactResolver:
Type: AWS::AppSync::Resolver
Properties:
TypeName: Mutation
FieldName: addContact
DataSourceName: !GetAtt ContactsTableDataSource.Name
# path to request mapping template file for addContact
RequestMappingTemplateS3Location: mapping-templates/addContact.request.vm
# path to response mapping template file for addContact
ResponseMappingTemplateS3Location: mapping-templates/addContact.response.vm
ApiId: !GetAtt GraphQLApi.ApiId
Outputs:
GraphQLApiEndpoint:
Description: GraphQL API URL
Value: !GetAtt GraphQLApi.GraphQLUrl
ApiKey:
Description: API Key
Value: !GetAtt ApiKey.ApiKey
First, install AWS CLI and configure it by specifying account’s security credentials and region information. Provide the profile option in all commands if you want to use a particular profile configuration otherwise, it will use the default profile.
Create an S3 bucket for uploading code files.
aws s3 mb s3://contacts-dev-codes --profile faisal
Execute package command to upload local artifacts referred in the given template to the specified S3 bucket. This will generate a new template which refers to S3 files instead of local files.
aws cloudformation package --s3-bucket contacts-dev-codes --template-file template.yml --output-template-file packaged-template.yml --profile faisal
Execute deploy command with the newly generated template to create infrastructure resources. Also, provide stack name and parameters required in the template.
aws cloudformation deploy --stack-name contacts-dev --template-file packaged-template.yml --capabilities CAPABILITY_IAM --parameter-overrides ApiName=contacts-dev --profile faisal
Now you have completely functional AppSync GraphQL API with DynamoDB in AWS Cloud. You can use package and deploy commands repeatedly whenever you make changes in the template, it will automatically create change set and execute it. If you are using Node.js you can add these commands as NPM scripts to package.json of a project for convenience.
I have created GitHub repository which contains all boilerplate code with complete setup required to kickstart AppSync project.