CREATING REST APIS WITH SERVERLESS FRAMEWORK, LAMBDA, AND NODE.JS

The idea of ​​Serverless is that you don't need to worry about server configuration or autoscale anymore. That has been taken care of by the cloud providers, and now you only need to care about the coding. In this article, we will build a simple note taking application using the Serverless framework. To avoid confusion, you don't need a serverless framework to build a serverless application, but it will help you a lot instead of doing it manually on AWS.

REQUEST IN ADVANCE

You need the following to be able to follow this tutorial:

 

AWS Account

Node.js

AWS-CLI configured

GETTING STARTED WITH THE SERVERLESS FRAMEWORK

$ npm install serverless -g

 

This is the command that will download the Serverless cli to your machine. This is a framework that makes it very easy to build applications using AWS Lambda.

 

STEP 1: CREATE A NODE.JS SERVERLESS PROJECT

We are going to create a Notes application. Create folder lambda-notes-app.

 

$ mkdir lambda-notes-app && cd lambda-notes-app

 

We will create a serverless project with the template aws-nodejs:

 

$ serverless create --template aws-nodejs --path note-service --name note

 

The --pathgood part that can be abbreviated -pwill be the location created with the service along with --namethe name of the service being initialized. Here, we will be initializing a folder note-servicewith the following structure:

 

.

├── .npmignore

├── handler.js

└── serverless.yml

 

In these 3 files:

 

.npmignore : This is the file that tells npm which files should not be added to the package.

handle.js : This is the Lambda function declaration file.

serverless.yml : This is the configuration file that the Serverless Framework will use to initialize your service. A serverless.yml file has 3 parts - provider, function and resource

provider: This is the part that declares the configurations according to your cloud provider such as name, domain,...

functions: This is the part that will declare all the functions in your service. A service can have one or more functions.

resources: This is the section that will declare all the resources that your function will use. Resources will be declared using AWS Cloudformation.

STEP 2: CREATE A REST RESOURCE TO CREATE NOTES

Update the file serverless.ymllike below:

 

service: note #

 

frameworkVersion: ">=1.1.0" #

 

provider:

name: aws #

runtime: nodejs14.x #

stage: dev #

region: ap-southeast-1 #

lambdaHashingVersion: 20201221

functions:

noteCreation:

handler: api/notes.create

memorySize: 128

description: Create a note.

events:

- http: # REST API endpoint (API Gateway v1)

path: notes

method: post # HTTP method cho endpoint 

 

Next, create a folder api, delete the file handler.js, create a file api/note.jsand add the following code:

 

"use strict";

 

module.exports.create = (event, context, callback) => {

const response = {

statusCode: 200,

body: JSON.stringify({

message: "Go Serverless v1.0! Your function executed successfully!",

input: event,

}),

};

 

callback(null, response);

};

 

First of all, we will deploy temporarily to test the function:

 

$ sls deploy

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 note.zip file to S3 (249.26 kB)...

Serverless: Validating template...

Serverless: Updating Stack...

Serverless: Checking Stack update progress...

..............................

Serverless: Stack update finished...

Service Information

service: note

stage: dev

region: ap-southeast-1

stack: note-dev

resources: 11

api keys:

None

endpoints:

POST - https://xxx.execute-api.ap-southeast-1.amazonaws.com/dev/notes

functions:

noteCreation: note-dev-noteCreation

layers:

None

 

If the deployment is successful, you will have a message like this, with the endpoint of the form: https://xxx.execute-api.ap-southeast-1.amazonaws.com/dev/notes You can use Postman or curl to make a POST request:

 

curl -X POST https://xxx.execute-api.ap-southeast-1.amazonaws.com/dev/notes

 

will return results

 

{ "message": "Go Serverless v1.0! Your function executed successfully!", "input": {...} }

 

STEP 3: USE WITH DYNAMODB

Edit the provider section in serverless.yml:

 

provider:

name: aws

runtime: nodejs14.x

stage: dev

region: ap-southeast-1

lambdaHashingVersion: 20201221

environment:

NOTE_TABLE: ${self:service}-${opt:stage, self:provider.stage}

iamRoleStatements:

- Effect: Allow

Action:

- dynamodb:Query

- dynamodb:Scan

- dynamodb:GetItem

- dynamodb:PutItem

Resource: "*"

 

Here I will create a NOTE_TABLE environment variable that is based on the service and stage, with the role being able to access and update data for DynamoDB.

 

Next, we will create one more resource that will create the DynamoDB table as shown below:

 

resources:

Resources:

NotesDynamoDbTable:

Type: "AWS::DynamoDB::Table"

DeletionPolicy: Retain

Properties:

AttributeDefinitions:

- AttributeName: "id"

