3[](https://travis-ci.org/imalsogreg/servant-reflex)
5## The problem `servant-reflex` solves
7Keeping your frontend in sync with your API server can be difficult - when the API changes its input parameters or return type, XHR requests from the frontend will fail at runtime. If your API is defined by [servant](haskell-servant.readthedocs.io) combinators, you can use `servant-reflex` to share the API between the server and frontend.
8Syncronization between is checked at compile time, and rather than building XHR requests by hand, API endpoints are available behind `reflex`'s FRP semantics.
14We have a webservice API defined in a module where both the server (compiled with ghc) and the frontend (compiled with ghcjs) can see it:
17type API = "getint" :> Get '[JSON] Int
18 :<|> "sayhi" :> QueryParam "username" Text
19 :> QueryParams "greetings" Text
22 :<|> "double" :> ReqBody '[JSON] Double
23 :> Post '[JSON] Double
27`servant-reflex` then computes client functions that can query the API through an `XhrRequest`.
31 runGUI :: forall t m.MonadWidget t m => do
33 -- servant-reflex computes FRP functions for each API endpoint
34 let (getint :<|> sayhi :<|> doubleit :<|> _) = client (Proxy :: Proxy API)
36 (constDyn (BasePath "/"))
39These client functions are computed from your API type. They manage serialization, XhrRequest generation, and deserialization for you. `a` parameters used in URL captures become `Dynamic t (Either Text a)` parameters in the client functions. 'QueryFlag', 'QueryParams' and 'QueryParam' API parameters map to 'Dynamic t Bool', 'Dynamic t [a]' and 'Dynamic t (QParam a)' respectively. These parameters to the client function are wrapped with failure possibility to allow you to indicate at any time whether input validation for that parameter has failed and no valid XHR request can be generated. The final parameter is a trigger event for the XHR request. The return value `Event t (ReqResult a)` contains responses from the API server.
42 -- No need to write these functions. servant-reflex creates them for you!
43 getint :: MonadWidget t m
44 => Event t () -- ^ Trigger the XHR Request
45 -> m (Event t (ReqResult Int)) -- ^ Consume the answer
47 sayhi :: MonadWidget t m
48 => Dynamic t (QParam Text)
49 -- ^ One input parameter - the 'name', wrapped in 'QParam'
51 -- ^ Another input: list of preferred greetings
53 -- ^ Flag for capitalizing the response
55 -- ^ Trigger the XHR Request
56 -> m (Event t (ReqResult Text))
58 doubleit :: MonadWidget t m
59 => Dynamic t (Either Text Double)
61 -> m (Event t (ReqResult Double))
64`ReqResult a` is defined in [`Servant.Common.Req`](https://github.com/imalsogreg/servant-reflex/blob/6d866e338edb9bf6fd8f8d5083ff0187b4d8c0d2/src/Servant/Common/Req.hs#L40-L42) and reports whether or not your request was sent (if validation fails, the request won't be sent), and how decoding of the response went. You can pattern match on these explicitly, but usually you'll want to use `fmapMaybe :: (a -> Maybe b) -> Event t a -> Event t b` and one of the elimination functions to filter the result type you care about, like this:
68 res :: Event t (ReqResult Double) <- doubleIt xs triggers
69 let ys = fmapMaybe reqSuccess res
70 errs = fmapMaybe reqFailure res
72 -- Green <p> tag showing the last good result
73 elAttr "p" ("style" =: "color:green") $ do
74 text "Last good result: "
75 dynText =<< holdDyn "" (fmap show ys)
77 -- Red <p> tag showing the last error, cleared by a new good value
78 elAttr "p" ("style" =: "color:red") $
79 dynText =<< holdDyn "" (leftmost [errs, const "" <$> ys])
82This example builds some input fields to enter API parameters, buttons to trigger the API calls, and text elements to show the results:
85 elClass "div" "int-demo" $ do
86 intButton <- button "Get Int"
87 serverInts <- fmapMaybe resSuccess <$> getint intButton
88 display =<< holdDyn (Just 0) serverInts
90 elClass "div" "hello-demo" $ do
91 nameText <- QParamSome . value <$> textInput def
92 greetings <- (fmap words . value) <$> textInput def
93 withGusto <- checkbox def
94 helloButton <- button "Say hi"
95 hellos <- fmapMaybe resResult <$> sayhi nameText greetings withGusto helloButton
96 display =<< holdDyn Nothing hellos
98 elClass "div" "demo-double" $ do
99 inputDouble <- (fmapMaybe readMaybe) <$> textInput def
100 doubleButton <- button "Double it"
101 outputDouble <- fmapMaybe resSuccess <$> doubleit inputDouble doubleButton
102 display =<< holdDyn Nothing outputDouble
105For a great introduction to recative DOM building, see the [README](https://github.com/reflex-frp/reflex-platform) for the `reflex-platform`. For more information about servant, see their [documentation](http://haskell-servant.readthedocs.io/en/stable/). Thanks to the respective authors of these fabulous libraries.
108## Building the library and test server
110This repository comes with a small example of an API shared between a ghcjs-compiled frontend ([exec/](https://github.com/imalsogreg/servant-reflex/tree/master/exec)) and a ghc-compiled backend ([testserver/](https://github.com/imalsogreg/servant-reflex/tree/master/testserver). To build these components:
113First build the library:
116git submodule update --init --recursive
120Then build the test server:
123deps/reflex-platform/work-on ./overrides-ghc.nix ./testserver --command "cd testserver && cabal build"
127## Running the example site
129The server must be run from the directory where static assets live:
133dist/build/back/back -p 8001
136And simply browse to `localhost:8001`
138**For a larger example of a project that shares types between backend and frontend, see [hsnippet](https://github.com/mightybyte/hsnippet).**
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list