DEV Community

Mark Aiken
Mark Aiken

Posted on

CLJS ❤️'s Lambda

What is Clojure and why do you care?

Because every post about Clojure or Clojurescript (CLJS) has to explain what Clojure(script) is and why you should use it:

But in all seriousness a bunch of people have done better introductions to Clojure then this post will:

🔥Serverless🔥

Unfortunately there isn't an amazing parody commercial for Serverless so instead let's just try to actually motivate why you should care about it. Serverless is just a shorthand for describing a way to run code without worrying about infrastructure. What does that mean practically? Give them and zip and they throw it on some unused hardware, that's it!

It supported on AWS, Google Cloud, and Azure and probably other clouds. Because the examples are built with AWS that is where we will focus.

Probably the biggest motivator for migrating to or build new services with serverless is cost. The the free tier is very generous and it's pay as you go so it's free to prototype!

Eyes popping

You also get ease of deployment/operation and native integration with your cloud provider making it super easy to tap into services like cloud provided DBs, metrics/analytics, queues, email services, etc.

CLJS + Lambda

While there exist ways for you to plug together Clojurescript and Lambda they are coupled to specific build tooling and are a bit too much like a black box for my liking. This also is a good excuse to learn how all the pieces plug together. This post is a distillation of how to do just that. Before finally getting to the meat of the post we have to introduce one more player to the stage.

Shadow-CLJS

Shadow-CLJS is an alternative frontend to the Clojurescript compiler, it is fills the same role as lein-cljsbuild or Figwheel-main if you are familiar with the space. The big advantage it has over the others tools is awesome npm support. We can use basically all of npm with CLJS via Shadow which is important if we want to leverage things like AppSync or the AWS Node SDK.

MAKE THE DAMN LAMBDA ALREADY

Setup

Okay to being lets make a lambda, eventually we will throw away everything inside of it, but for now just leave it be.

And with our basic lambda up we just need our tools and some example code, first the tools:
brew install yarn #Or your OS equiv, apt-get, yum, etc
And that's it!

Example Code

For the example code I have forked Chenyong's minimal Shadow + Node example to create a hello world lambda with Shadow:

GitHub logo royalaid / minimal-shadow-cljs-nodejs

shadow-cljs hot code swapping for Node.js

Node.js example for shadow-cljs

Develop

Watch compile with with hot reloading:

yarn
yarn shadow-cljs watch app

Start program:

node target/main.js

REPL

Start a REPL connected to current running program, app for the :build-id:

yarn shadow-cljs cljs-repl app

Build

shadow-cljs release app

Compiles to target/main.js.

You may find more configurations on http://doc.shadow-cljs.org/ .

Steps

  • add shadow-cljs.edn to config compilation
  • compile ClojureScript
  • run node target/main.js to start app and connect reload server

License

MIT






git clone https://github.com/royalaid/minimal-shadow-cljs-nodejs.git /tmp/lambda-example
cd /tmp/lambda-example
yarn

and now all of your deps are setup!

Code Breakdown

While the project should be pretty self explanatory I will given a high level overview of the important bits, the shadow-cljs.edn and src/server/main.cljs files.

shadow-cljs.edn

The shadow-cljs.edn is the equivalent of project.clj for lein or boot.clj for boot, its the build configuration file for Shadow. The most important part of that file for us is the :target :node-library line which tells shadow to setup module.exports for use by node, this is bridge between the CLJS code with write and the Javascript that Node reads. The second most important line is :exports {:handler server.main/handler}. This line tell Shadow what to call the export and then what CLJS function to bind to it, in our case the handler function in the Clojurescript namespace server.main. This function is what AWS will end up calling when we execute the lambda.

main.cljs

As for the src/server/main.cljs, this is what will eventually be run by AWS after Shadow transpiles it for us. The lone function inside of the namespace is what actually invoked. Important note, the function signature has to have 3 args (2 if using async/await, which we aren't) and must "return" its value by then invoking the cb arg and passing the result to the second arg of that callback (cb) invocation.

We need to do this because Lambda expects the result to come as a promise or from the callback passed in. This took me waaaaaaaaay too long too piece together and hopefully this won't trip up anyone else either. Additionally we need to be sure to return Javascript values and not Clojurescript values hence the use of #js for the array.

Build and release time

Note: I am going to skip the process of actually developing the code but check out the earlier linked Shadow + Node example for details and workflow for how to use Shadow with Nodejs and hot-reloading.
cd /tmp/lambda-example # Get you back to the correct directory
yarn shadow-cljs release app # This transpiles and optimizes your CLJS
zip -r archive.zip target # Prep your output for upload to Lambda

Then we just upload our new zip to our old Lambda and point the handler function at our CLJS function (which will be .handler, main.handler in the recording).

🎉🎉🎉

And there we have it! If you have any questions or feedback reach out on Twitter, Mastodon, or @royalaid on the Clojurians Slack or Zulip

Top comments (0)