GraphQL is a specification for which there are many different kinds of implementations in the market. AppSync is a serverless implementation of GraphQL by AWS and is a managed GraphQL platform. As mentioned in this article, AppSync can be a way to replace the API Gateway + AWS Lambda pattern for connecting clients to your serverless backends. In this post, we’ll start with AppSync by creating a simple GraphQL application with two data sources: DynamoDB and AWS Lambda. But first, let’s learn a bit more about GraphQL.

GraphQL – Some Background

GraphQL is a different way to connect your client applications to your backend. In general, we are used to REST, where the client connects to the backend via a defined endpoint. When connecting to an endpoint, the client needs to specify headers, an HTTP method (GET, POST, DELETE, etc.), a body, and all kinds of parameters. Usually, he needs to be familiar with all the information beforehand to be able to request the information from the server. The client might end up getting a lot of information back that isn’t necessary (over fetch) and often forces him to do a lot of calls to the backend to populate a view since one request only brings part of the information necessary (under fetch). GraphQL has some important characteristics. Between the client and the server, a contract is signed, called the GraphQL schema. Here, all possible types of operations that the client can perform on the backend are defined. The client can call an operation with one request, which will return multiple types if needed so that there is no under fetch of information. The client can also specify attributes for the types it wants so that there is no over fetch of information. GraphQL is implemented between the client and the data sources, making GraphQL a single entry point to the backend. The data sources can be anything from NoSQL databases, relational databases, and HTTP servers to whatever returns data. To connect a GraphQL implementation to the data sources, you need to write resolvers. Resolvers are a very important part of your GraphQL application, as they are the ones that translate GraphQL requests or responses to whatever the data sources understand. For example, if the data source is a relational database, the resolver will need to know how to transform a GraphQL query into a SELECT operation and then translate whatever the relational database returns into a GraphQl response.

Introduction to AppSync AppSync can do everything GraphQL specifies and also has additional features that come out of the box. For example, it supports authentication and authorization. This enables to filter the data you return and the operations that the clients can perform, depending on which user is in. AppSync supports Cognito, API Key, IAM permissions, and Open ID Connect and even provides out-of-the-box support for DynamoDB, AWS Lambda, Elasticsearch, HTTP, and RDS as data sources. Appsync also has support for real-time operations. This means that if a data source is updated, the clients that are subscribed to these operations will get updated as well. This way, the clients don’t need to ping the GraphQL service to check if there is new data available. Also, there is offline support, meaning that if the client application goes offline and modifies the data when it comes online, the data will get synced with the data sources automatically. Read about all of AppSync’s features in this document and understand the pricing better. Monitor and troubleshoot AppSync with Epsagon today!

Start With AppSync As mentioned, we’ll be using DynamoDB and AWS Lambda to create our GraphQL application. The application will store image metadata in DynamoDB. The images will be in S3, and we’ll save an identifier in DynamoDB for each image and the S3 path. Then, when we want to retrieve an image, it will fetch the S3 path with the image identifier. Using AWS Lambda, we will then get the signed URL for that image so that the client can see it.

When getting started with a new serverless application, you should first read this blog post, which covers some basic considerations for a beginner serverless developer. We’re going to start with a Serverless Framework and implement a serverless framework plugin to use AppSync easily in our project. Before starting this tutorial, you will also have to make sure your computer has an AWS account and Serverless Framework installed and configured. Then, you can go ahead and create a new Serverless Framework project. For that, we need to run this command inside an empty directory: $ sls create --template aws-nodejs --name appsync-intro This will create the boilerplate for our project–the serverless.yml and handler.js files. Now go to the serverless.yml file and edit it like this: service: appsync-intro plugins: - serverless-appsync-plugin - serverless-pseudo-parameters provider: name: aws runtime: nodejs10.x functions: graphql: handler: handler.graphql For this tutorial, you’ll use the serverless-appsync-plugin and the serverless-pseudo-parameters. For the plugins to work, install them into your project. In order to do that, type this into your terminal: $ npm install --save serverless-appsync-plugin $ npm install --save serverless-pseudo-parameters Now you can start setting up the AppSync application. Write the following code into the serverless.yml after the provider and before the functions: custom: IMAGE_TABLE: appsync-intro-image-table BUCKET_NAME: <your-bucket-name> appSync: name: appsync-intro authenticationType: API_KEY mappingTemplates: - dataSource: Images type: Mutation field: saveImage request: saveImage-request-mapping-template.vtl response: saveImage-response-mapping-template.vtl - dataSource: lambdaDatasource type: Query field: getImageSignedPath request: getImageSignedPath-request-mapping-template.vtl response: getImageSignedPath-response-mapping-template.vtl dataSources: - type: AMAZON_DYNAMODB name: Images description: 'Table containing the metadata of the images' config: tableName: { Ref: ImageTable } iamRoleStatements: - Effect: "Allow" Action: - "dynamodb:GetItem" - "dynamodb:PutItem" Resource: - "arn:aws:dynamodb:#{AWS::Region}:*:table/${self:custom.IMAGE_TABLE}" - "arn:aws:dynamodb:#{AWS::Region}:*:table/${self:custom.IMAGE_TABLE}/*" - type: AWS_LAMBDA name: lambdaDatasource description: 'Lambda DataSource' config: functionName: graphql 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:#{AWS::Region}:#{AWS::AccountId}:function:${self:service}-dev-graphql" - "arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:${self:service}-dev-graphql:*" The first two lines are just defining environmental variables with the name of the bucket and the DynamoDB table We’ll use them to store the metadata. Now, we can start configuring the AppSync application with the following details: Name : The name of the AppSync application.

