{"id":16721890,"url":"https://github.com/cnuernber/cljs-lambda-gateway-example","last_synced_at":"2025-03-21T21:30:42.805Z","repository":{"id":66590101,"uuid":"362166237","full_name":"cnuernber/cljs-lambda-gateway-example","owner":"cnuernber","description":"Simple bare-bones example of launching a full website using API gateway and AWS lambda","archived":false,"fork":false,"pushed_at":"2021-08-26T13:00:20.000Z","size":32,"stargazers_count":58,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-18T05:11:59.072Z","etag":null,"topics":["aws-lambda","graal-native","shadow-cljs"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cnuernber.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-04-27T15:42:58.000Z","updated_at":"2024-09-13T13:25:54.000Z","dependencies_parsed_at":"2023-03-14T04:16:50.751Z","dependency_job_id":null,"html_url":"https://github.com/cnuernber/cljs-lambda-gateway-example","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cnuernber%2Fcljs-lambda-gateway-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cnuernber%2Fcljs-lambda-gateway-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cnuernber%2Fcljs-lambda-gateway-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cnuernber%2Fcljs-lambda-gateway-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cnuernber","download_url":"https://codeload.github.com/cnuernber/cljs-lambda-gateway-example/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244874104,"owners_count":20524572,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws-lambda","graal-native","shadow-cljs"],"created_at":"2024-10-12T22:32:40.468Z","updated_at":"2025-03-21T21:30:42.788Z","avatar_url":"https://github.com/cnuernber.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Shadow CLJS, Reagent, Graal Native, \u0026 AWS API Gateway, Lambda\n\n\nHere is a very basic but complete example of a working shadow-cljs-based application\nthat works as a standalone server uberjar, standalone executable server, and a lambda\nproxy extension using AWS API Gateway.\n\n\n## Usage\n\nFirst install the necessary npm packages.\n\n* `npm install`\n\n## Development Commands\n\nCompile clojurescript in dev mode to resources/public/app.js\n\n* `clj -M:cljs watch :app`\n\nNow start a repl and run `(gateway-example.main/start-server)`.  This starts up\na basic web server with a ring handler stack.  This stack servers a homepage and\nthe required resources.  Note that some of these resources are in resources/public.\n\n\nAt this point you have a working server and you can change the text in webapp and\nit should hot-reload.  We can do one better, however, in that we can have boot up\na clojurescript repl.\n\nWhen shadow-cljs booted up it listed a port - 8777.  Connect to this with your\nIDE. You should see a prompt that looks like this:\n\n```clojure\nshadow.user\u003e\n```\n\nWe can query shadow-cljs to find the running apps:\n\n```clojure\nshadow.user\u003e (shadow/active-builds)\n#{:app}\n```\n\nAnd we can connect to an active build:\n\n```clojure\nshadow.user\u003e (shadow/repl :app)\nTo quit, type: :cljs/quit\n[:selected :app]\ncljs.user\u003e\n```\n\nYou can test this with an alert - `(js/alert \"hey\")`.\n\n\n## Standalone Uberjar/Executable\n\n\n* Compile clojurescript in release mode -\n\n```console\nrm -rf resources/public/js/* \u0026\u0026 clj -M:cljs release app\n```\n\nThis builds app.js, an all-in-one js that bundles everything we are using.\n\nNow build the uberjar\n\n```console\nclj -X:standalone-server\n```\n\n\nYou should be able to run the server via\n\n```console\njava -jar target/standalone.jar\n```\n\n\nNow that we have an uberjar we can compile a graal native executable with this\nfunctionality.  The repo includes three scripts -\n\n* scripts/get-graal - get a linux distribution of graal native compiled with java 11.\n* scripts/activate-graal - get graal if user doesn't have it, then export environment\n  variable `GRAALVM_HOME` that indicates where graal is installed.  Additionally update\n  path such that the graal-installed java, javac, and various graalvm utilites are\n  before anything else.\n* scripts/compile-standalone - Here is where the magic is.  This command works for Linux\n  but I imagine various things need to change for Mac.  Ask in the Clojurians/graal\n  slack channel for more information about this script; it packages resources and\n  has additions for postgres and httpkit's ssl engine as well as enabling\n  http,https support.\n\n\nAll you have to do at this point is run:\n\n```console\nscripts/compile-standalone\n```\n\nWhich should result in an executable compiled to target/standalone.  Running\nthis executable provides the server.\n\n```console\nchrisn@chrisn-lt-01:~/dev/cnuernber/cljs-lambda-gateway-example/target$ ./standalone\n08:26:33.039 [main] INFO  gateway-example.main - Starting server on port 3000\n08:26:33.041 [main] INFO  gateway-example.main - Main function exiting-server still running\n```\n\n\n## AWS API Gateway/Lambda Proxy\n\n\n### Step 1 - Proxy Lambda\n\nHere things start to get interesting.  We first want to compile the proxy lambda\nand upload it to AWS.  You will need credentials in your environment for this step.\n\nAWS Lambda gives you as the developer an interesting option - a 'custom' lambda\nruntime is simply an executable script or file named 'bootstrap' in a zip file.\nThis pares well with graal native but we do need bootstrap to be a script so we\ncan set java runtime variables; namely -Xmx so we stay within the bounds of our\ndefault lambda memory requirements.\n\nWe have a simple build script - scripts/compile-proxy-lambda that builds an uberjar\nwith the proxy-lambda function and packages it, along with our launch script into\na zipfile named proxy-lambda.zip.\n\n```console\nscripts/compile-proxy-lambda\n```\n\nOnce this script completes you should see target/proxy-lambda.zip is created.\n\n\nWe now need to create an IAM role with the basic lambda execution permission that\nwe have to attach to our lambda.\n\n* In the AWS console, to to IAM.\n* From the left menu, click on Roles.\n* Create Role - Lambda - then click Next\n* Find the `AWSLambdaBasicExecutionRole` policy and attach it.\n* Name your role and finish up.  Get the arn - mine was `arn:aws:iam::801514925221:role/lambda-role`.  It has one policy attached which is the policy listed above.\n\nNow we can upload that script to AWS assuming you have the appropriate credentials\nin your environment.\n\n```console\naws lambda create-function --function-name proxy-lambda \\\n    --zip-file fileb://target/proxy-lambda.zip --handler proxy.handler --runtime provided \\\n    --role arn:aws:iam::801514925221:role/lambda-role\n```\n\nSuccessful output ends with:\n\n```console\n    ...\n    \"RevisionId\": \"e5764007-f018-4882-985f-dc8e408c9009\",\n    \"State\": \"Active\",\n    \"LastUpdateStatus\": \"Successful\"\n}\n```\n\n\nYou should also be able to see your lambda in your console.\n\n### Step 2 - API Gateway\n\nI wish I had the time to come up with a command line pathway to do this.  The tricky\npart of that is the permissions; gateway needs specific access to your lambda\nwhich console does automagically and correctly.  In any case, this part of our\nwalkthrough now goes back to the console.\n\n##### Setup Base URL\n\n1.  In Console, got to API Gateway.\n2.  Find Rest API and click Build.\n3.  Click New API and fill out details ensuring you click Regional.  I named mine `proxy-lambda-example`.\n4.  This brings you to your API management screen and here lie demons so hang in there.\n5.  In the middle panel, click on the single '/' resource.  Choose `Create Method` from the dropdown and a new dropdown appears.  Select 'ANY'.\n6.  Now we configure our method to be a proxy lambda method.  Choose 'Lambda' as the type and click the `Use Proxy Lambda Itegration` box and select our\n    lambda method (proxy-lambda in my case) as the lambda function to run.\n\nWith that, you should see a test screen.  We can test our method - click Test, select Get, and hit Test.  At this point you should see your homepage\nin HTML form echoed back to you from the proxy lambda.  New we need to setup additional routes with a wildcard so urls derived from our initial url\nalso go through our lambda.\n\n\n##### Deploy Test Stage\n\n1. In the middle pane click Actions.\n2. Click `Deploy API`, click 'create stage' name your stage.  The other details are optional.\n3. This brings to you Stages where near the top you see a test URL.  Clicking this brings you to\n   a blank page.  Our HTML homepage loaded but our resources did not; you can verify this in the\n   network tab of your browser dev tools where you see a bunch of 403 error codes.\n\n\n##### Configure Proxy Wildcard URLs\n\nThat still is a large step further but of course we want our entire site to load which involves\nmore than just hitting the base url.\n\n1.  From the left pane go back to Resources.\n2.  From Actions click `Create Resource`.\n3.  Click 'configure as proxy resource'\n4.  Click 'Create Resource`.\n5.  Configure it to use our proxy lambda function.\n\nYou should now have *two* methods configured under your base resource.\n\nNow retest with your test url **but make sure to add a '/' at the end**.  For example\nin my case https://izo5hfi5d8.execute-api.us-west-2.amazonaws.com/test returns the\nsame 403 errors while https://izo5hfi5d8.execute-api.us-west-2.amazonaws.com/test/\nreturns the resources encoded in base-64.  We fix that now.\n\n##### Supporting Binary Resources\n\nThere is one more bit of configuration we have to do to our API gateway to support\nbase64 encoded resources which will be most of the file-based resources such as\njs files, css files, and images.\n\n1.  In API Gateway, in leftmost pane click the first Settings.  It is indented a bit.\n2.  You should see a set of panels.  A few are interesting but near the end is a panel\n    that named `Binary Media Types`. Click `Add Binary Media Type`.\n3.  Type in `*/*`.  This simply *allows* all media types to be binary.  Our code in\n    proxy_lambda.clj explicitly binary encodes any response bodies that are streams.\n4.  Save changes.\n5.  Redeploy API.\n\n\nA *hard reload* (Shift-F5) should reload your page and at this point you should see\nour nice welcome screen.\n\n\n## Addendum\n\nIf you make a change to proxy-lambda and you want to update it, run 'compile-proxy-lambda to produce a new zip package and here is the command line to update it:\n\n```console\naws lambda update-function-code  --function-name proxy-lambda \\\n    --zip-file fileb://target/proxy-lambda.zip\n```\n\nIt is instructive at this point to check the CloudWatch logs.  Logs for our lambda\nare nicely done and since we are careful to write out JSON our logs are quite\nreadable -- we can get into fancier ways to do structured logging that integrates with\nCloudWatch later.  We can also enable Cloudwatch logs for API gateway if we so\nchoose.\n\n\n## Further Resources\n\n* [HolyLambda](https://github.com/FieryCod/holy-lambda) - supports babashka-based deployments.\n* [graalvm-clojure](https://github.com/BrunoBonacci/graalvm-clojure/blob/master/doc/clojure-graalvm-native-binary.md) - lots of examples of various projects.\n* [babashka](https://github.com/babashka/babashka) - Compiled Clojure command line system with extensive extension system.\n* [avclj](https://github.com/cnuernber/avclj/) - In depth Clojure program using FFMpeg's shared libraries directly and compiling both an an executable and has an example of compiling Clojure to a library and calling it from C++.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcnuernber%2Fcljs-lambda-gateway-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcnuernber%2Fcljs-lambda-gateway-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcnuernber%2Fcljs-lambda-gateway-example/lists"}