From AWS Lambda in Action by Danilo Poccia

When configuring the Amazon API Gateway to use Lambda functions, you can choose to have no authentication and leave the API publicly available. Using this option together with the HTTP GET method allows your API to be called by a web browser. You need to return the expected content type for HTML (text/html) to implement a public website.

Save 37% on AWS Lambda in Action. Just enter code fccpoccia into the discount code box at checkout at

The architectural configuration we’ll be using in our example is in figure 1. The client application is a web browser similar to those on somewhere like AWS Lambda and Amazon API Gateway are used to distribute a public website on the Internet. The advantage this offers compared with static website hosting, such as that available with Amazon S3, is that the Lambda function can generate server-side dynamic content.

Keen to learn more about website hosting? Website hosting is a service allowing organizations and individuals to post websites or web pages to the Internet. If you are searching for a hosting website then sites like do a great job at reviewing and comparing the best hosting services on the market. Whether you’re a big business or a small blog, choosing the wrong host can lead to a lot of complications further down the line and the cheapest plans usually end up having a bunch of hidden fees. You can still take a look at the best cheap web hosting platforms on to see which one would suit you better. Websites are hosted on unique computers called servers. When Internet users want to view your website, all they need to do is type your website address or domain into an web browser. Their computer then connects to your server and your webpages will be delivered to them through the browser. Web hosting is available all over the world, so if you were looking for web hosting in Canada for example, a simple online search for ‘website hosting canada‘ would enable you to find Canadian web hosting solutions.


Figure 1 You can serve web content from a Lambda function using the Amazon API Gateway, and expose the function via HTTP GET. You need to be careful in configuring the integration to return the right HTTP Content-Type, for example “text/html” for HTML.

As an example, let’s create a simple website using Embedded JavaScript (EJS) templates that’re dynamically served to the end-users. I’m using EJS templates in this example, but any kind of server-side technology works. You need a single Lambda function, “ejsWebsite” (listing 1), that you can integrate with multiple resources in the Amazon API Gateway. For example, the root resource of the API (/) and a resource parameter that can be used for any single-level paths (/{path}). Calling this function will return HTML content.

Note This example is available in Node.js only because it’s using EJS templates.

Listing 1 ejsWebsite (Node.js)

console.log('Loading function');

const fs = require('fs');
const ejs = require('ejs');

exports.handler = (event, context, callback) => {  console.log('Received event:', JSON.stringify(event, null, 2));
  var fileName = './content' + event.path + 'index.ejs';  #A
  fs.readFile(fileName, function(err, data) {
    if (err) {
      callback("Error 404");  #B
    } else {
      var html = ejs.render(data.toString());  #C
      callback(null, { data: html });  #D

#A Build a local file name, included in the function deployment package, based on the path in the event

#B If the file’s missing, fail returning a “404” string that the Amazon API Gateway can intercept and manage to return HTTP 404 status

#C Interpret the EJS template server side to produce HTML content

#D Return the HTML wrapped in JSON to preserve encoding

Create the “ejsTemplate” Lambda function using the basic execution role and all default parameters. Because the ejs module’s required, you need to install it with npm and create a ZIP archive to upload your deployment package. In the ZIP archive, include a content folder with some sample EJS templates to be interpreted by the Lambda function. For example, for a tiny website with About and Contact pages, you can include the following files:


Each of these files are described in listings 2, 3 and 4, respectively. As you can see, these files are similar to each other and contain some dynamic content that’s evaluated on the server by the Lambda function before the result is returned by the Amazon API Gateway to the browser.

Listing 2 Root EJS template


    Home Page

Home Page

The home page at <%= new Date() %>


#A The JavaScript code between <%= and %> is interpreted server side by the Lambda function

Listing 3 About EJS template



Home Page

The about page at <%= new Date() %>


#A The JavaScript code between <%= and %> is interpreted server side by the Lambda function

Listing 4 Contact EJS template



Home Page

The contact page at <%= new Date() %>


#A The JavaScript code between <%= and %> is interpreted server side by the Lambda function

This part of the template’s using the EJS template syntax to get the current date and time and replace it server-side:

<%= new Date() %>

Integrating the Lambda functions with the Amazon API Gateway

You now need to integrate this Lambda function with the Amazon API Gateway. From the API Gateway console, create a “Simple Website” API. This won’t be a normal Web API, but it’ll be used as a public website.

Create a Method for the root (/) resource. Your code needs to answer only the HTTP GET request for this simple website; choose the GET method from the menu. You can easily extend this example to support other HTTP verbs such as POST.

In the Integration Request, select the “ejsTemplate” Lambda function you created and create a Mapping Template to send the “path” to the function. Use the application/json content type and this basic static mapping template:

  "path": "/"

You now need to change the default content type returned by the API call to text/html. In the Method Response, expand the “200” HTTP Status, add the text/html content type with an Empty Model and remove the default application/json content type. Use the code in listing 5 as a mapping template for the text/html content type:

Listing 5 Mapping template to return the content of the data attribute

#set($inputRoot = $input.path('$'))

I’m using a “data” attribute in the JSON payload that’s returned by the function to embed all the HTML content; this template’s extracting the HTML to be the only content returned. If you return HTML content directly, it’s escaped with HTML entities and difficult to use. You can use the Test button to check whether the content and the content type returned by the integration are correct.

A website with a home page is now implemented, but let’s create another integration to manage all single-level paths, such as /about or /contact. Create a new resource with “Page” as the name and {page} as resource path and add a GET method to this resource as well. In the Method Request, you now have the “page” resource path parameter. In the Integration Request, use the same “ejsTemplate” function as before, but in the mapping template for the application/json content type, use the following template to pass the “page” parameter to the function:

  "path": "/$input.params('page')/"

In the Integration Response, replace the default application/json content type with text/html and use the same mapping template as before, using the code in listing 5.

Now you have to manage possible requests that don’t find content (a corresponding EJS template in this case) within the function. To do that, in the Method Response, add a 404 HTTP Status. In the Integration Response, use Add integration response with 404 as Lambda Error Regex to return the 404 HTTP code, in case the page (the EJS template) isn’t found in the content folder of the Lambda function.

Use the Test button to try different “page” values, for example “about/” or “wrong/,” to see what happens when the EJS template’s found or not found.

When you deploy this API in a stage (for example “home”), the website’s publicly accessible and you can navigate through the links using a web browser. Remember to use the full path, the domain, and the stage; for example, (the domain will be different in your case):

The dates in the EJS templates are evaluated on the server, and you should see the dates being updated as soon as the browser accesses the link again.

If you want to learn more about the book, check it out on liveBook here and see this slide deck.