Building a serverless web application

I’ve built a few APIs using AWS Lambda and NodeJs (Micro-micro-services? Nano-services) and found the process pretty enjoyable, especially once I discovered the Serverless Framework which takes away the pain of setting up API Gateway and automates your deployment process. (Copy-pasting code into the AWS console is not the way forward, and CloudFormation is a little intimidating!)

I thought I would see how easy it would be to build an actual web application (With a UI) on Serverless. Here’s how that process went…

The application

You can see the end result by visiting https://www.showmeadog.net/ (Spoiler alert?). I’d just come across the Dog API and what better use of the internet than looking at animal photos?

The app has two pages — one that shows a list of all the breeds of dog that are available, and one that shows a photo of a chosen breed. The list page will link to the photo page.

First steps

I used Serverless Framework and AWS Lambda along with NodeJs. You can of course use Serverless with other programming languages and other cloud providers, but these are the two I’m most familiar with.

I’m assuming NodeJs and NPM are already installed.

The first thing to do is install Serverless:

npm install -g serverless

Then we can get an empty project by running:

serverless create --template aws-nodejs --path show-me-a-dog
cd show-me-a-dog

This will create the serverless.yml configuration file and a handler.js that contains the entrypoint for Lambda.

Credentials

Serverless has instructions on their website of how to set up your AWS credentials.

One disadvantage of Serverless to my mind is that it needs quite a high level of permissions and it’s not that easy to work out exactly what set is needed for a particular application (It varies based on what exactly your functions are doing).

The simplest way forward is to give Serverless “Full Access” — it’s a good idea to create a separate IAM user to do that with.

Add some dependencies

We have access to the full set of Node APIs, but there’s also a lot of useful libraries out there on NPM, so let’s get set up to use them:

npm init

You can keep most of the defaults here. Now we can npm install anything that seems useful, like the Handlebars templating language and an implementation of the Fetch API:

npm install --save handlebars
npm install --save isomorphic-unfetch

When you deploy your app, Serverless will package up everything including your node_modules and push it all up to the cloud.

Offline Testing

Something that might seem a little awkward about Lambda is the inability to test your functions offline. You can of course write unit tests around your handler function, but Serverless also has a plugin system and a very useful example is serverless-offline. This can be installed via NPM and is easy to configure in serverless.yml by adding a couple of lines of config:

plugins: 
- serverless-offline

Now running serverless offline will start a server on port 3000 that you can open up in a browser (Or via curl etc).

Now we’re ready to add some functionality to that empty handler file…

Write some code!

Let’s start with the list page. You can take a look at the full source code on GitHub — I won’t recreate everything here.

First we need a little configuration:

functions:  
list:
handler: handler.list
events:
- http:
path: /
method: get

This sets up a mapping between the root URL of the Lambda and a function called list in the handler file. That function looks like this:

It’s pretty simple:

  • Grab the data from the API
  • Pull the list of breeds from the JSON response
  • Call Handlebars with this data
  • Return the HTML

You can see the full source code of handler.js on GitHub. The “show” function is pretty similar, but the configuration is a little different to allow passing in a parameter:

path: /{breed}

That param is available in the handler by calling:

const breed = event.pathParameters.breed;

Styling

With both functions built, we have a functional but not particularly attractive application.

It would be possible to host some CSS on S3 and reference that, but it adds an extra deployment step outside of the Serverless environment. For a small app like this I decided to embed the CSS inside the HTML output.

To make things a little more manageable I put the CSS into a seperate file, and then used the uglifycss module to minify it before sending it to Handlebars to be placed into the HTML document:

const css = uglifycss.processFiles(['style.css']);

Deployment

OK, now we’re ready to deploy our application up to the AWS cloud. This is nice and easy:

serverless deploy

Done! Serverless will output a URL — copy & paste this into a browser and you should see the app.

For extra credit, it’s possible to set up CodeBuild and CodePipeline to build all new commits automatically.

Adding a custom URL

The default URL that Lambdas are hosted at isn’t especially user-friendly. Luckily it’s possible to map the function to a custom address with the following steps in the AWS console:

  • First, register a new domain name with AWS (Or use one you already own — they can be imported from other providers)
  • The domain must support HTTPS — AWS provides ACM (Which is free!). The ACM cert needs to be created in the us-east-1 region, no matter which region your function is hosted in. (e.g. My function is in the Canada region and this still works fine)
  • Go to the API Gateway console for your function and select “Custom Domain Name”. Follow the steps here. Also be sure to grab the CloudFront distribution ID for your function. You’ll need it in the next step.
  • Create a Route 53 record set: You’ll need an “A” link type with ALIAS to the CloudFront distribution from the last step.

You should now be able to access your Lambda via your chosen URL, and anyone who doesn’t know better will have no idea how it’s hosted!

What’s Next?

We’ve seen how simple it can be to set up your application to be hosted without servers. This method is great for small applications like the example here but it might not scale.

Luckily there are some projects out there that attempt to give this level of simplicity but with more flexibility. One example is the Architect Framework which sounds like it could be perfect!

I plan to try and rewrite this application with Architect to see just how well it works. Stay tuned…