AttributeType: "S"

KeySchema:

- AttributeName: "id"

KeyType: "HASH"

ProvisionedThroughput:

ReadCapacityUnits: 1

WriteCapacityUnits: 1

StreamSpecification:

StreamViewType: "NEW_AND_OLD_IMAGES"

TableName: ${self:provider.environment.NOTE_TABLE}

 

Next we will download 2 more dependencies for the project:

 

$ npm install --save bluebird

$ npm install --save uuid

 

Edit the file api/note.jsas follows:

 

"use strict";

 

const uuid = require("uuid");

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

 

AWS.config.setPromisesDependency(require("bluebird"));

 

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

 

module.exports.create = (event, context, callback) => {

const requestBody = JSON.parse(event.body);

const title = requestBody.title;

const text = requestBody.text;

 

if (typeof title !== "string" || typeof text !== "string") {

console.error("Validation Failed");

callback(new Error("Couldn't create note because of validation errors."));

return;

}

 

createNote(noteInfo(title, text))

.then((res) => {

callback(null, {

statusCode: 200,

body: JSON.stringify({

message: `Successfully created note with text ${text}`,

noteId: res.id,

}),

});

})

.catch((err) => {

console.log(err);

callback(null, {

statusCode: 500,

body: JSON.stringify({

message: `Unable to create note with text ${text}`,

}),

});

});

};

 

const createNote = (note) => {

console.log("Submitting note");

const noteInfo = {

TableName: process.env.NOTE_TABLE,

Item: note,

};

return dynamoDb

.put(noteInfo)

.promise()

.then((res) => note);

};

 

const noteInfo = (title, text) => {

const timestamp = new Date().getTime();

return {

id: uuid.v1(),

title: title,

text: text,

createdAt: timestamp,

updatedAt: timestamp,

};

};

 

Now, you redeploy:

 

$ sls deploy

 

It will create more DynamoDB table. To test the API you can reuse it using cURL:

 

$ curl -H "Content-Type: application/json" -X POST -d '{"title":"Write blog today","text": "About Serverless CRUD API"}' https://xxx.execute-api.ap-southeast-1.amazonaws.com/dev/notes

 

If successful, the following message will be returned:

 

{"message":"Successfully created note with text About Serverless REST API","noteId":"abbb5460-46d2-11ec-a668-6bb284632c9a"}

 

STEP 4: VIEW ALL NOTES

Declare a new function in the file serverless.ymllike below:

 

listNotes:

handler: api/notes.list

memorySize: 128

description: List all notes

events:

- http:

path: notes

method: get

 

Create more functions in the file api/notes.jsas below:

 

module.exports.list = (event, context, callback) => {

var params = {

TableName: process.env.NOTE_TABLE,

ProjectionExpression: "id, title",

};

 

console.log("Scanning Note table.");

const onScan = (err, data) => {

if (err) {

console.log(

"Scan failed to load data. Error JSON:",

JSON.stringify(err, null, 2)

);

callback(err);

} else {

console.log("Scan succeeded.");

return callback(null, {

statusCode: 200,

body: JSON.stringify({

notes: data.Items,

}),

});

}

};

 

dynamoDb.scan(params, onScan);

};

 

Similarly you can test by running cURL test.

 

STEP 5: GET DETAILS OF EACH NOTE WITH ID

Declare a new function in the file serverless.ymllike below:

 

noteDetails:

handler: api/notes.get

events:

- http:

path: notes/{id}

method: get

 

Create more functions in the file api/notes.jsas below:

 

module.exports.get = (event, context, callback) => {

const params = {

TableName: process.env.NOTE_TABLE,

Key: {

id: event.pathParameters.id,

},

};

 

dynamoDb

.get(params)

.promise()

.then((result) => {

const response = {

statusCode: 200,

body: JSON.stringify(result.Item),

};

callback(null, response);

})

.catch((error) => {

console.error(error);

callback(new Error("Couldn't fetch note."));

return;

});

};

 

Now you can test the API with cURL:

 

$ curl -X GET https://xxx.execute-api.ap-southeast-1.amazonaws.com/dev/notes/abbb5460-...

{"createdAt":1637063117990,"text":"About Serverless REST API","id":"abbb5460-46d2-11ec-a668-6bb284632c9a","updatedAt":1637063117990,"title":"Write blog today"}

 

TEST ON AWS

When accessing the API Gateway screen, you can see the endpoints as well as check the functions that have been created.

 

As well as the AWS Lambda display:

 

CONCLUDE

We've created an application that runs completely on AWS, and we don't have to worry about configuring the infrastructure - Serverless takes care of that for us. With Serverless, you don't have to create your own on the AWS Web Console.

Catalog: