DEV Community

Cover image for How to use Serverless Lamba Function with Appsync and DynamoDB
Abdul Waqar
Abdul Waqar

Posted on

How to use Serverless Lamba Function with Appsync and DynamoDB

How to use AWS Lambda Function as backend and deploy to AWS Cloud. (Part 1)

In this tutorial, we will learn the following things

  • How to write Lamda Function code for CRUD operations.
  • How to Use Appsync and :
    • Types
    • Mutation
    • Query

In Part 2 We will connect Appsync Graphql Api with React Frontend

AWS Account is required for this tutorial. If you don't have a premium AWS account you can use AWS with Free tier for the deployment of AWS-CDK application to AWS Clouds. Then we need to configure AWS at a local PC or Laptop, If you did not configure AWS-CDK, you can follow the below-given post to configure AWS-CDK on your local machine. How to configure AWS-CDK .

If you are interested in how to Deploy static pages to the AWS S3 bucket and deliver these pages with Cloud fron You can Follow this Guide How to Deploy Static pages to AWS S3 bucket and connect with Cloudfornt.

In this post we will learn the advanced topic of AWS Services .

Let's get Started and Create a simple AWS-CDK Application using the terminal.

Step No .01

initialize AWS-CDK app with typescript

cdk init app --language typescript

We are using Typescript if you have not installed typescript install typescript globally first by running this command

npm -g install typescript

Step No. 02

Install required Dependencies. All dependencies can be installed with a single command and one by one also. copy this command to install the required dependency

npm i @aws-cdk/aws-appsync @aws-cdk/aws-lambda @aws-cdk/aws-dynamodb

Why AppSync?

Organizations choose to build APIs with GraphQL because it helps them develop applications faster, by giving front-end developers the ability to query multiple databases, microservices, and APIs with a single GraphQL endpoint.

AWS AppSync is a fully managed service that makes it easy to develop GraphQL APIs by handling the heavy lifting of securely connecting to data sources like AWS DynamoDB, Lambda, and more. Adding caches to improve performance, subscriptions to support real-time updates, and client-side data stores that keep off-line clients in sync are just as easy. Once deployed, AWS AppSync automatically scales your GraphQL API execution engine up and down to meet API request volumes. You can also get help from this post :

Building Real-time Serverless APIs with PostgreSQL, CDK, TypeScript, and AWS AppSync

We will be using AWS Appsync in our application so the first thing to do when working with Appsync is to define our Types, Query, and Mutation in graphql format.

type Todo {
  id: ID!
  title: String!
  done: Boolean!
}

input TodoInput {
  id: ID!
  title: String!
  done: Boolean!
}

type Query {
  getTodos: [Todo]
}

type Mutation {
  addTodo(todo: TodoInput!): Todo
  updateTodo(todo: TodoInput!): Todo
  deleteTodo(todoId: String!): String
}
Enter fullscreen mode Exit fullscreen mode

Copy this code and paste in .graphQL/schema.gql\
Here we have defined one Query to get all Todos and 3 mutations to add , update and delete Todos. We have also declared arguments which we will pass when we need to call our lambda function.

Next import Appsynct at top of /lib/todo-stack.ts like this :

import * as appsync from '@aws-cdk/aws-appsync';
Enter fullscreen mode Exit fullscreen mode

Then declared our Appsync configrations like below :

 const api = new appsync.GraphqlApi(this, "GRAPHQL_API", {
      name: 'todo-api',
      schema: appsync.Schema.fromAsset('graphQL/schema.gql'),       ///Path specified for lambda
      authorizationConfig: {
        defaultAuthorization: {
          authorizationType: appsync.AuthorizationType.API_KEY,     ///Defining Authorization Type  
        },
      },

    })

Enter fullscreen mode Exit fullscreen mode

In the above code, we have created an Appsync API and passed required arguments like name of API then schema which we will use in our application then Authentication configuration.

Lamda Function as Datasrource

We need a datasource to respond when a GraphQL query is called. In this post, We will use Lambda Function as datasource for Appsync API to save todos in the database and retrieve records. Let's create an instance of the AWS lambda function. Use this code for Lambda Function. Import * as lambda at top

import * as lambda from '@aws-cdk/aws-lambda';
Enter fullscreen mode Exit fullscreen mode

Then write this code

 ///Lambda Fucntion
    const todoLambda = new lambda.Function(this, "TodoFucntion", {
      functionName:"todoHandler",
      runtime: lambda.Runtime.NODEJS_12_X,            ///set nodejs runtime environment
      code: lambda.Code.fromAsset("functions"),          ///path for lambda function directory
      handler: 'main.handler',                       ///specfic fucntion in specific file
    })
Enter fullscreen mode Exit fullscreen mode

Let's explain this code, We created an instance of lambda function and passed required configuration 1. Function name you can use any unique name(Unique in sense of your existing Lambda Function)2. Declared Nodejs version to use for our Lambda Function, 3. Directory, where is our main code of the function, is written. We have written our code on the functions folder at root. Then handler is the name of the function in our lambda function codes.

Next we will write code for our lambda function.

