How to Deploy JavaScript & Node.js Applications to AWS Lambda

September 14, 2017
Written by
Maciej Treder
Contributor
Opinions expressed by Twilio contributors are their own

AWS Lambda logo

Functions-as-a-Service (FaaS) such as Amazon Web Services Lambda and Twilio Functions can be cheap execution environments in which you pay only for resources used to deal with a particular request, typically measured in seconds or milliseconds.

Today we will take a look at deploying a JavaScript Node.js application to AWS Lambda. Our application will keep a list of our best friends which we would like to invite for a birthday party. In this post we will:

  • Set up an npm project and create a ‘hello world’ JavaScript application.
  • Refactor the app to collect and keep friends names in memory.
  • Set up a serverless framework configuration and deploy the application on Lambda environment.

To accomplish tasks in this post we will need:

  • Node.js and npm installed
  • Access to the package manager (for this post purpose I am using Linux and apt-get)

Configuring our environment & app

Let’s start with a simple ‘hello world’ application. First we need to initialize an npm project and install common libraries:

mkdir myServerlessApp
cd myServerlessApp
npm init
npm install nodemon --save-dev
npm install express --save

Now create our application with a “hello world” page:

mkdir src 
touch src/server.js

Inside server.js we will:

  1. Initialize our express app
  2. Define our app’s URL and port
  3. Add a simple “hello world” message to the home page
  4. Invoke express

To do that, paste this code into server.js:

const express = require('express');
const app = express();

const port = process.env.PORT || 8000;
const baseUrl = `http://localhost:${port}`;


app.get('/', (req, res) => {
   res.status(200).send('hello world!');
});

// Server
app.listen(port, () => {
   console.log(`Listening on: http://localhost:${port}`);
});

We need to create a startup script in the npm configuration file – package.json:

"scripts": {
 "start": "nodemon src/server.js"
},

We can now run our application by typing in the command line:

npm start

Output from this command should be:

npm start

> myserverlessapp@1.0.0 start /path/to/your/project
> nodemon src/server.js

[nodemon] 1.11.0
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node src/server.js`
Listening on: http://localhost:8000

After navigating to http://localhost:8000 in the browser, you should see your “hello world!” message:

Screen Shot 2017-07-02 at 5.35.16 PM.png

Find all the code up to this point in this GitHub repository you can clone:

git clone git@github.com:maciejtreder/myServerlessApp.git
cd myServerlessApp
git co firstStep
npm install 

Building the friends list feature

Now we are going to add basic functionality for our user to specify the name of their friend and click an ‘add’ button to add that person to the list of friends.

Let’s add the dependencies which we will use in our application:

  • body-parser – request parser middleware
  • ejs – embedded JavaScript templates (to control flow of our response, similar to php – mixing code snippets with html)

Run this command to install them:

npm install body-parser ejs --save

Add a InMemoryFriends class in server.js to keep the list of people in memory:

class InMemoryFriends {
   constructor() {
       this.list = [];
   }

   add(name) {
       this.list.push(name);
   }

   getAll() {
       return this.list;
   }
}
const friendsList = new InMemoryFriends();

Now let’s create two views for our app:

mkdir views
touch views/index.html
touch views/person-added.html

Add this code to index.html file:

<form action="submit" method="post">
   My best friend is: <input type="text" name="friendName" />
   <input type="submit" value="Add" />
</form>

<p>Actual friend list:</p>
<ul>
   <% personList.forEach(function(personName){ %>
   <li>
       <%= (personName) ? personName : '' %>
   </li>
   <% }); %>
</ul>

Add this code to person-added.html file:

<p>Added person: <%= (personName) ? personName : '' %> </p>
<a href="/">Add another</a>
<br/><br/>
<p>Updated list:</p>

<ul>
   <% personList.forEach(function(personName){ %>
   <li>
       <%= (personName) ? personName : '' %>
   </li>
   <% }); %>
</ul>

Now we are going to add a render engine, set the view engine, specify the directory in which we are keeping our views and configure our new routes. Add the following lines to server.js anywhere after declaration of the app variable:

const bodyParser = require('body-parser');

app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
app.set('views', 'views');


app.use(bodyParser.urlencoded({ extended: true }));

app.get('/', (req, res) => {
   res.render('index', {personList: friendsList.getAll()});
});

app.post('/submit', (req, res) => {
   friendsList.add(req.body.friendName);
   res.render('person-added', { personName: req.body.friendName, personList: friendsList.getAll() });
});

At this moment the application should look as follows:[a][b]

Screen Shot 2017-07-02 at 7.40.00 PM.png

Screen Shot 2017-07-02 at 7.40.25 PM.png

If you want to catch up to this exact step, run the following commands:

git clone git@github.com:maciejtreder/myServerlessApp.git
cd myServerlessApp
git co secondStep
npm install 

Set up Serverless and deploy your app

Our app is ready, so we can now deploy it. Let’s install the Serverless framework and create a config file:

npm install serverless --save-dev
npm install aws-serverless-express --save
touch serverless.yml
touch lambda.js
touch local.js

Prepare an entry point of our lambda function, lambda.js:

const awsServerlessExpress = require('aws-serverless-express');
const app = require('./src/server');
const server = awsServerlessExpress.createServer(app)


module.exports.universal = (event, context) => awsServerlessExpress.proxy(server, event, context);

We need also to edit our server.js file. Replace these lines:

app.listen(port, () => {
   console.log(`Listening on: http://localhost:${port}`);
});

With:

module.exports = app;

But wait, aren’t those lines necessary to run our app?! Yes, there are.

To keep our app running locally we can place them in a local.js file:

const app = require('./src/server.js');
const port = process.env.PORT || 8000;

// Server
app.listen(port, () => {
   console.log(`Listening on: http://localhost:${port}`);
});

And edit the start script in package.json:

"scripts": {
 "start": "nodemon local.js"
},

Install AWS CLI (steps below are for Linux OS, AWS also supplies guides to install the CLI on other platforms):

sudo apt-get install python-setuptools python-dev build-essential -y
sudo easy_install pip -y
sudo pip install --upgrade virtualenv
sudo pip install awscli

After that you need to configure aws cli:

aws configure

You will be asked to provide following values:

  • AWS Access Key ID
  • AWS Secret Access Key
  • Default region name
  • Default output format

Two first are most important, you can find them in the ‘My Security Credentials’ section, after logging into your AWS account:

Screen Shot 2017-07-02 at 8.22.59 PM.png

Screen Shot 2017-07-02 at 8.23.15 PM.png

The third parameter is your default Amazon region, you can leave it default for now, as long as we are going to define region in the Serverless configuration. The fourth one is the format in which you want to receive output from CLI execution and it is not important in our case so you can leave it blank.

When the AWS CLI configuration is done, set up the serverless configuration. Place the following lines in the serverless.yml file:

service: my-serverless-app

provider:
 name: aws
 runtime: nodejs6.10
 memorySize: 128
 timeout: 10
 stage: production
 region: eu-central-1

functions:
 api:
   handler: lambda.universal
   events:
     - http: ANY {proxy+}
     - http: ANY /


Add deploy script to package.json:
"scripts": {
 "start": "nodemon local.js",
 "deploy": "serverless deploy"
},

And deploy our function:

npm run deploy

If you have done everything correctly you should see output like:

npm run deploy

> myserverlessapp@1.0.0 deploy /path/to/your/project
> serverless deploy

Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (929.36 KB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..............
Serverless: Stack update finished...
Service Information
service: my-serverless-app
stage: production
region: eu-central-1
api keys:
  None
endpoints:
  ANY - https://1v0xvsd7pc.execute-api.eu-central-1.amazonaws.com/production/{proxy+}
  ANY - https://1v0xvsd7pc.execute-api.eu-central-1.amazonaws.com/production
functions:
  api: my-serverless-app-production-api

After invoking link from serverless command output (in my case: https://1v0xvsd7pc.execute-api.eu-central-1.amazonaws.com/production), you should see your app deployed on AWS Lambda.

But after trying to add friend to my list I am getting a 403 ({"message":"Forbidden"}) error!
You are getting this because the entry point to your application has a /production prefix. When you are adding new friend to the list Node.js application is trying to reach URL https://1v0xvsd7pc.execute-api.eu-central-1.amazonaws.com/submit (not /production/submit).

You have two ways to solve this issue:

  1. You can change “action” attribute in the index.html view to:
<form action="production/submit" method="post">
  • You can use a custom domain:

Here you can find a step-by-step guide to setup a custom domain to point to your application.

If you want to catch up this final step in the tutorial run the following commands:

git clone git@github.com:maciejtreder/myServerlessApp.git
cd myServerlessApp
git co thirdStep
npm install 

Now we’re all set with our Node.js application on Lambda!

Next Steps

With few simple steps we created the Node.js application and deployed it to the function-as-a-service platform AWS Lambda.

Live demo of this application can be found here: my-serverless-app.maciejtreder.com

And repo here: https://github.com/maciejtreder/myServerlessApp 

Let me know what you build with this base application. My contact info:

contact@maciejtreder.com
https://www.maciejtreder.com
@maciejtreder (GitHub, Twitter, linkedIn)