Serverless API with AWS Lambda, AWS API Gateway, and DynamoDB

close up photo of programming of codes
Reading Time: 5 minutes

Since we are using AWS cloud services, make sure you have an account for the AWS Console. Let’s start with some basic concepts of serverless computing and then will build serverless API.

Introduction

“Serverless computing is a method of providing backend services on an as-used basis”, i.e developers do not need to worry about writing the code for the server, creating the server, maintaining the server, and deploying the server. It allows developers to focus on building the application instead of configuring it.

The term “serverless” is somewhat misleading, it still requires the server to run, but all of the server space and infrastructure concerns are managed by a serverless provider or cloud provider like AWS, Google Cloud, MS Azure, etc. Developer only has to pay for the services they use.

The core of Serverless Computing

FaaS(function-as-a-service) is the core of serverless computing.

FaaS is a type of cloud computing service that allows us to execute the code in response to events without complex infrastructure. I.e Applications deployed using a serverless computing strategy are usually called serverless functions. The cloud provider gives a service wherein we can write our code or function and put it on that service and we only pay whenever the function is triggered due to an event (like user click, submit the form, etc).

Some FaaS providers are AWS lambda from AWS, Azure functions from MS, Google cloud functions from google, IBM cloud functions from IBM, etc.

Serverless Architecture in AWS

AWS provides services like AWS Lambda, Amazon API Gateway, and Amazon DynamoDB to implement serverless architectural patterns that reduce the operational complexity of running and managing applications.

An application consists of three layers- compute, integration, and Data stores, and cloud providers provide solutions for these three layers and for more.

The Frontend code is hosted on S3. Amazon S3 is a service that allows storing objects and these objects can be HTML files, JS files, etc. 

Users will access the website from the S3. when the user clicks to get information the request goes through the Amazon API Gateway and in turn it is given to the AWS lambda which contains our working code. AWS lambda runs the code and fetches the information from AmazonDynamoDB and returns that output to API Gateway which in return displays on the user’s screen.

Now, let’s start using these services and creating a serverless crud application.

Prerequisite

  • AWS account
  • Nodejs
  • AWS CLI configuration

Install Serverless Framework

To install Serverless on your machine, run the below-mentioned npm command.

npm install -g serverless

This will install serverless CLI globally on our machine.

Create Nodejs serverless Project

Now make a directory named serverless-user-rest-API and go to the directory and run the below-mentioned command.

serverless create --template aws-nodejs

This will create a boilerplate for the application which consists:

  1. .gitignore: This file is used to tell git which files should be kept outside of the package.
  2. handler.js: This declares your Lambda function. The created Lambda function returns a body with Go Serverless v1.0!
  3. serverless.yml: This file declares the configuration uses to create the service. 

Let’s see the default configuration of serverless.yml and moving forward we will modify the serverless.yml file for configuration and the handler.js file based on the lambda function we require for the application.

It has three sections — provider, functions, and resources.

  1. provider: This section declares cloud provider configuration. You can use it to specify the name of the cloud provider, region, runtime, etc.
  2. functions: This section is used to specify all the functions that your service is composed of. A service can be composed of one or more functions.
  3. resources: This section declares all the resources that functions use. Like database type, table name, etc. Resources are declared using AWS CloudFormation.

Saving Data to Database

Since, we are using dynamo DB to save the record, modify the provider in the serverless.yml file by adding iamRoleStatemements as below

service: serverless-user-rest-api
frameworkVersion: "3"

provider:
  name: aws
  runtime: nodejs12.x
  region: ap-south-1
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:DescribeTable
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
        - dynamodb:UpdateItem
        - dynamodb:DeleteItem
      Resource: arn:aws:dynamodb:ap-south-1:*:*

Now add resources to create dynamoDB table as follow:

resources:
  Resources:
    UsersTable:
      Type: "AWS::DynamoDB::Table"
      DeletionPolicy: Retain
      Properties:
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1
        TableName: "users"

Now install “uuid” and “aws-sdk”using npm command and update the handler.js file and create functions for CRUD operation as below:

