AWS Amplify and Clojurescript Re-Frame Part 1

AWS Amplify and Clojurescript Re-Frame Part 1

Introduction

AWS Amplify

AWS Amplify makes it super simple to build Web and Mobile Apps that use the entire suite of AWS Services with minimal coding by the app developer. Clojurescript and Re-Frame make creating SPA apps a joy. The combination of all three is truly a superpower!

The AWS Amplify CLI makes it easy to setup various AWS services and make them available via the Amplify SDK to your Web or Mobile App. It also has great support for React based apps.

The goal of this project is to show how to be able to use AWS Amplify with Clojurescript / Re-Frame Web Apps.

The initial AWS feature to incorporate is the Amplify Authentication service which has AWS Cognito at its core. Amplify offers a React Higher Order Component that allows you to wrap your JS app with Authentication.

  • Ensures that your App can only be accessed when authenticated
  • Handles all the UI and API integration with Cognito

    • You do not have to write any of the code
    • Sign-up
      • Verify phone_number or email address
    • Sign-in
    • Sign-out
    • Forgot Password
    • Password reset
  • Optionally (and not described in this first article but easy to add)

    • Customize Authenticator UI
    • Use your own UI components
    • AWS Auth for AWS services which require signing requests.
    • Social Provider Federation / OAuth
    • Force new password
    • Multi-Factor Authentication
    • User Attributes
    • Lambda Triggers

Note: This article and its followup AWS Amplify and Clojurescript Re-Frame Part 2: Deploy to Production Cloudfront are based on branch basic-amplify-with-cloudfront of the omnyway-labs/amplify_cljs_example repo.

Re-Frame / Clojurescript / Shadow-cljs

The main goal of this project is to show how to leverage Amplify in a Clojurescript Single Page App (SPA). In particular, using the re-frame Clojurescript Library.

Re-frame is a functional framework for building SPAs leveraging Reagent. Reagent is a minimalistic interface between ClojureScript and React.

Clojurescript is hosted on Javascript and can naturally interoperate with Javascript in general. This project uses shadow-cljs as the buildtool. Its major superpower is to make it pretty easy to leverage node.js packages. The main features it offers include:

  • Good configuration defaults so you don't have to sweat the details
  • Seamless npm integration
  • Fast builds, reliable caching, ...
  • Supporting various targets :browser, :node-script, :npm-module, :react-native, :chrome-extension, ...
  • Live Reload (CLJS + CSS)
  • CLJS REPL
  • Code splitting (via :modules)

Put together, these tools allows one to build SPAs in a functional / Clojure centric way, but still leverage all the wonders of React and node.

That being said. There are a lot of learning curves that also need to be climbed to make it all work together.

This project is an attempt to make it easier for folks to jump right in. It can be used as a starting point to build your own SPA with sophisticated Authentication services.

This article and the associated github repo omnyway-labs/amplify_cljs_example should tell you everything you need from scratch to get you to a working basis of a re-frame SPA running on your local machine with full AWS Cognito Simple Authentication backend. The amount of code you have to write to make it work is surprisingly trivial due to the scaffolding features of re-frame and the AWS generation / auto-configuration of Amplify.

The second article in this series Deploy to CloudFront with Amplify Console shows how to deploy the SPA to AWS CloudFront with minimal effort.

Assumptions

You have already installed the following on your laptop / dev machine:

Initial Setup

We're using the re-frame scaffold template to create the initial app. We're not going to add much to the app in this article other than the Amplify Authentication wrapper.

Later articles will add more functionality so we're including some extras like garden for doing CSS in clojure. More importantly we're including re-com Which is a library of ClojureScript UI components, built on top of Reagent. It integrates with re-frame in a very functional / clojure way.

10x is a very nice debugging dashboard for re-frame. Again, won't be using it until later articles but its something you will want to have if you use this for building on top of.

  • Generate the scaffold
lein new re-frame amplify_cljs_example +10x +cider +re-com +test +garden
cd amplify_cljs_example
lein garden once
git init
  • Update .gitignore by adding:
/resources/public/css/screen.css
.shadow-cljs
node_modules
  • More setup; build and run the local instance of the initial scaffolded SPA
