Menu

Serverless content management with NetlifyCMS and AWS Lambda (Part 1)

In 2018 I decided to see how I could take my personal website and turn it into a serverless function. My goal for this project was to create a content managed, serverless website - and host it for free.

In this guide I'll show you the process I went through to stand up this serverless website, install a content management system, and deploy it to AWS - all for only the price of the domain name.

What is Serverless?

Serverless is a buzzword that is used a lot in the tech industry. For me, the value of hosting applications in a serverless environment comes in two forms:

  1. Low maintenance, and
  2. Low cost (pretty much free)

Systems built using Serverless environments can be limited in their functionality, but the upside of never having servers that go down is great for me.

There are some great tools on the market to help you host web applications in a serverless environment, the most popular being Netlify.

My goal however was to see if I could do this without using an off the shelf product, and understand the process that I need to go through along the way.

What tools are we using?

This is a very hands on tutorial, and you will need some knowledge of node.js in order to get it all working.

In this tutorial you will need access to the following:

  • Amazon AWS (API Gateway / Lambda)
  • GitLab (Code Storage and CI/CD)
  • Netlify
  • Node.js / NPM

You can sign up for all of these completely free. Once you have your accounts you can move on to the next step.

Installing Node.js and NPM in your environment

To install Node.js on Mac follow this guide. On Windows go here. For other operating systems, you’re on your own.

From here on out this guide assumes you’ve got node and npm running and updated to the latest version.

Creating your Application

Open up your favourite terminal, make a new directory for your application and cd into it:

$ mkdir sample-website
$ cd sample-website

Now you can use the npm init script to create your project.

$ npm init --yes
Wrote to /Users/jordan/Projects/sample-website/package.json:

{
  "name": "sample-website",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Creating a simple web application

We’re going to use Express.js as the basis for our web application in this example.

To install Express.js as a dependency to your project run the following command:

$ npm install express --save

In your favourite text editor, create a file called index.js as the entry point for this application.

//index.js
const express = require('express');
let app = express();

// respond with "hello world" on the homepage
app.get('/', (req, res) => {
   res.send('hello world');
});

app.listen(3000, () => {
   console.log('Example app listening on port 3000!');
});

You can now run this app by executing the following command in your console:

$ node .

You should expect to see the following output in your console:

`Example app listening on port 3000!`

And then browsing to http://localhost:3000 on your browser will reveal:

Local node JS application showing Hello World

Congratulations, you’re now running a simple web application in node.js!

Adding a View layer to your application

Before we can integrate our CMS we need to be able to create user interfaces (views) in our application that are separate from our application logic.

To allow you to build views easily there are a number of templating languages that are supported by Express.js; my personal favourite is Handlebars.js.

To add handlebars support to your application, you need another dependency:

$ npm install express-handlebars --save

You then need to create a few directories that will be used by handlebars:

$ mkdir views
$ mkdir views/layouts

The layouts directory is for the main layout of the application and will include your main css, js and other front end dependencies.

All other views will exist in the views directory.

Set up your views

Create the following files that will be rendered by your application:

<!-- views/layouts/main.hbs -->
<!doctype html>
<html>
    <head>
        <title>Sample Serverless Web App</title>
    </head>
    <body>
        {{{body}}}
    </body>
</html>

We can make this layout more feature rich later on. At this point a basic HTML layout is fine.

<!-- views/index.hbs -->
<p>Hello World!</p>

We’ll connect these files up in the next step.

Configure your view engine

To configure handlebars as your view engine you’ll need to modify your index.js as follows:

//index.js
const express = require('express');
const exphbs = require('express-handlebars');

let app = express();
let hbs = exphbs.create({
   defaultLayout: 'main',
   extname: '.hbs',
   layoutsDir: `${__dirname}/views/layouts`
});

//Set the view engine
app.engine('hbs', hbs.engine);
app.set('view engine', 'hbs');

// render the main.hbs layout and the index.hbs file
app.get('/', (req, res) => {
   res.render('index');
});

app.listen(3000, () => {
   console.log('Example app listening on port 3000!');
});

Run your application again using the following command:

$ node .

And browse to http://localhost:3000 to see the result:

Sample Serverless Web App

Hello World… with Handlebars!

Great, you’re now running your application using Handlebars.js as your view engine.

Prepare for Serverless deployment

For our serverless infrastructure we're going to use AWS Lambda and proxy the requests through AWS API Gateway.

There are lots of ways to deploy services to lambda, but for this tutorial we're going to use a package called Claudia.js.

Claudia.js automates the full deployment cycle of your code to create AWS API Gateway endpoints and associate them automatically with lambda functions. This significantly improves the ability to deploy services to lambda as Claudia takes care of the tricky parts for you.

To install Claudia.js run the following command:

$ npm install claudia -g

To set up Claudia we first need a user in AWS with appropriate permissions, and a .aws/credentials file in our home directory.

The setup instructions can be found on the claudia.js website. Once you've got your .aws/credentials file created with an appropriate client id/secret, you can continue.

Update our application to use Lambda

We need to make a few changes to our index.js file to allow lambda to handle the requests instead of the traditional listener.

First, comment out the listen functionality and add a module.exports line at the bottom:

//index.js
const express = require('express');
const exphbs = require('express-handlebars');

let app = express();
let hbs = exphbs.create({
   defaultLayout: 'main',
   extname: '.hbs',
   layoutsDir: `${__dirname}/views/layouts`
});

//Set the view engine
app.engine('hbs', hbs.engine);
app.set('view engine', 'hbs');

// render the main.hbs layout and the index.hbs file
app.get('/', (req, res) => {
   res.render('index');
});

// app.listen(3000, () => {
// &nbsp;&nbsp;&nbsp;console.log('Example app listening on port 3000!');
// });

module.exports = app;

Next run the following command from the console to generate your proxy stub.

$ claudia generate-serverless-express-proxy --express-module index

You should now see a file called lambda.js in your filesystem. This file is the entrypoint for the function when running inside the lambda. You should not edit this file unless you know what you are doing.

Lastly, we need to create and deploy our function in AWS. To do this we use the following command:

$ claudia create --handler lambda.handler --deploy-proxy-api --region ap-southeast-2
packaging files    npm install -q --no-audit --production

added 71 packages in 693ms

1 package is looking for funding
validating package    npm dedupe -q --no-package-lock
saving configuration
{
  "lambda": {
    "role": "sample-website-executor",
    "name": "sample-website",
    "region": "ap-southeast-2"
  },
  "api": {
    "id": "nle518nwfa",
    "url": "https://nle518nwfa.execute-api.ap-southeast-2.amazonaws.com/latest"
  }
}

After a short time you should see a generated URL appear. Browsing to that URL will render your website:

Sample website hosted on lambda

You've now deployed your serverless website to Lambda!

Note on timeouts

Lambda functions by default have a runtime of 3 seconds. Given the way that lambda works there are times when the initial load after a deployment will exceed this. To get around it you can update the default timeout to something like 10 seconds to be more fault tolerant.

Update lambda config to use 10s timeout

In the next post I'll show how to add the Netlify CMS to this and render your content from your serverless CMS.