Authentication Type : How we want to secure the AppSync app. In this case, you will use the simplest way of securing the app–with an API Key.

Mapping Templates : To define all the different resolvers for your application. For each operation or field defined in your schema.graphql, you need to specify the resolvers–one resolver for each request and one resolver for each response. Resolvers in AppSync are written in VTL (velocity template language).

Data sources: To define all the different data sources for your application. You have two data sources in this application: DynamoDB and Lambda. In this section, you will specify the type of data source, the resource name or ARN (Amazon resource name), and the permissions that you will give AppSync to operate over this data source. After everything is defined above, we’ll create the Table and also give permissions to the function to fetch items from the Table and to retrieve items from the S3 bucket. For this, we’ll need to add the following code after the provider and before the custom property in the serverless.yml: iamRoleStatements: - Effect: "Allow" Action: - "s3:ListBucket" - "s3:GetObject" Resource: "arn:aws:s3:::${self:custom.BUCKET_NAME}/*" - Effect: "Allow" Action: - "dynamodb:GetItem" Resource: "arn:aws:dynamodb:#{AWS::Region}:*:table/${self:custom.IMAGE_TABLE}" And then at the end of the file, add: resources: Resources: #Image table ImageTable: Type: "AWS::DynamoDB::Table" Properties: KeySchema: - AttributeName: name KeyType: HASH AttributeDefinitions: - AttributeName: name AttributeType: S BillingMode: PAY_PER_REQUEST TableName: ${self:custom.IMAGE_TABLE} Your serverless.yml is almost ready, but we still need to define the schema.graphql file for your application. So, create a new file and name it “schema.graphql.” There we can add: type Query { getImageSignedPath(imageName: String!): String! } type Mutation { saveImage(name: String!, s3Path: String!): Image! } type Image { name: String! s3Path: String! } schema { query: Query mutation: Mutation } This will add two operations and one type to your application. The type is called Image and has a name and S3Path that will refer to the place where it is stored in AWS S3. The query is the operation without side effects and is called getImageSignedPath. When given an image name, the query returns a signed URL for the image. The mutation is the operation that performs side effects in your backend and is called saveImage. When given an image name and S3Path, the mutation stores the image metadata in the DynamoDB image table. Next, we’ll create the resolver files by specifying the four different resolvers in your serverless.yml–one each for the request and the response of the operation getImageSignedPath as well as for the request and the response of the operation saveImage. The resolvers are very simple, and you can find the files in this link. Finally, write the business logic for your function. Go ahead and write the following code in your handler.js: 'use strict'; const AWS = require('aws-sdk'); const s3 = new AWS.S3({signatureVersion: 'v4'}); const dynamo = new AWS.DynamoDB.DocumentClient(); module.exports.graphql = async (event) => { switch (event.field) { case 'getImageSignedPath': { const bucket = <your own bucket name>; const imageName = event.arguments.imageName; const key = await getImageS3Path(imageName); return signURL(bucket, key); } default: { return `Unknown field, unable to resolve ${event.field}`, null; } } }; function signURL(bucket, key) { const params = {'Bucket': bucket, 'Key': key}; return s3.getSignedUrl('getObject', params); } async function getImageS3Path(imageName) { const params = { Key: { name: imageName }, TableName:'appsync-intro-image-table' }; return dynamo.get(params).promise().then(result => { return result.Item.s3Path; }); } This function is a very simple one that, given an image name that is passed in the arguments of the request, returns a signed URL. It first fetches the image S3 path from the image metadata table. Then, when it gets the path, it uses the AWS SDK S3 module to retrieve the signed URL and finally returns that URL as a response. You can find all the code for this application here. Testing To test this application, simply deploy it from the terminal: $ sls deploy This will deploy and create all the resources you need–DynamoDB table, AppSync application, and function. Then, in your AWS account, simply create an S3 bucket with the name that you specified in the environmental variables. Here, you can store some images. Now go to the AppSync console, then to Queries. There you can create some queries to test the application. For example, if you stored an image with the path image1.png, you can type this mutation there.

If you want to fetch the signed path, you can type this query: