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:
- An AWS account
- 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:
- Initialize our express app
- Define our app’s URL and port
- Add a simple “hello world” message to the home page
- 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:
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 middlewareejs
– 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]
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:
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:
-
You can change “action” attribute in the
index.html
view to:
<form action="production/submit" method="post">

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)