npm install uuid aws-sdk

createUser

"use strict";

const AWS = require("aws-sdk");
const uuid = require("uuid");

const dynamoDb = new AWS.DynamoDB.DocumentClient();

module.exports.createUser = (event, context, callback) => {
  const datetime = new Date().toISOString();
  const data = JSON.parse(event.body);

  const params = {
    TableName: "users",
    Item: {
      id: uuid.v1(),
      fullname: data.fullname,
      designation: data.designation,
      createdAt: datetime,
      updatedAt: datetime,
    },
  };
  dynamoDb.put(params, (error, data) => {
    if (error) {
      console.error(error);
      callback(new Error(error));
      return;
    }
    const response = {
      statusCode: 201,
      body: JSON.stringify(data.Item),
    };
    callback(null, response);
  });
};

listUser

module.exports.listUser = (event, context, callback) => {
  const params = {
    TableName: "users",
  };

  dynamoDb.scan(params, (error, data) => {
    if (error) {
      callback(new Error(error));
      return;
    }

    const response = {
      statusCode: 200,
      body: JSON.stringify(data.Items),
    };

    callback(null, response);
  });
};

getUser

module.exports.getUser = (event, context, callback) => {
  
  const params = {
    TableName: "users",
    Key: {
      id: event.pathParameters.id,
    },
  };

  dynamoDb.get(params, (error, data) => {
    if (error) {
      callback(new Error(error));
      return;
    }

    const response = data.Item
      ? {
          statusCode: 200,
          body: JSON.stringify(data.Item),
        }
      : {
          statusCode: 404,
          body: JSON.stringify({ message: "User not found" }),
        };

    callback(null, response);
  });
};

updateUser

module.exports.updateUser = (event, context, callback) => {
  const datetime = new Date().toISOString();
  const data = JSON.parse(event.body);

  const params = {
    TableName: "users",
    Key: {
      id: event.pathParameters.id,
    },
    ExpressionAttributeValues: {
      ":f": data.fullname,
      ":d": data.designation,
      ":u": datetime,
    },
    UpdateExpression: "set fullname = :f, designation = :d, updatedAt = :u",
  };
  
  dynamoDb.update(params, (error, data) => {
    if (error) {
      console.error(error);
      callback(new Error(error));
      return;
    }
    const response = {
      statusCode: 200,
      body: JSON.stringify(data.Item),
    };

    callback(null, response);
  });
};

deleteUser

module.exports.deleteUser = (event, context, callback) => {
  const params = {
    TableName: "users",
    Key: {
      id: event.pathParameters.id,
    },
  };

  dynamoDb.delete(params, (error, data) => {
    if (error) {
      callback(new Error(error));
      return;
    }

    const response = {
      statusCode: 200,
      body: JSON.stringify({}),
    };

    callback(null, response);
  });
};

Add Endpoints

As our functions are ready, lets’ set up the endpoints in serverless.yml file to call those.


functions:
  create:
    handler: src/handler/handler.createUser
    events:
      - http:
          path: users
          method: post
          cors: true

  list:
    handler: src/handler/handler.listUser
    events:
      - http:
          path: users
          method: get
          cors: true

  get:
    handler: src/handler/handler.getUser
    events:
      - http:
          path: users/{id}
          method: get
          cors: true

  update:
    handler: src/handler/handler.updateUser
    events:
      - http:
          path: users/{id}
          method: put
          cors: true

  delete:
    handler: src/handler/handler.deleteUser
    events:
      - http:
          path: users/{id}
          method: delete
          cors: true

Serverless deploy

Now, our application is ready to deploy. To deploy it use “serverless deploy or sls deploy” command. It will take a few minutes based on the internet connection and will generate the endpoints as below

We can also run it invokes these functions locally by using the command “sls invoke local -f getUser”.

Conclusion

In this blog, we understood what is serverless computing and how to use AWS services and serverless framework with nodejs. To learn more about serverless, refer

For more updates on such topics, please follow our LinkedIn page- FrontEnd Studio.