Render one file HTML page with AWS API Gateway+Lambda using node.js with PUG

In this brief article, I will describe how to use AWS Lambda with AWS API Gateway to render HTML pages with embedded CSS and how to do this using node.js.

Why would you need a single HTML file in the first place?

Before we start, let’s answer this question. And let’s do it with a story. If you don’t like stories - just skip to the next paragraph… (but seriously - you don’t like stories!?)

Last month, I decided to improve the way our clients monitor progress in their projects. Until now, the only way to do that was to track changes in Jira - the software we use to manage projects in SCRUM. Jira is not the easiest tool to use, so our clients have had problems with it. To deal with that issue, our UX team created a design for a beautiful and usable report. The idea was to send this report periodically to our clients. A couple of hours after receiving the designs, I implemented them into a service serving single HTML files.

So we had the microservice ready to be deployed. The first approach was: let’s dockerize it and deploy it in this form. But then I thought… hmmm…. there is something like AWS Lambda - maybe I can use it… And I did. It took some time to set up Continuous Integration and connect Lambda with API Gateway properly. All those things I will describe in this article where, step by step, we will deploy a similar service. End of the story - let’s start coding.

Github repository with the app

I extracted the code responsible for rendering single HTML pages and I created from it an application boilerplate. The code can be found here: https://github.com/SoftwareBrothers/aws-lambda-static-pageJust clone it, install dependencies and check if it runs. The entry point for the entire system will be (index.js)[https://github.com/SoftwareBrothers/aws-lambda-static-page/blob/master/index.js] file and its `handler` function.

Take a look at the readme file - maybe you will have to use Docker to install dependencies.

Create a Lambda function

After downloading the repo - you have to create a new lambda function which will run it. Go to https://eu-central-1.console.aws.amazon.com/lambda/home. Give function name, pick latest Node.js (8.10) as a runtime and then create a new Role or use an existing one. The role will give your function the ability to use other AWS resources - like S3, SQS or whatever you like. In this example, I pick ‘new role’ and set its name to `testLambda`. I won’t use any external resources so I leave permissions field blank. Then hit `Create function` and after a couple of seconds, you will see the function editor. Memorize ARN of your function - we will need this later in the deployment process.


Deploy your function

Of course, you can use the editor (which has quite awesome functions like multi-selection) but in this example, we will deploy the code which we already cloned from GitHub.To deploy the function you have to use (aws-cli)[https://aws.amazon.com/cli/]. Install it and next you will have to create AWS IAM User who has rights to deploy the code.
Go to https://console.aws.amazon.com/iam/home#/users and create a new user. Give it a proper name (i.e. lambdaDeployer) and select Programmatic Access:


In the next screen select: Attach existing policy directly and then press create policy, Pick JSON tab and write following policy:

"Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "lambda:UpdateFunctionCode",
                "lambda:UpdateFunctionConfiguration", 
                "lambda:InvokeFunction",
                "lambda:GetFunction"
            ],
            "Resource": ["ARN_ADDRESS_OF_YOUR_LAMBDA_FUNCTION"]
        }
    ]
}```


Replace `ARN_ADDRESS_OF_YOUR_LAMBDA_FUNCTION` with your function ARN (mentioned above).

Give the policy a name: i.e. lambdadeploypolicy, and go back to add user page. You have to refresh policies list and then attach a newly created policy to the user. Confirm it and proceed to the end.AWS will create the user and gives you the access_key and secret_access_key.
After that configure aws cli to use those credentials using aws configure command

$ aws configure

After setting up credentials you are ready to deploy the code:

$ cd aws-lambda-static-page
$ zip -r lambda.zip ./*
$ aws lambda update-function-code --function-name FUNCTION_NAME --zip-file fileb://lambda.zip

Replace FUNCTION_NAME with the name you gave to the function

After a successful upload you can test the function:

$ aws lambda invoke --function-name FUNCTION_NAME out.html

By the way, we use this script in your Continuous Integration process.

Connect Lambda function with AWS API Gateway

In the previous paragraph I described how to deploy your app to the server. Now let’s attach this function to the real URL. In order to do this, we will utilise AWS API Gateway. Go to https://eu-central-1.console.aws.amazon.com/apigateway/home and Create Api.Give API a name and create it.

Under the resource menu add a new GET Method. In the action setup pick Lambda function as an integration type and find your newly created function. Confirm adding permission popup.

By default, API Gateway sends responses in JSON. We have to change that. Click Method Response block. Then remove application/json from response body and add header Content-Type:


In the Integration response block change: Header Mappings value for Content-Type to 'text/html; charset=utf-8' delete Mapping template for application/json and add template for text/html and put $input.path('$') in it. This will prevent content to be escaped by AWS Gateway.


Next, you have to deploy your API: Actions/Deploy API. Create a new stage: let’s say production and this is it. Now you should see Invoke URL in the stage page which is the link to your new API.

Summary

In our case using AWS Lambda helped a lot. Deployment is super easy and there is no server in between, at least you don’t see it. I totally recommend it in similar use cases.