Serverless AppSync Component
The AppSync Serverless Component allows you to easily and quickly deploy GraphQL APIs on AWS, and integrate them with AWS Lambda, DynamoDB & others. It supports all AWS AppSync features, while offering sane defaults that makes working with AppSync a lot easier without compromising on flexibility.
Features
Contents
- Install
- Create
- Configure
- Deploy
1. Install
$ npm install -g serverless
2. Create
Just create the following simple boilerplate:
$ touch serverless.yml # more info in the "Configure" section below
$ touch schema.graphql # your graphql schema file
$ touch index.js # only required if you use a Lambda data source
$ touch .env # your AWS api keys
# .env
AWS_ACCESS_KEY_ID=XXX
AWS_SECRET_ACCESS_KEY=XXX
3. Configure
Basic Configuration
The following is a simple configuration that lets you get up and running quickly with a Lambda data source. Just add it to the serverless.yml
file:
myLambda:
component: "@serverless/aws-lambda"
inputs:
handler: index.handler
code: ./
myAppSyncApi:
component: "@serverless/aws-app-sync"
inputs:
name: Posts
authenticationType: API_KEY
apiKeys:
- myApiKey
dataSources:
- type: AWS_LAMBDA
name: getPost
config:
lambdaFunctionArn: ${myLambda.arn}
mappingTemplates:
- dataSource: getPost
type: Query
field: getPost
This configuration works with the following example schema. Just add it to the schema.graphql
file right next to serverless.yml
:
schema {
query: Query
}
type Query {
getPost(id: ID!): Post
}
type Post {
id: ID!
author: String!
title: String
content: String
url: String
}
You'll also need to add the following handler code for this example to work:
exports.handler = async event => {
var posts = {
"1": {
id: "1",
title: "First Blog Post",
author: "Eetu Tuomala",
url: "https://serverless.com/",
content:
"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s."
},
"2": {
id: "2",
title: "Second Blog Post",
author: "Siddharth Gupta",
url: "https://serverless.com",
content:
"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s."
}
};
return posts[event.id];
};
For more advanced usage, keep reading!
Custom Domains
You could optionally specify a custom domain for your GraphQL API, just add a domain property to the app sync component inputs:
myAppSyncApi:
component: "@serverless/aws-app-sync"
inputs:
domain: api.example.com
name: Posts
This would create a CloudFront distribution (aka CDN) for your AppSync API, which reduces request latency significantly, and would give you an SSL certificate out of the box powered by AWS ACM.
Please note that your domain (example.com in this example) must have been purchased via AWS Route53 and available in your AWS account. For advanced users, you may also purchase it elsewhere, then configure the name servers to point to an AWS Route53 hosted zone. How you do that depends on your registrar.
Create or Reuse APIs
The AppSync component allows you to either create an AppSync API from scratch, or integrate with an existing one. Here's how to create a new API:
myAppSync:
component: '@serverless/aws-app-sync'
inputs:
name: 'my-api-name'
domain: api.example.com
authenticationType: 'API_KEY'
apiKeys:
- 'myApiKey'
mappingTemplates:
- dataSource: 'dynamodb_ds'
type: 'Query'
field: 'getMyField'
request: 'mapping-template-request.vtl'
response: 'mapping-template-response.vtl'
functions:
- dataSource: 'dynamodb_ds'
name: 'my-function'
request: 'function-request.vtl'
response: 'function-response.vtl'
dataSources:
- type: 'AMAZON_DYNAMODB'
name: 'dynamodb_ds'
config:
tableName: 'my-dynamo-table'
schema: 'schema.graphql'
Reuse an existing AWS AppSync service by adding replacing the name
input with apiId
. This way the component will modify the AppSync service by those parts which are defined.
myAppSync:
component: '@serverless/aws-app-sync'
inputs:
apiId: 'm3vv766ahnd6zgjofnri5djnmq'
mappingTemplates:
- dataSource: 'dynamodb_2_ds'
type: 'Query'
field: 'getMyField'
request: 'mapping-template-request.vtl'
response: 'mapping-template-response.vtl'
dataSources:
- type: 'AMAZON_DYNAMODB'
name: 'dynamodb_2_ds'
config:
tableName: 'my-dynamo-table'
Authentication
The app using AppSync API can use four different methods for authentication.
- API_KEY - Api keys
- AWS_IAM - IAM Permissions
- OPENID_CONNECT - OpenID Connect provider
- AMAZON_COGNITO_USER_POOLS - Amazon Cognito user pool
When using OpenID connect method, inputs has to contain openIDConnectConfig
block.
myAppSync:
component: '@serverless/aws-app-sync'
inputs:
authenticationType: 'OPENID_CONNECT'
openIDConnectConfig:
issuer: 'NNN'
authTTL: '1234'
clientId: 'NNN'
iatTTL: '1234'
When using Amazon Cognito user pools, userPoolConfig
has to be defined.
myAppSync:
component: '@serverless/aws-app-sync'
inputs:
authenticationType: 'AMAZON_COGNITO_USER_POOLS'
userPoolConfig:
awsRegion: 'us-east-1'
defaultAction: 'ALLOW'
userPoolId: 'us-east-1_nnn'
ApiKey can be created and modified by defining apiKeys
.
myAppSync:
component: '@serverless/aws-app-sync'
inputs:
apiKeys:
- 'myApiKey1'
- name: 'myApiKey2'
expires: 1609372800
- name: 'myApiKey3'
expires: '2020-12-31'
Schema
You can define the schema of your GraphQL API by adding it to the schema.graphql
file right next to serverless.yml
. Here's a simple example schema:
schema {
query: Query
}
type Query {
getPost(id: ID!): Post
}
type Post {
id: ID!
author: String!
title: String
content: String
url: String
}
Alternatively, if you have your schema file at a different location, you can specify the new location in serverless.yml
inputs:
name: myGraphqlApi
schema: ./path/to/schema.graphql
Data Sources & Templates
The AppSync component supports 4 AppSync data sources and their corresponding mapping templates. You could add as many data sources as your application needs. For each field (or operation) in your Schema (ie. getPost
), you'll need to add a mapping template that maps to a data source.
Here are the data sources that are supported:
Lambda Data Source
Here's an example setup for a Lambda data source:
serverless.yml
myLambda:
component: "@serverless/aws-lambda"
inputs:
handler: index.handler
code: ./
myAppSyncApi:
component: "@serverless/aws-app-sync"
inputs:
name: Posts
authenticationType: API_KEY
apiKeys:
- myApiKey
dataSources:
- type: AWS_LAMBDA
name: getPost
config:
lambdaFunctionArn: ${myLambda.arn}
- type: AWS_LAMBDA
name: addPost
config:
lambdaFunctionArn: ${myLambda.arn}
mappingTemplates:
- dataSource: getPost
type: Query
field: getPost
- dataSource: addPost
type: Mutation
field: addPost
request: request.vtl
response: response.vtl
index.js
exports.handler = async (event) => {
var posts = {
'1': {
id: '1',
title: 'First Blog Post',
author: 'Eetu Tuomala',
url: 'https://serverless.com/',
content:
"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s."
},
'2': {
id: '2',
title: 'Second Blog Post',
author: 'Siddharth Gupta',
url: 'https://serverless.com',
content:
"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s."
}
}
return posts[event.id]
}
schema.graphql
schema {
query: Query
}
type Query {
getPost(id: ID!): Post
}
type Post {
id: ID!
author: String!
title: String
content: String
url: String
}
DynamoDB Data Source
For the DynamoDB data source, you'll need to provide your own request/response templates that works according to your schema. Here's an example setup:
serverless.yml
myTable:
component: '@serverless/aws-dynamodb'
appsync:
component: '@serverless/aws-app-sync'
inputs:
name: Posts
authenticationType: API_KEY
apiKeys:
- myApiKey
dataSources:
- type: AMAZON_DYNAMODB
name: Posts
config:
tableName: ${myTable.name}
mappingTemplates:
- dataSource: Posts
type: Mutation
field: addPost
request: request.vtl
response: response.vtl
request.vtl
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
"id" : { "S" : "${context.arguments.id}" }
},
"attributeValues" : {
"author": { "S" : "${context.arguments.author}" },
"title": { "S" : "${context.arguments.title}" },
"content": { "S" : "${context.arguments.content}" },
"url": { "S" : "${context.arguments.url}" },
"ups" : { "N" : 1 },
"downs" : { "N" : 0 },
"version" : { "N" : 1 }
}
}
response.vtl
$util.toJson($context.result)
schema.graphql
schema {
query: Query
mutation: Mutation
}
type Query {
getPost(id: ID): Post
}
type Mutation {
addPost(id: ID!, author: String!, title: String!, content: String!, url: String!): Post!
}
type Post {
id: ID!
author: String
title: String
content: String
url: String
ups: Int!
downs: Int!
version: Int!
}
ElasticSearch Data Source
Relational Database Data Source
This example is using an Amazon Aurora Serverless cluster with PostgreSQL database which is already running in AWS.
The database has two tables, authors
table, which contains an id
and a fullname
columns, and posts
table which contains following columns id
, title
, content
, url
, and a foreign key fk_authors_id
.
serverless.yml
myAppSyncApi:
component: "@serverless/aws-app-sync"
inputs:
name: Posts
authenticationType: API_KEY
apiKeys:
- myApiKey
dataSources:
- type: RELATIONAL_DATABASE
name: Posts
config:
awsSecretStoreArn: 'arn:aws:secretsmanager:us-east-1:123456789123:secret:rds-db-credentials/cluster-ABCDEFGHI/admin-aBc1e2'
databaseName: 'mydatabase'
dbClusterIdentifier: 'arn:aws:rds:us-east-1:123456789123:cluster:my-serverless-aurora-postgres-1'
schema: 'public'
mappingTemplates:
- dataSource: Posts
type: Query
field: getPost
request: request.vtl
response: response.vtl
request.vtl
{
"version": "2018-05-29",
"statements": [
"select posts.id, posts.title, posts.content, posts.url, authors.fullname as author from posts, authors where authors.id = posts.fk_authors_id and posts.id = '$ctx.args.id'"
]
}
response.vtl
#if($ctx.error)
$utils.error($ctx.error.message, $ctx.error.type)
#end
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])
schema.graphql
schema {
query: Query
mutation: Mutation
}
type Query {
getPost(id: ID): Post
}
type Mutation {
addPost(fk_authors_id: Int!, title: String!, content: String!, url: String!): Post!
}
type Post {
id: ID!
author: String
fk_authors_id: Int
title: String
content: String
url: String
}
Functions
4. Deploy
To deploy, just run the following command in the directory containing your serverless.yml file
:
$ serverless
After few seconds (up to a minute if it's your first deployment), you should see an output like this:
myAppSyncApi:
apiId: samrhyo7srbtvkpqnj4j6uq6gq
arn: arn:aws:appsync:us-east-1:552751238299:apis/samrhyo7srbtvkpqnj4j6uq6gq
url: "https://samrhyo7srbtvkpqnj4j6uq6gq.appsync-api.us-east-1.amazonaws.com/graphql"
apiKeys:
- da2-coeytoubhffnfastengavajsku
domain: "https://api.example.com/graphql"
9s › myAppSyncApi › done
myApp (master)$
New to Components?
Checkout the Serverless Components repo for more information.