DEV Community

Cover image for AWS API + CLJS
Mark Aiken
Mark Aiken

Posted on

AWS API + CLJS

Building on our own shoulders

Previously we built a simple AWS Lambda. Today we are going mess around with the AWS Node SDK and DynamoDB. The main goal of this post is to show more serious inter-op between Clojurescript and Javascript.

AWS Services

Image of AWS Services Dashboard
AWS offers a lot of services for lot of different business needs and getting started can be really overwhelming. Before starting this blog anytime I needed to use AWS at work I was nervous. Coworkers through around nonsense acronyms left and right. "Check the Codebuild step in the Codepipeline to see if there is a problem with S3 or IAM" makes no sense if you haven't used AWS before. Luckily climbing over that hurdle really doesn't take more than a few hours of googling and poking around and hopefully this post will help anyone trying to get involved in the AWS ecosystem. Just to steer our exploration we will be using DynamoDB but if something else seems cool I highly encourage you to check out the intro project and docs!

πŸ–The actual meatπŸ–

For this posts example code I have ported the Node SDK's Create Table, CRUD Operations, and Delete Table.

Link to repo

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




Tools

We will obviously need a few tools:

CLJS VS JS

Note: I have basically ported the JS to it's literal, but not idiomatic, CLJS equivalent. I would use this code to help get a better understanding of how the two languages relate and how to call one from the other. I would NOT code like this when using CLJS as the primary language.

In this post I will just break down on example, createTable, because the only difference between any of the examples is the params var and the dynamodb/docClient fn call.

JS for reference

var AWS = require("aws-sdk");

AWS.config.update({
  region: "us-west-2",
  endpoint: "http://localhost:8000"
});

var dynamodb = new AWS.DynamoDB();

var params = {
    TableName : "Movies",
    KeySchema: [       
        { AttributeName: "year", KeyType: "HASH"},  //Partition key
        { AttributeName: "title", KeyType: "RANGE" }  //Sort key
    ],
    AttributeDefinitions: [       
        { AttributeName: "year", AttributeType: "N" },
        { AttributeName: "title", AttributeType: "S" }
    ],
    ProvisionedThroughput: {       
        ReadCapacityUnits: 10, 
        WriteCapacityUnits: 10
    }
};

dynamodb.createTable(params, function(err, data) {
    if (err) {
        console.error("Unable to create table. Error JSON:", JSON.stringify(err, null, 2));
    } else {
        console.log("Created table. Table description JSON:", JSON.stringify(data, null, 2));
    }
});

CLJS

(ns server.create-table
  (:require ["aws-sdk" :as AWS])) ;; Our var AWS = require statement

(AWS/config.update #js{:region "us-east-1"})
;; first example of js interop, the translates to the AWS.config.update above, the AWS/ bit is used for accessing the CLJS "Namespace" for the AWS SDK

(def dynamo (AWS/DynamoDB.  #js{:apiVersion "2012-08-10"}))
;; Second example of interop and shows constructor invocation. It can also be written as (def dynamo (new AWS/DynamoDB  #js{:apiVersion "2012-08-10"})) because the . is shorthand for new
;;Additionally def is the CLJS equivalentish of var but isn't used as often as in Clojure/script

(def params
  (clj->js {:TableName "Movies",
            :KeySchema
            [{:AttributeName "year", :KeyType "HASH"}
             {:AttributeName "title", :KeyType "RANGE"}],
            :AttributeDefinitions
            [{:AttributeName "year", :AttributeType "N"}
             {:AttributeName "title",
              :AttributeType "S"}],
            :ProvisionedThroughput
            {:ReadCapacityUnits 10,
             :WriteCapacityUnits 10}}))

(defn invoke []
 (.createTable dynamo params
               #(if %1
                  (js/console.error "Unable to create table. Error JSON:"
                                    (js/JSON.stringify %1 nil 2))
                  (js/console.log "Created table. Table description JSON:"
                                  (js/JSON.stringify %2 nil 2)))))
;; This is the one difference from the AWS example code above, the actual call to AWS is wrapped in a function so it can be call from node.js proper.

This pattern follows through all the the rest of the examples.

Node.js REPL calls

If you want to be able to test the code out for yourself you can call into from a node.js repl just compile and require

npx shadow-cljs compile app
cd target
node

then once in the repl

var m = require('./main.js');
m.aws.createTable() //Other options include getItem, createItem, readItem, deleteTable, deleteItem, updateItem, updateItemConditionally, atomicInc
//Inside of the Shadow-CLJS config is a mapping between the CLJS fn's to the m.aws object

πŸŽ‰πŸŽ‰πŸŽ‰

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

Top comments (1)

Collapse
 
nepeckman profile image
nepeckman

I'd love to see more Clojure content here! Keep it coming!