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.