Simple AWS GraphQL API Tutorial.

Easily, develop and deploy a secured GraphQL API using SLS, Appsync, Amplify & Cognito.

We will use the Serverless Framework, which will abstract away for us, underlying technomplexity, including, AWS Appsync, AWS Cognito, AWS CloudFormation, DynamoDB, etc,.. and a bit of AWS Amplify, as we will try to avoid using it in-place of SLS to keep things simple and manageable!

Update: part 2 (UI) of this series is published now here.

Prerequisites:

Serverless, now! The Serverless Framework is an open-source, awesome IaC scaffolding tool that you need to start using right away, period!
It essentially abstracts the complexity of Cloudformation templates, but it also has a ton of plugins, adding a bunch of features and functionalities!
This article is not about serverless, but if you don’t have it, simply run npm i -g serverless and you live longer!

Let’s get to work!

0. If you want to jump to deployment (Step 7), simply run:

sls create --template-url https://github.com/mim-Armand/serverless-appsync-template
# then run:
npm i
# Then goto 7...
# but if you'd like to learn, please start from Step 1.

1. Creating the project shell structure:

sls create -t aws-nodejs -p testGraphqlApi

2. Installing the Serverless Appsync plugin:

sls plugin install -n serverless-appsync-plugin

3. let’s define our GraphQL schema,

Create a file in the root of the project called schema.graphql. This file will define the structure of our GraphQL APIs, copy and paste the following code in it:

type UserType {
UserId: ID!
firstName: String
lastName: String
likesMe: Boolean
}

type Query @auth(rules: [{allow: owner}]){
getUser(UserId: ID!): UserType
}

type Mutation {
saveUser(firstName: String!, lastName: String!, likesMe: Boolean): UserType
deleteUser(UserId: ID!): UserType
}

type Schema {
query: Query
mutation: Mutation
}

4. AWS Amplify Magic Sauce!

@auth(rules:[{allow: owner}]) in the previous step!
This is what we borrowed from AWS Amplify (one of its main value-propositions). it essentially abstracts away the complexity of defining common use-cases (like protecting a data-type or a method) in Vanilla GraphQL Schema Definition Language and mapping templates. Amplify, has a library that reads these annotations and converts them to fully described SDLs ( in Amplify, they’ll be injected directly into cloudformation templates, here, however, the Appsync plugin will translate those to be used with sls, which is much better!)
There are more directives, like @model, @versioned, or more complicated @auth definitions and more, so definitely check the Amplify documentation to learn about them. some of them, however, are not as compatible with the Server-less architectural concepts ( or even very well designed at all ) and may not have been implemented in the AppSync plugin yet (or forever), but, I’d suggest staying away from some of them anyway, as they may cause more headaches in the future by tying resources to code without explicit IaC definitions.

5. GraphQL request/response resolution mapping templates:

To keep things simple and maintainable, we will keep the logic about how GraphQL deals with each request type separately:
create the following files in a directory called mapping-templates:

$util.toJson($ctx.result)
{
"operation": "DeleteItem",
"key": {
"UserId": $util.dynamodb.toDynamoDBJson($ctx.identity.sub)
}
}
  • saveUser-req.vtl

6. Now let’s define our Infra:

This is the main reason we used Serverless framework, as, currently, it provides the best and cleanest option ( comparing to alternatives like Terraform and Amplify ) to handle multi-cloud Infra as Code. In the case of AWS, it abstracts the difficult to read/manage cloud-formation templates to beautiful and simple YAML files.
Edit the serverless.yml in the root of the project so it looks like below:

service: testGraphqlApi

provider:
name: aws
runtime: nodejs8.10
apiname: ${opt:apiname, 'testGraphqlApi-dev'}

plugins:
- serverless-appsync-plugin

# This is our Appsync infrastructure, consumed by the serverless-appsync-plugin:
custom:
accountId: { Ref: AWS::AccountId }
appSync:
name: ${self:provider.apiname}
region: ${self:provider.region}
authenticationType: AMAZON_COGNITO_USER_POOLS
userPoolConfig:
awsRegion: { Ref: AWS::Region }
defaultAction: ALLOW
userPoolId: { Ref: UserPool }
serviceRole: "AppSyncServiceRole"
dataSources:
- type: AMAZON_DYNAMODB
name: testGraphqlApiTableDS
config:
tableName: { Ref: testGraphqlApiTable }
serviceRoleArn: { Fn::GetAtt: [ DynamoDBRole, Arn ] }
mappingTemplates:
- dataSource: testGraphqlApiTableDS
type: Query
field: getUser
request: "getuser-request.vtl"
response: "common-response.vtl"
- dataSource: testGraphqlApiTableDS
type: Mutation
field: saveUser
request: "saveuser-request.vtl"
response: "common-response.vtl"
- dataSource: testGraphqlApiTableDS
type: Mutation
field: deleteUser
request: "deleteuser-request.vtl"
response: "common-response.vtl"

# These are our normal Serverless Framework IaC:
resources:
Resources:
# Amazon Cognito user pool
UserPool:
Type: "AWS::Cognito::UserPool"
Properties:
UserPoolName: ${self:provider.apiname}-user-pool

# An app client for the Amazon Cognito user pool
UserPoolClient:
Type: "AWS::Cognito::UserPoolClient"
Properties:
ClientName: ${self:provider.apiname}-appsync-client
GenerateSecret: false
UserPoolId: { Ref: UserPool }

# DynamoDB Table
testGraphqlApiTable:
Type: "AWS::DynamoDB::Table"
Properties:
TableName: ${self:provider.apiname}-kugelblitz-table
AttributeDefinitions:
- AttributeName: "UserId"
AttributeType: "S"
KeySchema:
- AttributeName: "UserId"
KeyType: "HASH"
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1

# IAM Policy to access Dynamo by the service
AppSyncDynamoDBPolicy:
Type: "AWS::IAM::ManagedPolicy"
Properties:
Path: /appsync/
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:DeleteItem
- dynamodb:UpdateItem
- dynamodb:Query
- dynamodb:Scan
Resource:
- { Fn::Join: [ '', [ { Fn::GetAtt: [ testGraphqlApiTable, Arn ] }, '/*' ] ] }
- { Fn::GetAtt: [ testGraphqlApiTable, Arn ] }

# IAM Role for implementing the AppSync / DynamoDB policy
DynamoDBRole:
Type: "AWS::IAM::Role"
DependsOn:
- AppSyncDynamoDBPolicy
Properties:
RoleName: ${self:provider.apiname}-appsync-dynamodb-role
ManagedPolicyArns:
- Ref: AppSyncDynamoDBPolicy
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- sts:AssumeRole
Principal:
Service:
- appsync.amazonaws.com

7. That was it! Just Deploy it!

Don’t worry, you can un-deploy it just as easily!

sls deploy

Try out the API:

Before sls removeing your new shiny GraphQL API, you can try it out, even without an actual client. To do this you’d need:

1. Create a new user in your user-pool:

Since we used AMAZON_COGNITO_USER_POOLS for our API security, anybody who wants to call this API needs to be autheticated.
To do this, log in to AWS console and go to your Cognito user pools and select the correct pool ( testGraphslAPI-dev-user-pool )

2. Query the API:

In NodeJS, usually, we’d use GraphiQL to provide a GUI to test and diagnose GraphQL APIs during development ( or sometimes even after that, exposing it as an API specification/documentation tool! ).
In AWS Appsync, however, you can simply query your API on the console using the built-in tool. it also provides documentation for your API.

mutation PutUser{
saveUser(
firstName: "mim"
lastName: "Armand"
likesMe: false
){
UserId
}
}
UserId
}
}
query GetUserById{
getUser(
UserId: "PUT-THE-ID-HERE-PLEASE!"
){
firstName
lastName
likesMe
}
}
mutation DeleteUserById{
deleteUser(
UserId: "eecded7f-c064-4e31-83b8-bd64b73c35d4"
){
firstName
lastName
}
}

Conclusion:

We demonstrated how easy it is to define and deploy a protected GraphQL endpoint with a user-base and everything else needed, The cool part is that, with this base we can create a lot more stuff, no matter how complicated they may be, we just add types and mapping templates and other security checks using Amplify annotations.
And we are not even limited to only AppSync and GraphQL! as I do in most of my projects, we can combine REST APIs and GraphQL, easily, just add other resources as you usually do use serverless, business as usual!

Part 2, about UI components is now available here!

Also, please don’t hesitate to connect with me on LinkedIn and/or Twitter, or leave a comment below, I’d love to have your feedback!

Sr Solutions Architect / Technology evangelist and Consultant / Teacher of the Full-Stack Web development courses at Washington University in st. Louis