Creating an AWS Lambda function with nbb

Nbb

Nbb is a scripting environment, in the tradition of babashka, but for Node.js. Like babashka, nbb doesn't have a compile cycle, but it just interprets Clojure code using SCI. It has a few useful CLJS libraries included, e.g. for JS interop. It can load any JS library from NPM. Check out the repo here. There are several examples available as well.

Nbb on AWS Lambda

While it was already possible to use nbb on AWS Lambda, it needed some boilerplate code. Valtterri Harmainen captured this boilerplate in his nbb-lambda-adapter.

This week AWS announced that AWS Lambda now supports ES Modules and Top-Level Await for Node.js 14. With that in place, the boilerplate code isn't necessary anymore.

Creating an nbb lambda

All you need to do to get nbb running on AWS Lambda is the following:

package.json:

{"dependencies": {"nbb": "0.1.0"}}

index.mjs:

import { loadFile } from 'nbb';

const { handler } = await loadFile('./example.cljs');

export { handler }

example.cljs:

(ns example)

(defn handler [event _ctx]
(js/console.log event)
(js/Promise.resolve #js{:hello "world"}))


#js {:handler handler}

Make sure to run npm install.

Zip the directory: zip -r app.zip .

Go to the AWS Console. Choose Lambda -> Author from Scratch -> Runtime Node.s 14.x + arm64. The default 128 MB should be sufficient for fast response times after cold start, but for fast cold starts, higher memory (which comes with higher CPU) is better.

Then choose Upload from and choose the zip file.

You can test the lambda function by creating a test event and invoking it.

To be able to invoke the function via HTTP, you'll first have to Publish it.

Then, under Configuration > Trigger you can add an API Gateway trigger. Create one and choose HTTP API and Security Open (make sure you change this when it becomes a private production lambda rather than just for the sake of trying nbb on lambda!).

After that you should end up with a public URL like https://9fov8nrv4f.execute-api.eu-central-1.amazonaws.com/default/... which you can then call from curl or via a browser. The response times I got after the cold start were around 100ms.

As a nice bonus, you can edit the CLJS code directly in the console:

Also check out this nbb serverless example that Valtteri Harmainen made.

Discuss this post here.

Published: 2022-01-08

Using babashka with PHP: guestbook example

One of the recurring themes in Clojure land is:

Enter PHP. PHP isn't a platform / language I like to use, but it sure is practical and widely adopted. I already have a PHP server running, so I thought, let's try some Clojure on top of this in the form of babashka. Babashka already has a built-in http server, but using this approach it isn't used. We just rely on PHP calling babashka when the PHP script is requested via nginx. The babashka script prints HTML and then exits. PHP sends the HTML to the browser as it would normally do. If you have already paid the operational costs of installing and maintaining PHP, this approach doesn't add much cost to it. A closely related CGI-based approach, but one that doesn't use PHP at all, has been explored before by Eccentric-J and you can read his blog post here.

The example described in this blog post is a guestbook. You can view the guestbook here and the code is on Github. Below is a brief explanation of the components involved.

Database table

I created the following database table to store a name, greeting and some metadata:

CREATE TABLE public.guestbook
(
name text,
message text,
_created timestamp without time zone,
_session text,
CONSTRAINT session_unique UNIQUE (session)
)

PHP Babashka wrapper

This small PHP script forwards some of the request data to babashka:

<?php
session_start();
$post_data=escapeshellarg(json_encode($_POST));
$query_params=escapeshellarg(json_encode($_GET));
$sess_id=session_id();
passthru("POST_DATA=$post_data QUERY_PARAMS=$query_params SESSION_ID=$sess_id ./bb guestbook.clj");
?>

The passthru command calls a locally installed version of babashka, in the same directory as the PHP script and adds some environment variables. It encodes posted data and query parameters as JSON, which in the babashka script we will decode as JSON:

(def post-data (-> (System/getenv "POST_DATA")
(cheshire/parse-string
true)
)
)

Babashka guestbook script

In the babashka script, I use the babashka PostgreSQL pod to interact with the database:

(require '[babashka.pods :as pods])
(pods/load-pod "./pod-babashka-postgresql")
(require '[pod.babashka.postgresql :as sql])

Note that I also downloaded the pod locally into the directory. I needed to do this because the PHP server runs under a different user which doesn't have a home directory, so installing from the pod registry didn't work for that reason.

Here I check if a user already posted a greeting before, using the session id:

(def session-id (System/getenv "SESSION_ID"))

(def db {:dbtype "postgresql"
:user "guestbook"
:password "guestbook"
:database "guestbook"
:port 5434}
)


(def posted-before
(-> (sql/execute-one! db ["select count(*) from guestbook where _session = ?" session-id])
:count)
)

The script uses hiccup to render HTML. E.g here is the code for the guestbook:

(defn render-messages []
[:table.table
[:thead
[:tr
[:th "Name"]
[:th "Greeting"]]
]

[:tbody
(for [{:guestbook/keys [name message]} entries]
[:tr
[:td name]
[:td message]]
)
]
]
)

The guestbook form has some fields to protect against spambots: the user is supposed to fill in the outcome of a sum or multiplication. Also it is required to post the data using the same session id that you got when you entered the data. You can see that in the function process-post-data here.

The babashka script can print at any time and the output is rendered via the PHP wrapper as HTML. This is pretty handy for debugging. I wrote the script remotely on a server and just refreshed my browser any time I added some debug information, old school style.

One downside of this approach is whenever an exception happens you might not see any useful output in the page, so some defensive programming using try/catch is sometimes necessary.

So here you have it, documented for any PHP + Clojure user who might find it useful.

Discuss this post here.

Published: 2022-01-07

OSS Highlights of November - December 2021

Like the previous OSS highlights, I'll give an overview of OSS work I did in November and December of 2021. I'd like to emphasize that I can do this work because of sponsoring via:

for which I'm very grateful. I love helping peoeple by providing OSS software and this is now my main activity.

Enterprise support

I'd like to add that I'm also offering enterprise support for my OSS projects. It might be good to know there are options beyond sponsoring if your company needs an extra level of support for my OSS libraries. Recurring sponsors are eligible for a discount. Feel free to reach out via Twitter DM, e-mail or on Clojurians Slack to discuss options.

Babashka

Native, fast starting Clojure interpreter for scripting.

Releases: 0.7.3 , 0.7.2 , 0.7.0 , 0.6.8 , 0.6.7 , 0.6.6 , 0.6.5

Highlights:

Clj-kondo

A linter for Clojure (code) that sparks joy.

Releases: 2021.12.19 , 2021.12.16 , 2021.12.01

Highlights:

Note that most new linters in clj-kondo are going to default to :level :off unless they detect a compile time error, like :duplicate-case-test-constant.

Special thanks to new contributor @mknoszlig who has been quite active in adding new linters in collaboration with me.

SCI

Configurable Clojure interpreter suitable for scripting and Clojure DSLs.

Releases: 0.2.8

Highlights:

Nbb

Ad-hoc CLJS scripting on Node.js using SCI.

Releases: v0.0.108 - v0.1.0. See npm.

Highlights:

Neil

Edamame

Configurable EDN/Clojure parser with location metadata

Releases: 0.0.13 , 0.0.14 , 0.0.15 , 0.0.16 , 0.0.17 , 0.0.18 , 0.0.19

Many small improvements. Edamame now supports reading with a reader which is not an indexing reader too, for compatibility with clojure.core/read.

Babashka.fs

File system utility library.

Releases: 0.1.2 , 0.1.1

Highlights: improve fs/which on Windows, add with-temp-dir macro.

Babashka.process

Clojure wrapper for java.lang.ProcessBuilder.

Releases: 0.1.0

Highlights:

tools.namespace

In this core library I contributed two ClojureScript fixes:

Discuss this post here.

Published: 2021-12-31

Archive