git add -A
git commit -a -m "Initial commit after lein new re-frame amplify_cljs_example +10x +cider +re-com +test +garden"
npm install
git add package-lock.json
git commit -m "package-lock for npm" package-lock.json
lein garden once
lein dev

The lein dev will build and run the application locally. It doesn't exit, it keeps running, monitoring the app files. If there are any changes it will automatically hot load the application. Lein is effectively running shadow-cljs watch app

  • The lein dev probably added more packages for npm and so you should update your git in another shell window in the same directory
    git commit -m "lein updates to package.json" package-lock.json  package.json
    

    Ensure the basics are working

So if the lein dev is running properly, it will end with:

[:app] Build completed. (1659 files, 1658 compiled, 0 warnings, 46.89s)

At that point, you should be able to:

  • Go to http://localhost:8280 in a browser (Chrome is optimal)
  • Should look something like:

Initial Working Display

Set up AWS Amplify

  • You should be in the top level of the amplify_cljs_example directory
  • Assuming you don’t already have AWS Amplify installed on your dev machine:

    npm install -g @aws-amplify/cli
    amplify configure
    
  • Follow the instructions as described in Installing & Configuring the AWS Amplify CLI - YouTube to configure you local dev amplify environment to manage your AWS environment

  • Initialize the Amplify project.
    • Mostly accept the defaults or as appropriate for you
    • Start command : lein dev
    • AWS Profile yes and use the one you created when you did amplify configure which by default is default ```

      amplify init Scanning for plugins... Plugin scan successful Note: It is recommended to run this command from the root of your app directory ? Enter a name for the project reframerecomamplifye ? Enter a name for the environment dev ? Choose your default editor: Emacs (via Terminal, Mac OS only) ? Choose the type of app that you're building javascript Please tell us about your project ? What javascript framework are you using react ? Source Directory Path: src ? Distribution Directory Path: build ? Build Command: lein prod ? Start Command: lein dev Using default provider awscloudformation

For more information on AWS Profiles, see: docs.aws.amazon.com/cli/latest/userguide/cl..

? Do you want to use an AWS profile? Yes ? Please choose the profile you want to use default ⠧ Initializing project in the cloud...

CREATE_IN_PROGRESS AuthRole AWS::IAM::Role Fri Oct 04 2019 22:50:40 GMT-0700 (Pacific Daylight Time) Resource creation Initiated CREATE_IN_PROGRESS DeploymentBucket AWS::S3::Bucket Fri Oct 04 2019 22:50:40 GMT-0700 (Pacific Daylight Time) Resource creation Initiated CREATE_IN_PROGRESS UnauthRole AWS::IAM::Role Fri Oct 04 2019 22:50:40 GMT-0700 (Pacific Daylight Time) Resource creation Initiated CREATE_IN_PROGRESS AuthRole AWS::IAM::Role Fri Oct 04 2019 22:50:39 GMT-0700 (Pacific Daylight Time) CREATE_IN_PROGRESS DeploymentBucket AWS::S3::Bucket Fri Oct 04 2019 22:50:39 GMT-0700 (Pacific Daylight Time) CREATE_IN_PROGRESS UnauthRole AWS::IAM::Role Fri Oct 04 2019 22:50:39 GMT-0700 (Pacific Daylight Time) CREATE_IN_PROGRESS reframerecomamplifye-dev-20191004225035 AWS::CloudFormation::Stack Fri Oct 04 2019 22:50:36 GMT-0700 (Pacific Daylight Time) User Initiated ⠴ Initializing project in the cloud...

CREATE_COMPLETE UnauthRole AWS::IAM::Role Fri Oct 04 2019 22:50:52 GMT-0700 (Pacific Daylight Time) ⠏ Initializing project in the cloud...

CREATE_COMPLETE AuthRole AWS::IAM::Role Fri Oct 04 2019 22:50:52 GMT-0700 (Pacific Daylight Time) ⠹ Initializing project in the cloud...

CREATE_COMPLETE DeploymentBucket AWS::S3::Bucket Fri Oct 04 2019 22:51:01 GMT-0700 (Pacific Daylight Time) ⠇ Initializing project in the cloud...

CREATE_COMPLETE reframerecomamplifye-dev-20191004225035 AWS::CloudFormation::Stack Fri Oct 04 2019 22:51:03 GMT-0700 (Pacific Daylight Time) ✔ Successfully created initial AWS cloud resources for deployments. ✔ Initialized provider successfully. Initialized your environment successfully.

Your project has been successfully initialized and connected to the cloud!

* Add the Amplify Authentication service
    * Details at [AWS Amplify Authentication](https://aws-amplify.github.io/docs/js/authentication)
    * Sign-in: Selecting 'Email' and/or 'Phone Number' will allow end users to sign-up using these values.  Selecting 'Username' will require a unique username for users.

amplify add auth Using service: Cognito, provided by: awscloudformation

The current configured provider is Amazon Cognito.

Do you want to use the default authentication and security configuration? Default configuration Warning: you will not be able to edit these selections. How do you want users to be able to sign in? Email Do you want to configure advanced settings? No, I am done. Successfully added resource reframerecomamplifye16cd456a locally

* Push the auth feature / config to AWS

amplify auth push

Current Environment: dev

CategoryResource nameOperationProvider plugin
Authreframerecomamplifye16cd456aCreateawscloudformation

? Are you sure you want to continue? Yes ⠹ Updating resources in the cloud. This may take a few minutes...

CREATE_IN_PROGRESS UpdateRolesWithIDPFunctionRole AWS::IAM::Role Fri Oct 04 2019 23:08:01 GMT-0700 (Pacific Daylight Time) Resource creation Initiated CREATE_IN_PROGRESS authreframerecomamplifye16cd456a AWS::CloudFormation::Stack Fri Oct 04 2019 23:08:01 GMT-0700 (Pacific Daylight Time) Resource creation Initiated CREATE_IN_PROGRESS authreframerecomamplifye16cd456a AWS::CloudFormation::Stack Fri Oct 04 2019 23:08:01 GMT-0700 (Pacific Daylight Time) CREATE_IN_PROGRESS UpdateRolesWithIDPFunctionRole AWS::IAM::Role Fri Oct 04 2019 23:08:00 GMT-0700 (Pacific Daylight Time) UPDATE_IN_PROGRESS reframerecomamplifye-dev-20191004225035 AWS::CloudFormation::Stack Fri Oct 04 2019 23:07:57 GMT-0700 (Pacific Daylight Time) User Initiated ⠇ Updating resources in the cloud. This may take a few minutes...

CREATE_IN_PROGRESS reframerecomamplifye-dev-20191004225035-authreframerecomamplifye16cd456a-19BSJYQRK36 AWS::CloudFormation::Stack Fri Oct 04 2019 23:08:01 GMT-0700 (Pacific Daylight Time) User Initiated ⠋ Updating resources in the cloud. This may take a few minutes...

CREATE_IN_PROGRESS SNSRole AWS::IAM::Role Fri Oct 04 2019 23:08:08 GMT-0700 (Pacific Daylight Time) Resource creation Initiated CREATE_IN_PROGRESS SNSRole AWS::IAM::Role Fri Oct 04 2019 23:08:07 GMT-0700 (Pacific Daylight Time) ⠴ Updating resources in the cloud. This may take a few minutes...

CREATE_COMPLETE UpdateRolesWithIDPFunctionRole AWS::IAM::Role Fri Oct 04 2019 23:08:15 GMT-0700 (Pacific Daylight Time) ⠋ Updating resources in the cloud. This may take a few minutes...

CREATE_COMPLETE SNSRole AWS::IAM::Role Fri Oct 04 2019 23:08:20 GMT-0700 (Pacific Daylight Time) ⠙ Updating resources in the cloud. This may take a few minutes...

CREATE_COMPLETE UserPool AWS::Cognito::UserPool Fri Oct 04 2019 23:08:27 GMT-0700 (Pacific Daylight Time) CREATE_IN_PROGRESS UserPool AWS::Cognito::UserPool Fri Oct 04 2019 23:08:27 GMT-0700 (Pacific Daylight Time) Resource creation Initiated CREATE_IN_PROGRESS UserPool AWS::Cognito::UserPool Fri Oct 04 2019 23:08:24 GMT-0700 (Pacific Daylight Time) ⠇ Updating resources in the cloud. This may take a few minutes...

CREATE_COMPLETE UserPoolClientWeb AWS::Cognito::UserPoolClient Fri Oct 04 2019 23:08:33 GMT-0700 (Pacific Daylight Time) CREATE_IN_PROGRESS UserPoolClientWeb AWS::Cognito::UserPoolClient Fri Oct 04 2019 23:08:33 GMT-0700 (Pacific Daylight Time) Resource creation Initiated CREATE_COMPLETE UserPoolClient AWS::Cognito::UserPoolClient Fri Oct 04 2019 23:08:33 GMT-0700 (Pacific Daylight Time) CREATE_IN_PROGRESS UserPoolClient AWS::Cognito::UserPoolClient Fri Oct 04 2019 23:08:32 GMT-0700 (Pacific Daylight Time) Resource creation Initiated CREATE_IN_PROGRESS UserPoolClientWeb AWS::Cognito::UserPoolClient Fri Oct 04 2019 23:08:31 GMT-0700 (Pacific Daylight Time) CREATE_IN_PROGRESS UserPoolClient AWS::Cognito::UserPoolClient Fri Oct 04 2019 23:08:31 GMT-0700 (Pacific Daylight Time) ⠙ Updating resources in the cloud. This may take a few minutes...

CREATE_IN_PROGRESS UserPoolClientRole AWS::IAM::Role Fri Oct 04 2019 23:08:36 GMT-0700 (Pacific Daylight Time) Resource creation Initiated CREATE_IN_PROGRESS UserPoolClientRole AWS::IAM::Role Fri Oct 04 2019 23:08:36 GMT-0700 (Pacific Daylight Time) ⠏ Updating resources in the cloud. This may take a few minutes...

CREATE_COMPLETE UserPoolClientRole AWS::IAM::Role Fri Oct 04 2019 23:08:48 GMT-0700 (Pacific Daylight Time) ⠹ Updating resources in the cloud. This may take a few minutes...

CREATE_COMPLETE UserPoolClientLambda AWS::Lambda::Function Fri Oct 04 2019 23:08:52 GMT-0700 (Pacific Daylight Time) CREATE_IN_PROGRESS UserPoolClientLambda AWS::Lambda::Function Fri Oct 04 2019 23:08:52 GMT-0700 (Pacific Daylight Time) Resource creation Initiated CREATE_IN_PROGRESS UserPoolClientLambda AWS::Lambda::Function Fri Oct 04 2019 23:08:52 GMT-0700 (Pacific Daylight Time) ⠸ Updating resources in the cloud. This may take a few minutes...

CREATE_IN_PROGRESS UserPoolClientLambdaPolicy AWS::IAM::Policy Fri Oct 04 2019 23:08:57 GMT-0700 (Pacific Daylight Time) Resource creation Initiated CREATE_IN_PROGRESS UserPoolClientLambdaPolicy AWS::IAM::Policy Fri Oct 04 2019 23:08:56 GMT-0700 (Pacific Daylight Time) ⠼ Updating resources in the cloud. This may take a few minutes...

CREATE_COMPLETE UserPoolClientLambdaPolicy AWS::IAM::Policy Fri Oct 04 2019 23:09:02 GMT-0700 (Pacific Daylight Time) ⠋ Updating resources in the cloud. This may take a few minutes...

CREATE_IN_PROGRESS UserPoolClientLogPolicy AWS::IAM::Policy Fri Oct 04 2019 23:09:06 GMT-0700 (Pacific Daylight Time) ⠸ Updating resources in the cloud. This may take a few minutes...

CREATE_IN_PROGRESS UserPoolClientLogPolicy AWS::IAM::Policy Fri Oct 04 2019 23:09:06 GMT-0700 (Pacific Daylight Time) Resource creation Initiated ⠸ Updating resources in the cloud. This may take a few minutes...

CREATE_COMPLETE UserPoolClientLogPolicy AWS::IAM::Policy Fri Oct 04 2019 23:09:12 GMT-0700 (Pacific Daylight Time) ⠸ Updating resources in the cloud. This may take a few minutes...

CREATE_IN_PROGRESS UserPoolClientInputs Custom::LambdaCallout Fri Oct 04 2019 23:09:15 GMT-0700 (Pacific Daylight Time) ⠹ Updating resources in the cloud. This may take a few minutes...

CREATE_IN_PROGRESS IdentityPool AWS::Cognito::IdentityPool Fri Oct 04 2019 23:09:23 GMT-0700 (Pacific Daylight Time) CREATE_COMPLETE UserPoolClientInputs Custom::LambdaCallout Fri Oct 04 2019 23:09:19 GMT-0700 (Pacific Daylight Time) CREATE_IN_PROGRESS UserPoolClientInputs Custom::LambdaCallout Fri Oct 04 2019 23:09:19 GMT-0700 (Pacific Daylight Time) Resource creation Initiated ⠼ Updating resources in the cloud. This may take a few minutes...

CREATE_COMPLETE IdentityPool AWS::Cognito::IdentityPool Fri Oct 04 2019 23:09:25 GMT-0700 (Pacific Daylight Time) CREATE_IN_PROGRESS IdentityPool AWS::Cognito::IdentityPool Fri Oct 04 2019 23:09:24 GMT-0700 (Pacific Daylight Time) Resource creation Initiated ⠋ Updating resources in the cloud. This may take a few minutes...

CREATE_IN_PROGRESS IdentityPoolRoleMap AWS::Cognito::IdentityPoolRoleAttachment Fri Oct 04 2019 23:09:28 GMT-0700 (Pacific Daylight Time) ⠴ Updating resources in the cloud. This may take a few minutes...

CREATE_COMPLETE reframerecomamplifye-dev-20191004225035-authreframerecomamplifye16cd456a-19BSJYQRK36 AWS::CloudFormation::Stack Fri Oct 04 2019 23:09:32 GMT-0700 (Pacific Daylight Time) CREATE_COMPLETE IdentityPoolRoleMap AWS::Cognito::IdentityPoolRoleAttachment Fri Oct 04 2019 23:09:30 GMT-0700 (Pacific Daylight Time) CREATE_IN_PROGRESS IdentityPoolRoleMap AWS::Cognito::IdentityPoolRoleAttachment Fri Oct 04 2019 23:09:30 GMT-0700 (Pacific Daylight Time) Resource creation Initiated ⠧ Updating resources in the cloud. This may take a few minutes...

CREATE_COMPLETE authreframerecomamplifye16cd456a AWS::CloudFormation::Stack Fri Oct 04 2019 23:09:36 GMT-0700 (Pacific Daylight Time) ⠏ Updating resources in the cloud. This may take a few minutes...

CREATE_IN_PROGRESS UpdateRolesWithIDPFunctionOutputs Custom::LambdaCallout Fri Oct 04 2019 23:09:41 GMT-0700 (Pacific Daylight Time) CREATE_COMPLETE UpdateRolesWithIDPFunction AWS::Lambda::Function Fri Oct 04 2019 23:09:39 GMT-0700 (Pacific Daylight Time) CREATE_IN_PROGRESS UpdateRolesWithIDPFunction AWS::Lambda::Function Fri Oct 04 2019 23:09:39 GMT-0700 (Pacific Daylight Time) Resource creation Initiated CREATE_IN_PROGRESS UpdateRolesWithIDPFunction AWS::Lambda::Function Fri Oct 04 2019 23:09:38 GMT-0700 (Pacific Daylight Time) ⠹ Updating resources in the cloud. This may take a few minutes...

UPDATE_COMPLETE reframerecomamplifye-dev-20191004225035 AWS::CloudFormation::Stack Fri Oct 04 2019 23:09:47 GMT-0700 (Pacific Daylight Time) UPDATE_COMPLETE_CLEANUP_IN_PROGRESS reframerecomamplifye-dev-20191004225035 AWS::CloudFormation::Stack Fri Oct 04 2019 23:09:47 GMT-0700 (Pacific Daylight Time) CREATE_COMPLETE UpdateRolesWithIDPFunctionOutputs Custom::LambdaCallout Fri Oct 04 2019 23:09:45 GMT-0700 (Pacific Daylight Time) CREATE_IN_PROGRESS UpdateRolesWithIDPFunctionOutputs Custom::LambdaCallout Fri Oct 04 2019 23:09:44 GMT-0700 (Pacific Daylight Time) Resource creation Initiated ✔ All resources are updated in the cloud


* Install the Amplify Node modules we’ll be using for Authentication
```shell
npm add aws-amplify aws-amplify-react
  • Copy the css for the amplify UI widgets into the proper resource for our app’s css to be picked up
    • Note: I was looking for a better way to do this that is more via configuration. If anyone knows a better way please let me know
