Increasingly Functional.
by Joshua Miller | on Twitter | on the web | on github

Serverless Web Applications on AWS Lambda with ClojureScript Part III

July 5th 2017

Tagged: clojure clojurescript

In part one and part two of this series, we looked at how to build a ClojureScript environment that we can deploy to AWS Lambda and the API Gateway that will respond to the outside world as a web service. With that done, today we'll see how to go about deploying it.

First, you'll need to have an Amazon AWS account, with your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables set. Doing that is exhaustively documented elsewhere, so I'll skip it. Instead, let's dig into our project.clj, which has a number of things we need to set up to take care of deployment.

Under the :cljs-lambda key, we first need to set a role for our function that will define the permissions it has to access various AWS resources on our behalf. If you have other Lambda functions already deployed, you'll have roles you've defined for them before; if not, you can run lein cljs-build default-iam-role. In either case, what you need is the resulting ARN (that looks like arn:aws:iam::123456789012:role/lambda_basic_execution); replace "FIXME" as the :role with that ARN.

Next, change the function :name to "JWTify" (this is the human-readable name our function goes by) and :invoke to jwtify.core/jwtify. You should now be able to run lein cljs-lambda deploy, and cljs-lambda will compile your ClojureScript, upload it to AWS, and create the associated Lambda function:

Compiling "target/jwtify/jwtify.js" from ["src"]...
Successfully compiled "target/jwtify/jwtify.js" in 3.761 seconds.
Writing index to /Users/josh/projects/jwtify/target/jwtify/index.js
Writing zip to /Users/josh/projects/jwtify/target/jwtify/jwtify.zip
Adding files from /Users/josh/projects/jwtify/target/jwtify
Adding files from /Users/josh/projects/jwtify/node_modules
aws lambda get-function-configuration --function-name JWTify
aws lambda create-function --role arn:aws:iam::123456789012:role/lambda_basic_execution --output text --runtime nodejs4.3 --zip-file fileb:///Users/josh/projects/jwtify/target/jwtify/jwtify.zip --query Version --handler index.jwtify_core_SLASH_jwtify --function-name JWTify
$LATEST

The last step is connecting our new Lambda function to the web. Log into the API Gateway, click on "APIs", then "Create API", enter "JWTify" as the API name, and then click "Create API." You should be in your new Gateway's settings page, where you can then click the "Actions" dropdown, select "Create Resource", click the "Configure as a proxy resource" checkbox (this will just forward everything from the web request directly to our Lambda function), and click "Create Resource." You'll then be in a proxy setup screen. Your integration type should be "Lambda Function Proxy" (of course), then you can select the region your Lambda function was deployed to (either "us-east-1" by default or whichever region you've configured your account to use; you can also set this in project.clj), and finally enter "JWTify" in the Lambda Function text input. "JWTify" should autocomplete there; if it doesn't, you might not have the right region selected. Click "Save," then confirm the request for the API Gateway to invoke your function.

Now you should have a testable API Gateway/Lambda integration. Click the "Test" button, and create a POST to /sign with a request body like {"payload": {"test": "payload"}, "secret": "secret"}. Testing that should return a 200 status with a JWT token in the response body. If it works, you're ready to deploy. Click the "Actions" dropdown, then "Deploy API." Give your deployment stage a name ("test" works fine here), and then click "Deploy." The result will be a deploy URL you can test with CURL on the command line, like so:

$ curl -X POST "https://jlfyyiazkb.execute-api.us-east-1.amazonaws.com/test/sign" -d '{"payload": {"test": "payload"}, "secret": "secret"}'
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoicGF5bG9hZCIsImlhdCI6MTQ5OTI3NTIxOX0.EIQ7rE3wVriPoGttsOEmlnqKcoAFTXTjHoQU_xq2KF8"}

So there you go. Given a few ClojureScript functions and a not-oppressive amount of clicking around Amazon's AWS console, we've deployed a maintenance-free web service. Lambda's pricing structure (free for 1M requests and 3.2M seconds of compute per month) makes this an ideal way to get up and going when you need a small web service behind a larger application. The autoscaling makes it ideal for web services whose traffic is bursty and unpredictable — when unused, you pay nothing; when traffic spikes, Lambda will just keep launching instances to keep up.

I hope this series has been useful for those looking to get started on Lambda with ClojureScript or those who've gotten stuck at one point or another. When I first went down this path, it seemed simple and straightforward enough, but there were lots of small gotchas and sharp edges around Amazon's documentation, and this series represents the accumulated experience of finding the right path around all of it. All of the code from the sample project is on Github. Enjoy!