{"id":23170360,"url":"https://github.com/choonkeat/elm-webapp","last_synced_at":"2026-02-20T12:01:09.593Z","repository":{"id":41487954,"uuid":"304792983","full_name":"choonkeat/elm-webapp","owner":"choonkeat","description":"A setup for writing http based, client-server app in elm, inspired wholly by lamdera.com","archived":false,"fork":false,"pushed_at":"2021-11-27T08:03:08.000Z","size":263,"stargazers_count":58,"open_issues_count":1,"forks_count":8,"subscribers_count":8,"default_branch":"main","last_synced_at":"2026-01-14T05:34:34.271Z","etag":null,"topics":["client","elm","fullstack","server","webapp"],"latest_commit_sha":null,"homepage":"https://package.elm-lang.org/packages/choonkeat/elm-webapp/latest","language":"Elm","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/choonkeat.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-10-17T03:59:27.000Z","updated_at":"2025-03-14T12:34:50.000Z","dependencies_parsed_at":"2022-07-08T02:16:19.667Z","dependency_job_id":null,"html_url":"https://github.com/choonkeat/elm-webapp","commit_stats":null,"previous_names":["choonkeat/create-elm-server","choonkeat/elm-fullstack"],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/choonkeat/elm-webapp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/choonkeat%2Felm-webapp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/choonkeat%2Felm-webapp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/choonkeat%2Felm-webapp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/choonkeat%2Felm-webapp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/choonkeat","download_url":"https://codeload.github.com/choonkeat/elm-webapp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/choonkeat%2Felm-webapp/sbom","scorecard":{"id":278784,"data":{"date":"2025-08-11","repo":{"name":"github.com/choonkeat/elm-webapp","commit":"2abb384d9ac7b0b06adb7d0b06981eceae4bb8f3"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 0/17 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 15 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-17T15:11:33.927Z","repository_id":41487954,"created_at":"2025-08-17T15:11:33.927Z","updated_at":"2025-08-17T15:11:33.927Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29650503,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-20T09:27:29.698Z","status":"ssl_error","status_checked_at":"2026-02-20T09:26:12.373Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["client","elm","fullstack","server","webapp"],"created_at":"2024-12-18T04:12:50.997Z","updated_at":"2026-02-20T12:01:09.547Z","avatar_url":"https://github.com/choonkeat.png","language":"Elm","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Elm-Webapp\n\nA setup for writing http based, client-server app in elm, inspired wholly by [Lamdera](https://lamdera.app)\n\n### 1. Message passing\n\nClient and Server communicate with each other using regular [Elm custom type](https://guide.elm-lang.org/types/custom_types.html) values.\n\n[![](https://mermaid.ink/img/eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtXG4gICAgbm90ZSBsZWZ0IG9mIENsaWVudC5lbG06IHR5cGUgTXNnRnJvbUNsaWVudCA9IEhlbGxvIFN0cmluZyB8IEdvb2RieWVcbiAgICBDbGllbnQuZWxtLT4-K1NlcnZlci5lbG06IHNlbmRUb1NlcnZlciAoSGVsbG8gXCJCb2JcIikgOiBDbWQgbXNnXG4gICAgU2VydmVyLmVsbS0tPj4tQ2xpZW50LmVsbTogVGFzay5zdWNjZWVkIChHcmVldCBcIkhpLCBCb2JcIilcbiAgICBub3RlIHJpZ2h0IG9mIFNlcnZlci5lbG06IHR5cGUgTXNnRnJvbVNlcnZlciA9IEdyZWV0IFN0cmluZyB8IEZhcmV3ZWxsXG4gICAgQ2xpZW50LmVsbS0-PitTZXJ2ZXIuZWxtOiBzZW5kVG9TZXJ2ZXIgR29vZGJ5ZSA6IENtZCBtc2dcbiAgICBTZXJ2ZXIuZWxtLS0-Pi1DbGllbnQuZWxtOiBUYXNrLnN1Y2NlZWQgRmFyZXdlbGxcbiAgICBcbiIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2UsImF1dG9TeW5jIjp0cnVlLCJ1cGRhdGVEaWFncmFtIjpmYWxzZX0)](https://mermaid-js.github.io/mermaid-live-editor/edit/#eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtXG4gICAgbm90ZSBsZWZ0IG9mIENsaWVudC5lbG06IHR5cGUgTXNnRnJvbUNsaWVudCA9IEhlbGxvIFN0cmluZyB8IEdvb2RieWVcbiAgICBDbGllbnQuZWxtLT4-K1NlcnZlci5lbG06IHNlbmRUb1NlcnZlciAoSGVsbG8gXCJCb2JcIikgOiBDbWQgbXNnXG4gICAgU2VydmVyLmVsbS0tPj4tQ2xpZW50LmVsbTogVGFzay5zdWNjZWVkIChHcmVldCBcIkhpLCBCb2JcIilcbiAgICBub3RlIHJpZ2h0IG9mIFNlcnZlci5lbG06IHR5cGUgTXNnRnJvbVNlcnZlciA9IEdyZWV0IFN0cmluZyB8IEZhcmV3ZWxsXG4gICAgQ2xpZW50LmVsbS0-PitTZXJ2ZXIuZWxtOiBzZW5kVG9TZXJ2ZXIgR29vZGJ5ZSA6IENtZCBtc2dcbiAgICBTZXJ2ZXIuZWxtLS0-Pi1DbGllbnQuZWxtOiBUYXNrLnN1Y2NlZWQgRmFyZXdlbGxcbiAgICBcbiIsIm1lcm1haWQiOiJ7XG4gIFwidGhlbWVcIjogXCJkZWZhdWx0XCJcbn0iLCJ1cGRhdGVFZGl0b3IiOmZhbHNlLCJhdXRvU3luYyI6dHJ1ZSwidXBkYXRlRGlhZ3JhbSI6ZmFsc2V9)\n\nelm-webapp will encode and decode these values to transmit them over HTTP. Websocket is possible but the code there is less robust.\n\nThough elm-webapp does NOT manage the versioning \u0026 migration of `MsgFromClient` and `MsgFromServer`, the initial generated type definition does come with `ClientServerVersionMismatch` value which [is leveraged](https://github.com/choonkeat/elm-webapp/blob/78c3688cdf266a6338dac0fccd5707f26b0af531/src/Application.elm#L50-L54) to know that the client/server is out of sync and [present a \"Please reload this browser page\" message](https://github.com/choonkeat/elm-webapp/blob/78c3688cdf266a6338dac0fccd5707f26b0af531/src/Application.elm#L162-L165) to the end user.\n\n### 2. Bring Your Own Data Persistence\n\nelm-webapp does NOT persist the \"model\" of the server (aka `serverState`), unlike Lamdera. While you can still write code that update the model of Server, note that the values are only held in memory and is lost when the Server process exits. Doing so is still worthwhile during development though, enabling quick iteration of the app without messing with db \u0026 schema\n\nYou CAN have `Server.elm` instead\n1. make [regular HTTP requests](https://docs.aws.amazon.com/apigateway/api-reference/making-http-requests/) to query and mutate persisted data on DynamoDB (e.g. [the-sett/elm-aws-core](https://package.elm-lang.org/packages/the-sett/elm-aws-core/latest/), [choonkeat/elm-aws](https://package.elm-lang.org/packages/choonkeat/elm-aws/latest/AWS))\n2. make regular GraphQL HTTP requests to Hasura to query and mutate persisted data in a PostgreSQL database (e.g. [graphql-to-elm](https://www.npmjs.com/package/graphql-to-elm), [dillonkearns/elm-graphql](https://package.elm-lang.org/packages/dillonkearns/elm-graphql/latest/))\n\n# Getting started\n\n```\nnpx elm-webapp element hello-app\n```\n\nThis will create a skeleton file directory structure\n\n```\nhello-app\n├── Makefile\n├── index.js\n└── src\n    ├── Client.elm\n    ├── Server.elm\n    ├── Protocol.elm\n    └── Protocol\n        └── Auto.elm\n\n1 directory, 5 files\n```\n\nThe above command generates a barebones `Client` of [Browser.element](https://package.elm-lang.org/packages/elm/browser/latest/Browser#element).\n\n### npx elm-webapp\n\nTo generate a [Browser.document](https://package.elm-lang.org/packages/elm/browser/latest/Browser#document), [Browser.application](https://package.elm-lang.org/packages/elm/browser/latest/Browser#application) or even a full fledged CRUD applications, run the cli without arguments `npx elm-webapp`:\n\n```\nUSAGE:\n\n    elm-webapp \u003ctype\u003e \u003ctarget_directory\u003e\n\nTYPE:\n\n    While the generated \"src/Server.elm\" is the same, you can choose\n    what kind of \"src/Client.elm\" to generate:\n\n        application             generates a standard \"Browser.application\"\n\n        document                generates a standard \"Browser.document\"\n\n        element                 generates a standard \"Browser.element\"\n\n        application-element     generates a standard \"Browser.element\" with\n                                routing capabilities like \"Browser.application\"\n                                but more compatible with browser extensions\n\n    This generates a different \"src/Server.elm\" that comes with \"CRUD\"\n    operations with an in-memory server state: Data is preserved on the\n    Server only while the Server process is running.\n\n        crud \u003cTypeName\u003e         patch the \u003ctarget_directory\u003e with the ability\n                                to list, create, edit, and destroy \"TypeName\"\n                                records\n\nEXAMPLES:\n\n    elm-webapp application helloworld\n\n    elm-webapp document helloworld\n\n    elm-webapp element helloworld\n\n    elm-webapp application-element helloworld\n\n    elm-webapp crud Post blog\n\n```\n\n### Advanced overview\n\nWhile app developers only need to work inside the cyan boxes on the extreme left and right, here's a rough overview of how the pieces are put together end-to-end:\n\n[![](https://mermaid.ink/img/eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtXG4gICAgXG4gICAgcmVjdCByZ2JhKDE3MywgMjU1LCAyNDUsIDEpXG4gICAgbm90ZSByaWdodCBvZiBDbGllbnQuZWxtOiB0eXBlIE1zZ0Zyb21DbGllbnQgPSBIZWxsbyAoTWF5YmUgU3RyaW5nKVxuICAgIENsaWVudC5lbG0tPj5XZWJhcHAuQ2xpZW50OiBzZW5kVG9TZXJ2ZXIgKEhlbGxvIChKdXN0IFwiQWxpY2VcIikpIDogQ21kIG1zZ1xuICAgIGVuZFxuICAgIFdlYmFwcC5DbGllbnQtPj5XZWJhcHAuQ2xpZW50OiBjbGllbnRNc2dFbmNvZGVyXG4gICAgV2ViYXBwLkNsaWVudC0-PmVsbS9odHRwOiBIdHRwLnRhc2tcbiAgICBlbG0vaHR0cC0tPj5pbmRleC5qczogXG4gICAgaW5kZXguanMgLT4-IFdlYmFwcC5TZXJ2ZXI6IHBvcnQgb25IdHRwUmVxdWVzdFxuICAgIFdlYmFwcC5TZXJ2ZXIgLT4-IFdlYmFwcC5TZXJ2ZXI6IGNsaWVudE1zZ0RlY29kZXI8YnI-aGVhZGVyRGVjb2RlclxuICAgIHJlY3QgcmdiYSgxNzMsIDI1NSwgMjQ1LCAxKVxuICAgIFdlYmFwcC5TZXJ2ZXItPj4rU2VydmVyLmVsbTogIHVwZGF0ZUZyb21DbGllbnQgPTxicj5jYXNlIGNsaWVudE1zZyBvZi4uLlxuICAgIFNlcnZlci5lbG0tPj4tV2ViYXBwLlNlcnZlcjogVGFzay5zdWNjZWVkIChHcmVldCBcIkhpLCBBbGljZVwiKVxuICAgIG5vdGUgbGVmdCBvZiBTZXJ2ZXIuZWxtOiB0eXBlIE1zZ0Zyb21TZXJ2ZXIgPSBHcmVldCBTdHJpbmdcbiAgICBlbmRcbiAgICBXZWJhcHAuU2VydmVyIC0-PiBXZWJhcHAuU2VydmVyOiBzZXJ2ZXJNc2dFbmNvZGVyXG4gICAgV2ViYXBwLlNlcnZlciAtPj4gaW5kZXguanM6IHBvcnQgb25IdHRwUmVzcG9uc2VcbiAgICBpbmRleC5qcyAtLT4-IGVsbS9odHRwOiBcbiAgICBlbG0vaHR0cCAtPj4gV2ViYXBwLkNsaWVudDogSHR0cC5yZXNvbHZlclxuICAgIFdlYmFwcC5DbGllbnQgLT4-IFdlYmFwcC5DbGllbnQ6IHNlcnZlck1zZ0RlY29kZXJcbiAgICByZWN0IHJnYmEoMTczLCAyNTUsIDI0NSwgMSlcbiAgICBXZWJhcHAuQ2xpZW50IC0-PiBDbGllbnQuZWxtOiB1cGRhdGVGcm9tU2VydmVyID08YnI-Y2FzZSBzZXJ2ZXJNc2cgb2YgLi4uXG4gICAgZW5kXG4iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlLCJhdXRvU3luYyI6dHJ1ZSwidXBkYXRlRGlhZ3JhbSI6ZmFsc2V9)](https://mermaid-js.github.io/mermaid-live-editor/edit/#eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtXG4gICAgXG4gICAgcmVjdCByZ2JhKDE3MywgMjU1LCAyNDUsIDEpXG4gICAgbm90ZSByaWdodCBvZiBDbGllbnQuZWxtOiB0eXBlIE1zZ0Zyb21DbGllbnQgPSBIZWxsbyAoTWF5YmUgU3RyaW5nKVxuICAgIENsaWVudC5lbG0tPj5XZWJhcHAuQ2xpZW50OiBzZW5kVG9TZXJ2ZXIgKEhlbGxvIChKdXN0IFwiQWxpY2VcIikpIDogQ21kIG1zZ1xuICAgIGVuZFxuICAgIFdlYmFwcC5DbGllbnQtPj5XZWJhcHAuQ2xpZW50OiBjbGllbnRNc2dFbmNvZGVyXG4gICAgV2ViYXBwLkNsaWVudC0-PmVsbS9odHRwOiBIdHRwLnRhc2tcbiAgICBlbG0vaHR0cC0tPj5pbmRleC5qczogXG4gICAgaW5kZXguanMgLT4-IFdlYmFwcC5TZXJ2ZXI6IHBvcnQgb25IdHRwUmVxdWVzdFxuICAgIFdlYmFwcC5TZXJ2ZXIgLT4-IFdlYmFwcC5TZXJ2ZXI6IGNsaWVudE1zZ0RlY29kZXI8YnI-aGVhZGVyRGVjb2RlclxuICAgIHJlY3QgcmdiYSgxNzMsIDI1NSwgMjQ1LCAxKVxuICAgIFdlYmFwcC5TZXJ2ZXItPj4rU2VydmVyLmVsbTogIHVwZGF0ZUZyb21DbGllbnQgPTxicj5jYXNlIGNsaWVudE1zZyBvZi4uLlxuICAgIFNlcnZlci5lbG0tPj4tV2ViYXBwLlNlcnZlcjogVGFzay5zdWNjZWVkIChHcmVldCBcIkhpLCBBbGljZVwiKVxuICAgIG5vdGUgbGVmdCBvZiBTZXJ2ZXIuZWxtOiB0eXBlIE1zZ0Zyb21TZXJ2ZXIgPSBHcmVldCBTdHJpbmdcbiAgICBlbmRcbiAgICBXZWJhcHAuU2VydmVyIC0-PiBXZWJhcHAuU2VydmVyOiBzZXJ2ZXJNc2dFbmNvZGVyXG4gICAgV2ViYXBwLlNlcnZlciAtPj4gaW5kZXguanM6IHBvcnQgb25IdHRwUmVzcG9uc2VcbiAgICBpbmRleC5qcyAtLT4-IGVsbS9odHRwOiBcbiAgICBlbG0vaHR0cCAtPj4gV2ViYXBwLkNsaWVudDogSHR0cC5yZXNvbHZlclxuICAgIFdlYmFwcC5DbGllbnQgLT4-IFdlYmFwcC5DbGllbnQ6IHNlcnZlck1zZ0RlY29kZXJcbiAgICByZWN0IHJnYmEoMTczLCAyNTUsIDI0NSwgMSlcbiAgICBXZWJhcHAuQ2xpZW50IC0-PiBDbGllbnQuZWxtOiB1cGRhdGVGcm9tU2VydmVyID08YnI-Y2FzZSBzZXJ2ZXJNc2cgb2YgLi4uXG4gICAgZW5kXG4iLCJtZXJtYWlkIjoie1xuICBcInRoZW1lXCI6IFwiZGVmYXVsdFwiXG59IiwidXBkYXRlRWRpdG9yIjpmYWxzZSwiYXV0b1N5bmMiOnRydWUsInVwZGF0ZURpYWdyYW0iOmZhbHNlfQ)\n\n## `src/Client.elm`\n\nIn this file, we see\n\n```elm\nwebapp =\n    Webapp.Client.element\n        { element =\n            { init = init\n            , view = view\n            , update = update\n            , subscriptions = subscriptions\n            }\n```\n\n☝️ This record is where we provide our standard [Browser.element](https://package.elm-lang.org/packages/elm/browser/latest/Browser#element), [Browser.document](https://package.elm-lang.org/packages/elm/browser/latest/Browser#document), or [Browser.application](https://package.elm-lang.org/packages/elm/browser/latest/Browser#application)\n\n```elm\n        , ports =\n            { websocketConnected = \\_ -\u003e Sub.none -- websocketConnected\n            , websocketIn = \\_ -\u003e Sub.none -- websocketIn\n            }\n```\n\n☝️ Here's where you can connect a WebSocket port implementation to communicate with `src/Server.elm`. Uncomment to enable.\n\nBy default, `elm-webapp` is wired up to communicate with `src/Server.elm` through regular http `POST /api/elm-webapp`\n\n```elm\n        , protocol =\n            { updateFromServer = updateFromServer\n            , clientMsgEncoder = Protocol.Auto.encodeProtocolMsgFromClient\n            , serverMsgDecoder =\n                Json.Decode.oneOf\n                    [ Protocol.Auto.decodeProtocolMsgFromServer\n                    , Json.Decode.map Protocol.ClientServerVersionMismatch Json.Decode.value\n                    ]\n            , errorDecoder = Json.Decode.string\n            , httpEndpoint = Protocol.httpEndpoint\n            }\n        }\n```\n\n☝️ This section wires up the necessary functions to coordinate with `src/Server.elm`\n\n#### updateFromServer\n\n```elm\nupdateFromServer : MsgFromServer -\u003e Model -\u003e ( Model, Cmd Msg )\n```\nis the entry point where we handle `MsgFromServer` values from `src/Server.elm`. We usually do a `case ... of` statement inside, much like how we write our standard `update` function\n\n#### main\n\n```elm\nmain =\n    webapp.element\n```\n\nthat gives us our `main` function for the client.\n\n#### sendToServer\n\n```elm\nsendToServer : Protocol.MsgFromClient -\u003e Cmd Msg\nsendToServer =\n    webapp.sendToServer \u003e\u003e Task.attempt OnMsgFromServer\n```\n\nsends `MsgFromClient` values to our server whereby the server must respond with a `MsgFromServer` that we've wired to handle in `updateFromServer` (see above). This happens over http post by default, and over websockets if enabled (see above)\n\nThis is how we achieve a seamless and type-safe way for Client-Server communication.\n\n## `src/Server.elm`\n\nserves our `Client` frontend app by default, and can respond to values from `Client.sendToServer` or regular http requests.\n\n```elm\nmain : Program Flags ServerState RequestContext Msg String MsgFromServer\nmain =\n    Webapp.Server.worker\n        { worker =\n            { init = init\n            , update = update\n            , subscriptions = subscriptions\n            }\n```\n☝️ This record is where we provide our standard [Platform.worker](https://package.elm-lang.org/packages/elm/core/latest/Platform#worker)\n\n```elm\n        , ports =\n            { writeResponse = writeResponse\n            , onHttpRequest = onHttpRequest\n            , onWebsocketEvent = \\_ -\u003e Sub.none -- onWebsocketEvent\n            , writeWebsocketMessage = \\_ _ _ -\u003e Cmd.none -- writeWebsocketMessage\n            }\n```\n\n☝️ Here's where we've connected our httpserver with Elm ports. You can connect a WebSocket server Elm port too; uncomment to enable.\n\n```elm\n        , protocol =\n            { routeDecoder = routeDecoder\n            , updateFromRoute = updateFromRoute\n            , updateFromClient = updateFromClient\n            , serverMsgEncoder = Protocol.Auto.encodeProtocolMsgFromServer\n            , clientMsgDecoder = Protocol.Auto.decodeProtocolMsgFromClient\n            , headerDecoder = headerDecoder\n            , errorEncoder = Json.Encode.string\n            , httpEndpoint = Protocol.httpEndpoint\n            }\n        }\n```\n☝️ This section wires up the necessary functions to coordinate with `src/Client.elm`\n\n#### updateFromClient\n\n```elm\nupdateFromClient : RequestContext -\u003e Time.Posix -\u003e MsgFromClient -\u003e ServerState -\u003e ( ServerState, Task String MsgFromServer )\n```\nis called whenever the `Client` sends a value over with its `sendToServer`. We usually do a `case ... of` statement inside, much like how we write our standard `update` function\n\n#### updateFromRoute\n\n```elm\nupdateFromRoute : ( Method, RequestContext, Maybe Route ) -\u003e Time.Posix -\u003e Request -\u003e ServerState -\u003e ( ServerState, Cmd Msg )\n```\nis the catch-all handler for http request; called whenever `Server` has to handle a http request that isn't handled by `updateFromClient`. e.g. oauth redirect path.\n\nNote that `ServerState` is simply `Model` you see in standard Elm apps; named differently.\n\n#### headerDecoder\n\n```elm\nheaderDecoder : ServerState -\u003e Json.Decode.Decoder RequestContext\n```\nis applied to http request headers and gives us a more meaningfully categorised `RequestContext` . e.g. we can decode the `Authorization` header and determine if the JWT value gives us a valid `LoggedInUser Email` or an `AnonymousUser`\n\nThis difference can be put into good use when we handle `updateFromClient` or `updateFromRoute`\n\n## Other files\n\n- `index.js` boots up our `src/Server.elm`\n\t- by default, `node.js`  runs [`http.createServer`](https://nodejs.org/api/http.html#http_http_createserver_options_requestlistener) and let Elm handles http request and write responses via Elm ports\n\t- if env `LAMBDA` is set, `lambda.js` will instead setup a callback so we can handle http request inside [AWS Lambda behind an API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started-with-lambda-integration.html).\n\t- other possible integrations are `cloudflare-workers.js` or even [`deno-deploy.js`](https://deno.com/deploy)\n\t- PRs are extremely welcome to improve the robustness of these integrations 🙇‍♂️\n- `src/Protocol.elm` holds the types shared between Server and Client.\n    - [encoders \u0026 decoders are auto-generated](https://github.com/choonkeat/elm-auto-encoder-decoder) in `src/Protocol/Auto.elm` ; also see [gotchas regarding imported types](https://github.com/choonkeat/elm-auto-encoder-decoder#dont-be-alarmed-with-i-cannot-find--variable-compiler-errors)\n    - we're using `elm-auto-encoder-decoder` in `elm-webapp` only for convenience; you can switch it out for your own encoders \u0026 decoders. BUT if you continue using `elm-auto-encoder-decoder`, don't use them anywhere else (e.g. as encoder to save in db, exposed as part of your external api, etc...). Main reason being that the serialized format could change future releases of `elm-auto-encoder-decoder` and thus MUST NOT be relied on.\n\n# How do I...\n\n- Support OAuth login? See https://github.com/choonkeat/elm-webapp-oauth-example#readme\n\n# License\n\nCopyright © 2021 Chew Choon Keat\n\nDistributed under the MIT license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchoonkeat%2Felm-webapp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchoonkeat%2Felm-webapp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchoonkeat%2Felm-webapp/lists"}