{"id":15474855,"url":"https://github.com/rhyskeepence/elm-websocket","last_synced_at":"2025-04-22T14:09:23.980Z","repository":{"id":62435898,"uuid":"100035032","full_name":"rhyskeepence/elm-websocket","owner":"rhyskeepence","description":"Generate an Elm Subscriber and JSON encoders/decoders for a Wai WebSocket server","archived":false,"fork":false,"pushed_at":"2017-11-02T21:15:14.000Z","size":76,"stargazers_count":6,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-22T14:09:19.702Z","etag":null,"topics":["elm","haskell","websocket"],"latest_commit_sha":null,"homepage":null,"language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rhyskeepence.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2017-08-11T13:33:23.000Z","updated_at":"2019-08-28T11:51:48.000Z","dependencies_parsed_at":"2022-11-01T21:16:35.500Z","dependency_job_id":null,"html_url":"https://github.com/rhyskeepence/elm-websocket","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/rhyskeepence%2Felm-websocket","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rhyskeepence%2Felm-websocket/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rhyskeepence%2Felm-websocket/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rhyskeepence%2Felm-websocket/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rhyskeepence","download_url":"https://codeload.github.com/rhyskeepence/elm-websocket/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250255697,"owners_count":21400410,"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":["elm","haskell","websocket"],"created_at":"2024-10-02T03:05:20.948Z","updated_at":"2025-04-22T14:09:23.960Z","avatar_url":"https://github.com/rhyskeepence.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Elm WebSocket\n\nGenerate an Elm Subscriber and JSON encoders/decoders for a Wai WebSocket server.\n\nElm encode/decode is generated thanks to [krisajenkins/elm-export](https://github.com/krisajenkins/elm-export), with ADT generation thanks to [FPtje](https://github.com/FPtje).\n\n## Installation\n\nAvailable on [Hackage](https://hackage.haskell.org/package/elm-websocket-1.0) as ```elm-websocket```\n\n## Usage\n\nThis package broadly does two things:\n 1. A library for creating a Wai WebSocket service, which can respond to requests as well as broadcast to all clients.\n 2. Generates the Elm code for data types, JSON encoders/decoders and a WebSocket subscriber\n\nFirst let's create our API - some haskell types to model our Request and Response. We derive Generic and ElmType,  \nso that [Elm Export](https://github.com/krisajenkins/elm-export) can do it's thing. We also \nneed a FromJSON instance for the Request, and ToJSON instance for the Response.\n\n```haskell\n{-# LANGUAGE DeriveGeneric #-}\n{-# LANGUAGE DeriveAnyClass #-}\n\nimport Elm.Export\nimport GHC.Generics (Generic)\nimport Data.Aeson (FromJSON, ToJSON)\n\nmodule Api where\n\ndata Request\n  = CreateTask Text Text\n  | LoadAllTasks\n  deriving (Eq, Show, Generic, ElmType)\n\ninstance FromJSON Request\n\ndata Response\n  = AllTasksResponse [Task]\n  | TaskCountUpdate Int\n  deriving (Eq, Show, Generic, ElmType)\n\ninstance ToJSON Response\n```\n\nNext, let's write a function to handle WebSocket requests.\n\nLet's also add a `Broadcaster`, so that when a task is created, we broadcast a `TaskCountUpdate` to all connected clients:\n\n```haskell\nimport Elm.WebSocket\n\nwebSocketService :: Broadcaster -\u003e WebSocketServer Request Response\nwebSocketService broadcaster request =\n  case request of\n    CreateTask name description -\u003e do\n      -- do some IO to create the task\n      taskCount \u003c- -- get the new taskCount \n      broadcast broadcaster $ TaskCountUpdate taskCount \n      return Nothing\n    LoadAllTasks -\u003e do      \n      tasks \u003c- -- do some IO to fetch all tasks      \n      return $ Just $ AllTasksResponse tasks\n```\n\nNext, create a server. In this example, `httpApplication` can be any IO Wai.Application, for example a [Scotty](https://hackage.haskell.org/package/scotty) or [Servant](https://hackage.haskell.org/package/servant) application.\n\n```haskell\nimport Api \nimport Elm.WebSocket\nimport Network.Wai.Handler.Warp (run)\n\nmain :: IO ()\nmain = do\n  httpApplication \u003c- ...\n  broadcaster \u003c- newBroadcaster\n  let webSocketApp = webSocketService broadcaster\n  run 8080 $ withWebSocketBroadcaster broadcaster webSocketApp httpApplication     \n```\n\nThat's it from the server side. We have built a server which can respond to WebSocket requests, \nas well as send broadcast messages to all clients. Note that the request, response and broadcast types\nare our Haskell types - JSON encoding/decoding is done under the covers.\n\nThe next bit is the Elm side. This package includes a code generator for Elm types, JSON encoders/decoders, and WebSocket subscription.\n\nCreate a main to generate the Elm source from our Haskell API:\n\n```haskell\nmodule Main where\n\nimport Api\nimport Data.Proxy\nimport Elm.Export\n\nspec :: Spec\nspec =\n  moduleSpec [\"Api\"] $ do\n    renderType (Proxy :: Proxy Request)\n    renderType (Proxy :: Proxy Response)\n    renderEncoder (Proxy :: Proxy Request)\n    renderDecoder (Proxy :: Proxy Response)\n    renderSubscriber (Proxy :: Proxy Request) (Proxy :: Proxy Response)\n\nmain :: IO ()\nmain = specsToDir [spec] \"client/src\"\n```\n\nNote that `renderSubscriber` takes the `Request` and `Response` type - this is so that the generated `listen` and `send` functions\nexpect the correct types, and the compiler not allow any other type to be sent or received.\n\nRun this, and `client/src/Api.elm` will be created.\n\nWe can then subscribe to WebSocket responses, and trigger a Msg of type `Receive (Result String Response)`:\n\n```elm\nsubscriptions : Sub Msg\nsubscriptions =\n    Api.listen \"hostname:port\" Receive\n```\n\nWe can also send WebSocket requests to the server, using this Cmd:\n\n```elm\n    Api.send \"hostname:port\" (Api.CreateTask \"name\" \"description\")\n```\n\n#### Elm Notes\n\n 1. The listen and send functions require the hostname of the server. This can be taken from the browser location and stored in the model, by using the `elm-lang/navigation` package. \n 2. The generated Elm code requires the following packages:\n   ```\n   elm package install elm-lang/websocket\n   elm package install NoRedInk/elm-decode-pipeline\n   elm package install krisajenkins/elm-exts\n   ```\n\n## Application design notes\n\nThe example application makes all requests and responses over WebSockets. From a performance standpoint\nthis design can be good, as the connection remains open so HTTP negotiation is minimised. However, it requires that\nclients of your API use WebSockets, which may not be ideal. It also makes it difficult to perform ad-hoc requests using cURL/Postman/etc,\nand viewing network activity is not as simple compared to a traditional REST API.\n\nIt is possible to use this library to write a system that uses REST to make Requests, and still broadcast events in HTTP request handlers, i.e., `liftIO $ broadcast ...`. \nFurthermore, the Elm boilerplate can be reduced if the REST API uses the generated Request and Response types.\n\n## Example application\n\nAn example 'Task Management' application is included in this repository under the example directory. It can be built and run using the following command\n\n```\n$ make setup\n$ make run-example\n```\n\nThis will build the haskell application, as well as the Elm client, and serve it at [localhost:8080](http://localhost:8080).\n\nNote: the run-example makefile depends on stack, elm-make, forever (node.js), chokidar (node.js) and browser-sync (node.js)\n \nYou can also run `make browser-sync` to bring up the app, and reload when changes are made. \n\n## Development\n\n```\n$ git clone https://github.com/rhyskeepence/elm-websocket.git\n$ cd elm-websocket\n$ make test\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frhyskeepence%2Felm-websocket","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frhyskeepence%2Felm-websocket","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frhyskeepence%2Felm-websocket/lists"}