How to build Serverless GraphQL API using AWS AppSync

How to build Serverless GraphQL API using AWS AppSync

Featured Services: AWS AppSync, Lambda and DynamoDB

What is GraphQL?

GraphQL is a query language for APIs which provides a complete and understandable description of the data in the API. GraphQL APIs give the exact data that the client wants, nothing more or less.

GraphQL is fast and flexible to use as compared to REST. GraphQL allows clients to decide what data to be returned unlike servers in REST. GraphQL reduces the number of HTTP requests since it can get you all the data required by your app in a single request. According to official documentation, apps using GraphQL can be quick even on slow mobile network connections.

GraphQL Operations

  1. Query - Used to fetch the data from the GraphQL API, same as GET request
  2. Mutation - Used to manipulate the data, same as CREATE, UPDATE, DELETE
  3. Subscription - Used to listen or subscribe to the changes to the data

What is AWS AppSync?

AWS AppSync is a fully managed, serverless GraphQL API service offered by Amazon Web Services. AppSync allows developers to build GraphQL APIs without much hassle; it takes care of the parsing and resolution of requests.

It also natively supports integration with other AWS services like AWS Lambda, NoSQL and SQL databases, and HTTP APIs to gather backend data for the API.

What are we going to build?

We will build a GraphQL API using AWS AppSync to perform CRUD operations. For example, we will make an API that will allow us to create, read, update and delete a blog. We will use DynamoDB to store our blog data to achieve the functionality of the CRUD API we will use Lambda. To configure our resources we will use Serverless Framework.

Initializing the project

First of all, create an empty directory and we will create a new serverless project in this directory. You can use the following command to do that.

serverless create --template aws-nodejs

The above command will create a new serverless project for you from scratch and you can find the serverless.yml file in the editor. This file is the heart of the project, where we will define all our infrastructure.

This is how the serverless.yml file will look after all the resources are defined. I will explain each section of this file further.

Firstly, start defining the basics like region, stage, and global configuration variables in the provider section.

provider:
  name: aws
  runtime: nodejs12.x
  lambdaHashingVersion: 20201221
  region: ${env:REGION, 'ap-south-1'}
  stage: ${opt:stage, 'dev'}
  timeout: 30

Then, we define global-level environment variables, as defined in the image below.

  # Environment variables
  environment:
    STAGE: ${self:provider.stage}
    REGION: ${self:provider.region}
    SERVICE_NAME: ${self:service}-${self:provider.stage}
    DYNAMODB: ${self:service}-${self:provider.stage}

IAM Role Statements

Since we are going to use DynamoDB to store the data and it will be integrated with the lambda function, we will need to define IAM role statements to give permission to the lambda function to access our DynamoDB table.

# IAM statements
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:GetItem
        - dynamodb:UpdateItem
        - dynamodb:DeleteItem
        - dynamodb:PutItem
      Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB}"
    - Effect: Allow
      Action:
        - lambda:invokeFunction
      Resource: "*"

serverless-appsync-plugin

serverless-appsync-plugin allows us to deploy AppSync API without much hassle, it provides a lot of configurations to set up your API on AppSync.

plugins:
  - serverless-appsync-plugin

Lambda Functions

Since we are going to use lambda resolvers, so we don't need custom resolver mapping templates (request-response VTL templates). Below is how all the lambda functions are defined.

functions:
  createBlog:
    handler: createBlog/index.handler
    name: createBlog-${self:provider.stage}
  getBlog:
    handler: getBlog/index.handler
    name: getBlog-${self:provider.stage}
  updateBlog:
    handler: updateBlog/index.handler
    name: updateBlog-${self:provider.stage}
  deleteBlog:
    handler: deleteBlog/index.handler
    name: deleteBlog-${self:provider.stage}

Now, it's time to set up the appsync configuration provided by serverless-appsync-plugin, this plugin has a lot of configurations available, in the below code snippet you can find some of them. In the custom section of the serverless.yml file, we can define the configurations.

To know more about what each of these configuration parameters does, head over to this link.

custom:
  appSync:
    name: appsync-crud-api
    schema: schema.api.graphql
    authenticationType: API_KEY
    apiKeys:
      - apiKey
    dataSources:
      - type: AWS_LAMBDA
        name: createBlog
        description: 'Lambda'
        config:
          functionName: createBlog           
      - type: AWS_LAMBDA
        name: getBlog
        description: 'Lambda'
        config:
          functionName: getBlog
      - type: AWS_LAMBDA
        name: updateBlog
        description: 'Lambda'
        config:
          functionName: updateBlog
      - type: AWS_LAMBDA
        name: deleteBlog
        description: 'Lambda'
        config:
          functionName: deleteBlog

DynamoDB Table

To store the blog data we are using the dynamodb table which can be configured like the below:

resources:
  Resources:
    BlogsTable:
      Type: 'AWS::DynamoDB::Table'
      Properties:
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1
        TableName: ${self:provider.environment.DYNAMODB}

GraphQL Schema

Every graphql API needs a schema where we define all the operations(query, mutation, subscription). We will need to create a file named schema.api.graphql. This same file we have referred to in the appsync plugin configuration is in the custom section. The file should look something like this:

schema {
    query: Query
    mutation: Mutation
}

type Query {
    # Query for blog details
    getBlog(id: ID!): Blog
}

type Mutation {
    # Create blog
    createBlog(input: CreateBlogInput!): AWSJSON

    # Update blog
    updateBlog(input: UpdateBlogInput!): AWSJSON

    # delete blog
    deleteBlog(id: ID!): AWSJSON
}

input UpdateBlogInput {
    id: ID!
    title: String
    blogUrl: String
    author: String
    publicationDate: AWSDate
}

input CreateBlogInput {
    title: String!
    blogUrl: String!
    author: String!
    publicationDate: AWSDate!
}

type Blog {
    id: ID
    title: String
    blogUrl: String
    author: String
    publicationDate: AWSDate
}

Lambda Function Code

Now it's time to move towards lambda functions, which will act as a resolver for the API. Hence the name, "Lambda Resolvers". As you can see in the image below, I have created a folder for each of the lambda functions which we defined in the functions section in the serverless.yml file.

All four lambdas just perform very simple dynamodb CRUD operations using dynamodb DocumentClient() from aws-sdk. I won't explain the lambda code since it's really straightforward. You can head over to this repository and find the code.

image.png

If you have followed until now, then it's time to deploy our service 🚀.

Use serverless deploy to deploy all of your defined resources to the aws cloud. This will give you the deployed API URL and API_KEY in the console which can be used by the client application.

Testing the GraphQL API

Once your deployment is successful, you'll see something like this in the AppSync console. You'll see a tile with the name you have given to your API.

image.png

Click on the tile and you'll see another interface, then in the left nav click on Queries. You should see the following interface where you will find all the CRUD operations we have defined in our schema.api.graphql file.

In the mutations section, you'll find createBlog, updateBlog, and deleteBlog, and in the queries section, you'll find getBlog.

You can then test the createBlog mutation by entering some data and hitting the play button on the top should give you the success response by returning the created blog's data in the rightmost window.

image.png

By doing the same steps, you can test the other operations as well.

Aaaand 🥁, we are done with the whole backend side of things. We just created an API for a simple blog application.

That's all for this blog, if you liked it do not forget to react to the blog. Thanks for reading and following along, Peace ☮✌️.

Feel free to reach out to me:

GitHub - https://github.com/iabhishek07

Twitter - https://twitter.com/abhishekrwagh

LinkedIn - https://www.linkedin.com/in/iamabhishek21/

Email -