const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient();
// Function Declation to add New Todo
async function addTodo(todo: Todo) {
    const params = {
        TableName: process.env.TODOS_TABLE,
        Item: todo
    }
    try {
        await docClient.put(params).promise();
        return todo;
    } catch (err) {
        console.log('DynamoDB error: ', err);
        return null;
    }
}

// Function Declation to Get all todo list
async function getTodos() {
    const params = {
        TableName: process.env.TODOS_TABLE,
    }
    try {
        const data = await docClient.scan(params).promise()
        return data.Items
    } catch (err) {
        console.log('DynamoDB error: ', err)
        return null
    }
}
// Function Declation to Delete Todo
async function deleteTodo(todoId: string) {
    const params = {
        TableName: process.env.TODOS_TABLE,
        Key: {
            id: todoId
        }
    }
    try {
        await docClient.delete(params).promise()
        return todoId
    } catch (err) {
        console.log('DynamoDB error: ', err)
        return null
    }
}

// Function Declation to Update Todo

type Params = {
    TableName: string | undefined,
    Key: string | {},
    ExpressionAttributeValues: any,
    ExpressionAttributeNames: any,
    UpdateExpression: string,
    ReturnValues: string
}

async function updateTodo(todo: any) {
    let params: Params = {
        TableName: process.env.TODOS_TABLE,
        Key: {
            id: todo.id
        },
        ExpressionAttributeValues: {},
        ExpressionAttributeNames: {},
        UpdateExpression: "",
        ReturnValues: "UPDATED_NEW"
    };
    let prefix = "set ";
    let attributes = Object.keys(todo);
    for (let i = 0; i < attributes.length; i++) {
        let attribute = attributes[i];
        if (attribute !== "id") {
            params["UpdateExpression"] += prefix + "#" + attribute + " = :" + attribute;
            params["ExpressionAttributeValues"][":" + attribute] = todo[attribute];
            params["ExpressionAttributeNames"]["#" + attribute] = attribute;
            prefix = ", ";
        }
    }

    try {
        await docClient.update(params).promise()
        return todo
    } catch (err) {
        console.log('DynamoDB error: ', err)
        return null
    }
}
type Todo = {
    id: string;
    title: string;
    done: boolean;
}
type AppSyncEvent = {
    info: {
        fieldName: string
    },
    arguments: {
        todoId: string,
        todo: Todo
    }
}
exports.handler = async (event: AppSyncEvent) => {
    switch (event.info.fieldName) {
        case "addTodo":
            return await addTodo(event.arguments.todo);
        case "getTodos":
            return await getTodos();
        case "deleteTodo":
            return await deleteTodo(event.arguments.todoId);
        case "updateTodo":
            return await updateTodo(event.arguments.todo);
        default:
            return null;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now will add Lambda As Datasource to Appsync

This code will add lambda as Datasource with Appsync

 ////Set lambda as a datasource
    const lambda_data_source = api.addLambdaDataSource(
      "lamdaDataSource",
      todoLambda
    );
Enter fullscreen mode Exit fullscreen mode

Next We will create Resolvers for our Datasource.

Now create resolvers for our Query and Mutations to our lambda datasource.


    ///Describing resolver for datasource

    lambda_data_source.createResolver({
      typeName: "Query",
      fieldName: "getTodos",
    });

    lambda_data_source.createResolver({
      typeName: "Mutation",
      fieldName: "addTodo",
    });

    lambda_data_source.createResolver({
      typeName: "Mutation",
      fieldName: "deleteTodo",
    });

    lambda_data_source.createResolver({
      typeName: "Mutation",
      fieldName: "updateTodo",
    });


Enter fullscreen mode Exit fullscreen mode

Create DynamoDB Instance

In last we need to create an instance of DynamoDB. We will use DynamoDB from our main function code to save , edit, delete and updated records.

//Import this line at top of file 
import * as ddb from "@aws-cdk/aws-dynamodb";
/*
Other Code
...
...
...*/
// Createing DynmoDB instance
 const todosTable = new ddb.Table(this, "CDKTodosTable", {
      tableName: "13ATodotable",
      partitionKey: {
        name: "id",
        type: ddb.AttributeType.STRING,
      },
    });
    // Granting Full acces to our Lambda Function to read and write records.
    todosTable.grantFullAccess(todoLambda);
    // Adding  our DynamoDB Table as Envoirnemnt Variable to our lambda Function
    todoLambda.addEnvironment("TODOS_TABLE", todosTable.tableName);
Enter fullscreen mode Exit fullscreen mode

Useful commands

After creating our stack and function declaration and Graphql Schema definition, We will make a build of our application Then run

npm run build

This command will compile typescript to javascript code which can be deployed to AWS.
Once You have successfully compiled and created a build of your application, You can deploy it to AWS Clouds by running this command

cdk deploy

This Command will deploy your application on AWS.

How To Test that our Application working properly

Login to your AWS Account and Goto Appsync Service. AWS provides a GraphQL playground to test Graphql Queries and Mutation. Here you can test your Queries and Mutation.

Image description

Image description

*Completed Code of this application available at:
Github Code *

In the Next Tutorial We will learn how to connect our Graphql API with React Frontend.

You can connect with me on Social Media.

Twitter

Facebook

Github

Discussion (0)