cp node_modules/@aws-amplify/ui/dist/style.css resources/public/css/aws-amplify-ui-style.css
  • Update resources/public/index.html to incorporate that css by adding the following line in the <head> section

    <link href="css/aws-amplify-ui-style.css" rel="stylesheet" type="text/css">
    
  • Get the changes into git

    git add amplify resources/public/css/aws-amplify-ui-style.css
    git commit -m "Added and configured Amplify" .gitignore package-lock.json package.json amplify resources/public/index.html resources/public/css
    
  • Your application at http://localhost:8280 should still be running fine assuming you still have the lein dev process running

Update the code to enable the Authenticator

Edit src/cljs/re_frame_re_com_amplify_exp/core.cljs

  • Add the following lines to the require block. This is what will import the node js into your Clojurescript
   ["aws-amplify" :default Amplify :as amp]
   ["aws-amplify-react" :refer (withAuthenticator)]
   ["/aws-exports.js" :default aws-exports]
  • Add the following def after dev-setup function

This is where you are wrapping the Amplify HOC around your application. The withAuthenticator is the HOC function being pulled in from the Amplify Auth SDK. It ensures that your app only is accessed after authentication and it handles all the Frontend UI and communication with the Backend to implement the full suite of Authentication.

(def root-view
  (reagent/adapt-react-class
   (withAuthenticator
    (reagent/reactify-component views/main-panel) true)))
  • Change the function mount-root to be:
    (defn ^:dev/after-load mount-root []
    (re-frame/clear-subscription-cache!)
    (.configure Amplify aws-exports)
    (re-frame/dispatch-sync [::events/initialize-db])
    (reagent/render [root-view]
                    (.getElementById js/document "app")))
    
  • Remove the following line from init function:

