AWS AppSync ToDo APIs with AWS Lambda Resolvers using Serverless Framework
Tags: aws, appsync, lambda, serverless
Published: 2021-09-26 14:20:30

Implement basic ToDo APIs using AWS AppSync with AWS Lambda Resolvers by using Serverless Framework

#####Application Stack:
* Serverless Framework
* AWS AppSync
* AWS Lambda
* AWS DynamoDB
* AWS IAM
* NodeJs
* GraphQL

[Install NodeJs as per instruction given here](https://nodejs.org/en/download/)

#### 1. Install Serverless Framework

```
npm install -g serverless
```

#### 2. Create basic NodeJs type template project using Serverless Framework

```
serverless create --template aws-nodejs
```

By default it will generate a folder structure like this:
* handler.js
* serverless.yml

Default serverless.yml will have below IaC code:

```
service: default-aws-nodejs
frameworkVersion: '2'
provider:
  name: aws
  runtime: nodejs12.x
  lambdaHashingVersion: 20201221

functions:
  hello:
    handler: handler.hello
```

Default handler.js will have below NodeJs code:

```
'use strict';

module.exports.hello = async (event) => {
  return {
    statusCode: 200,
    body: JSON.stringify(
      {
        message: 'Go Serverless v1.0! Your function executed successfully!',
        input: event,
      },
      null,
      2
    ),
  };
};

```

#### 3. Install required Serverless Framework Plugins
* npm install serverless-appsync-plugin
* npm install serverless-pseudo-parameters

#### 4. Modify serverless.yml Infrastructure as code to provision needful AppSync, DynamoDb, Lambda resources, and relevant configuration

```
service: appsync-todo-apis-demo
frameworkVersion: "2"

plugins:
   - serverless-appsync-plugin
   - serverless-pseudo-parameters

provider:
  name: aws
  runtime: nodejs12.x
  stage: dev
  region: ap-south-1
  lambdaHashingVersion: 20201221
  iam:
    role:
      statements:
        - Effect: "Allow"
          Action:            
            - "dynamodb:PutItem"
            - "dynamodb:UpdateItem"
            - "dynamodb:DeleteItem"
            - "dynamodb:GetItem"
            - "dynamodb:Scan"
          Resource: "arn:aws:dynamodb:#{AWS::Region}:*:table/${self:custom.TODO_TABLE}"

custom:      
  TODO_TABLE: todo-table

  appSync:
    name: appsync-todo-apis-demo
    authenticationType: API_KEY
    mappingTemplates:      
      - type: Mutation
        field: createTodo
        dataSource: createTodoFunction
        request: false
        response: false
      - type: Mutation
        field: updateTodo
        dataSource: updateTodoFunction
        request: false
        response: false
      - type: Mutation
        field: deleteTodo
        dataSource: deleteTodoFunction
        request: false
        response: false
      - type: Query
        field: getTodos
        dataSource: getTodosFunction
        request: false
        response: false      
    dataSources:        
      - type: AWS_LAMBDA
        name: createTodoFunction
        config:
          functionName: createTodo

      - type: AWS_LAMBDA
        name: updateTodoFunction
        config:
          functionName: updateTodo

      - type: AWS_LAMBDA
        name: deleteTodoFunction
        config:
          functionName: deleteTodo

      - type: AWS_LAMBDA
        name: getTodosFunction
        config:
          functionName: getTodos                     

functions: 
  getTodos:
    handler: functions/getTodos.handler
    environment:
      TODO_TABLE_NAME: !Ref TodoTable

  createTodo:
    handler: functions/createTodo.handler
    environment:
      TODO_TABLE_NAME: !Ref TodoTable

  updateTodo:
    handler: functions/updateTodo.handler
    environment:
      TODO_TABLE_NAME: !Ref TodoTable

  deleteTodo:
    handler: functions/deleteTodo.handler
    environment:
      TODO_TABLE_NAME: !Ref TodoTable

resources:
  Resources:
    TodoTable:
      Type: "AWS::DynamoDB::Table"
      Properties:
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        BillingMode: PAY_PER_REQUEST
        TableName: ${self:custom.TODO_TABLE}
```

#### 5. Create GraphQL Schema new file schema.graphql

```
schema {
	query: Query
	mutation: Mutation
}

type Query {
	getTodos: [Get!]!
}

type Mutation {
	createTodo(id: ID!, name: String!, description: String!): Save
	updateTodo(id: ID!, name: String!, description: String!): Update
	deleteTodo(id: ID!): Delete
}

type Get {
	id: ID!
	name: String
	description: String	
}

type Save {
	id: ID!
	name: String
	description: String	
}

type Update {
	id: ID!
	name: String
	description: String	
}

type Delete {
	id: ID!	
}
```

#### 6. Implement AWS Lambda Resolver CRUD handlers

Create new folder 'functions' with this files:
* createTodo.js
* deleteTodo.js
* getTodos.js
* updateTodo.js

##### 6.1 createTodo.js handler

```
const AWS = require('aws-sdk')
const dynamodb = new AWS.DynamoDB()

module.exports.handler = async (event) => {    
    const id = event.arguments.id
    const name = event.arguments.name
    const description = event.arguments.description    

    const params = {
        Item: {
            "id": {
                S: id
            },
            "name": {
                S: name
            },
            "description": {
                S: description
            }            
        },
        ReturnConsumedCapacity: "TOTAL",
        TableName: process.env.TODO_TABLE_NAME
    }

    return dynamodb.putItem(params).promise()
        .then(data => {            
            return {
                id,
                name,
                description
            }
        })
        .catch(err => {
            console.log(err)
        })
};
```

##### 6.2 deleteTodo.js handler

```
const AWS = require('aws-sdk')
const dynamodb = new AWS.DynamoDB()

module.exports.handler = async (event) => {
    const id = event.arguments.id

    const params = {
      Key: {
          "id": {
              S: id
          }
      },
      TableName: process.env.TODO_TABLE_NAME
    }


    return dynamodb.deleteItem(params).promise()
        .then(data => {            
            return {
                id
            }
        })
        .catch(err => {
          console.log(err)
        })
};
```

##### 6.3 updateTodo.js handler

```
const AWS = require('aws-sdk')
const dynamodb = new AWS.DynamoDB()

module.exports.handler = async (event) => {
    const id = event.arguments.id
    const name = event.arguments.name
    const description = event.arguments.description

    const params = {
      ExpressionAttributeNames: {
          "#n": "name",
          "#d": "description"          
      },
      ExpressionAttributeValues: {
          ":n": {
              S: name
          },
          ":d": {
              S: description
          }
      },
      Key: {
          "id": {
              S: id
          }
      },
      ReturnValues: "ALL_NEW",
      TableName: process.env.TODO_TABLE_NAME,
      UpdateExpression: "SET #n = :n, #d = :d"
  }
  
  return dynamodb.updateItem(params).promise()
      .then(data => {
          const body = data.Attributes
          return {
            id: body.id.S,
            name: body.name.S,
            description: body.description.S
          }
      })
      .catch(err => {
        console.log(err)
      })
};
```

##### 6.4 getTodos.js handler

```
const AWS = require('aws-sdk')
const dynamodb = new AWS.DynamoDB()

module.exports.handler = async (event) => {
    
    const params = {        
        TableName: process.env.TODO_TABLE_NAME
    }

    return dynamodb.scan(params).promise()
        .then(data => {            
            const todoList = [];
            for (let i = 0; i < data.Items.length; i++) {
                todoList.push({
                    id: data.Items[i].id.S,
                    name: data.Items[i].name.S,
                    description: data.Items[i].description.S
                });        
            }
            return todoList;            
        })
        .catch(err => {
            console.log(err)
        })
};
```

#### 7. Create AWS IAM user with programmatic access and give needful permission that allows Serverless Framework to provision resources

The Serverless Framework needs access to your cloud provider account so that it can create and manage resources on your behalf.

During Creating AWS IAM user copy API Key & Secret.

#### 8. Configure AWS Credentials for Serverless Framework Deployment

```
serverless config credentials --provider aws --key xxxxx --secret xxxxx
```

#### 9. Deploy using Serverless Framework

```
serverless deploy
```

```
> serverless deploy
AppSync Plugin: GraphQl schema valid
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
........
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service appsync-todo-apis-demo.zip file to S3 (16.98 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..........................................................................................
Serverless: Stack update finished...
Service Information
service: appsync-todo-apis-demo  
stage: dev
region: ap-south-1
stack: appsync-todo-apis-demo-dev
resources: 31
api keys:
  None
appsync api keys:
  xxxxxxxxxxxxxxx
endpoints:
  None
appsync endpoints:
  https://xxxxxxxxxxxxxxx.appsync-api.ap-south-1.amazonaws.com/graphql
functions:
  getTodos: appsync-todo-apis-demo-dev-getTodos
  createTodo: appsync-todo-apis-demo-dev-createTodo
  updateTodo: appsync-todo-apis-demo-dev-updateTodo
  deleteTodo: appsync-todo-apis-demo-dev-deleteTodo
layers:
  None
```

#### 10. AWS AppSync APIs Verification Screenshots
##### AppSync API Verification - getTodos
![getTodos API verification](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yio8657c8qqku1d6k156.png)

##### AppSync API Verification - createTodo
![createTodo API Verification](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4w1j08m2o64so4nyt0y9.png)

##### AppSync API Verification - deleteTodo
![deleteTodo API Verification](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7q29l53gpii2gxq3eqa5.png)

#### 11. Remove already provisioned resources

```
serverless remove
```

#### Relevant Resources Link
* Serverless Framework: https://www.serverless.com/
* AWS AppSync: https://aws.amazon.com/appsync/
* AWS Lambda: https://aws.amazon.com/lambda/
* NodeJs: https://nodejs.org/
* GraphQL: https://graphql.org/
* AWS DynamoDB: https://aws.amazon.com/dynamodb/
* AWS IAM: https://aws.amazon.com/iam/