This makes it so that the re-frame db is inited after the cache is cleared. As far as I can tell the Scaffolding puts it in the wrong place

(re-frame/dispatch-sync [::events/initialize-db])
  • The final file should be: ``` clojure (ns amplify_cljs_example.core (:require [reagent.core :as reagent] [re-frame.core :as re-frame] [amplify_cljs_example.events :as events] [amplify_cljs_example.views :as views] [amplify_cljs_example.config :as config] ["aws-amplify" :default Amplify :as amp] ["aws-amplify-react" :refer (withAuthenticator)] ["/aws-exports.js" :default aws-exports] ))

(defn dev-setup [] (when config/debug? (println "dev mode")))

(def root-view (reagent/adapt-react-class (withAuthenticator (reagent/reactify-component views/main-panel) true)))

(defn ^:dev/after-load mount-root [] (re-frame/clear-subscription-cache!) (.configure Amplify aws-exports) (re-frame/dispatch-sync [::events/initialize-db]) (reagent/render [root-view] (.getElementById js/document "app")))

(defn init [] (dev-setup) (mount-root))

* The display should look like this:
![Initial Working Display](https://cdn.hashnode.com/res/hashnode/image/upload/v1642313854225/naWPzlKZK.png)

![](assets/1/working_authentication.png)
* You should click the button on the debugger to have it be in its own window as it will block the “sign-out” button later
![Initial Working Display](https://cdn.hashnode.com/res/hashnode/image/upload/v1642313855640/RRL_URKuC.png)
* Commit the changes to git
``` shell
git commit -m "App updated to require Cognito Authenticator before any other operation" src/cljs/re_frame_re_com_amplify_exp/core.cljs

Wrap Up

You now have a template of how to create a basic Re-Frame SPA with authentication from AWS Amplify with almost not coding and without having to set up your own backend authentication infrastructure. You can continue with this to use other AWS Amplify authentication features (Social Provider Federation, Multi-factor auth, etc) as well as the other Amplify services such as graphql access to DynamoDB, Push notifications, etc.).

The next article, AWS Amplify and Clojurescript Re-Frame Part 2: Deploy to Production Cloudfront, will show how to deploy it to "production" on AWS Cloudfront using Amplify Console so that the Re-Frame SPA can be used on the general Internet also without you having to set up any servers yourself.