on 8b1bf02 help sign in
servant/doc/tutorial/javascript.lhs
191 lines 6.1 kB history raw
1---
2title: Deriving Javascript functions to query an API
3toc: true
4---
6We will now see how *servant* lets you turn an API type into javascript
7functions that you can call to query a webservice. The derived code assumes you
8use *jQuery* but you could very easily adapt the code to generate ajax requests
9based on vanilla javascript or another library than *jQuery*.
11For this, we will consider a simple page divided in two parts. At the top, we
12will have a search box that lets us search in a list of Haskell books by
13author/title with a list of results that gets updated every time we enter or
14remove a character, while at the bottom we will be able to see the classical
15[probabilistic method to approximate
16pi](http://en.wikipedia.org/wiki/Approximations_of_%CF%80#Summing_a_circle.27s_area),
17using a webservice to get random points. Finally, we will serve an HTML file
18along with a couple of Javascript files, among which one that's automatically
19generated from the API type and which will provide ready-to-use functions to
20query your API.
22The source for this tutorial section is a literate haskell file, so first we
23need to have some language extensions and imports:
25``` haskell
26{-# LANGUAGE DataKinds #-}
27{-# LANGUAGE DeriveGeneric #-}
28{-# LANGUAGE OverloadedStrings #-}
29{-# LANGUAGE TypeOperators #-}
31module Javascript where
33import Control.Monad.IO.Class
34import Data.Aeson
35import Data.Proxy
36import Data.Text (Text)
37import qualified Data.Text as T
38import GHC.Generics
39import Language.Javascript.JQuery
40import Network.Wai
41import Servant
42import Servant.JQuery
43import System.Random
44```
46Now let's have the API type(s) and the accompanying datatypes.
48``` haskell
49type API = "point" :> Get '[JSON] Point
50 :<|> "books" :> QueryParam "q" Text :> Get '[JSON] (Search Book)
52type API' = API :<|> Raw
54data Point = Point
55 { x :: Double
56 , y :: Double
57 } deriving Generic
59instance ToJSON Point
61data Search a = Search
62 { query :: Text
63 , results :: [a]
64 } deriving Generic
66mkSearch :: Text -> [a] -> Search a
67mkSearch = Search
69instance ToJSON a => ToJSON (Search a)
71data Book = Book
72 { author :: Text
73 , title :: Text
74 , year :: Int
75 } deriving Generic
77instance ToJSON Book
79book :: Text -> Text -> Int -> Book
80book = Book
81```
83We need a "book database". For the purpose of this guide, let's restrict ourselves to the following books.
85``` haskell
86books :: [Book]
87books =
88 [ book "Paul Hudak" "The Haskell School of Expression: Learning Functional Programming through Multimedia" 2000
89 , book "Bryan O'Sullivan, Don Stewart, and John Goerzen" "Real World Haskell" 2008
90 , book "Miran Lipovača" "Learn You a Haskell for Great Good!" 2011
91 , book "Graham Hutton" "Programming in Haskell" 2007
92 , book "Simon Marlow" "Parallel and Concurrent Programming in Haskell" 2013
93 , book "Richard Bird" "Introduction to Functional Programming using Haskell" 1998
94 ]
95```
97Now, given an optional search string `q`, we want to perform a case insensitive search in that list of books. We're obviously not going to try and implement the best possible algorithm, this is out of scope for this tutorial. The following simple linear scan will do, given how small our list is.
99``` haskell
100searchBook :: Monad m => Maybe Text -> m (Search Book)
101searchBook Nothing = return (mkSearch "" books)
102searchBook (Just q) = return (mkSearch q books')
104 where books' = filter (\b -> q' `T.isInfixOf` T.toLower (author b)
105 || q' `T.isInfixOf` T.toLower (title b)
106 )
107 books
108 q' = T.toLower q
109```
111We also need an endpoint that generates random points `(x, y)` with `-1 <= x,y <= 1`. The code below uses [random](http://hackage.haskell.org/package/random)'s `System.Random`.
113``` haskell
114randomPoint :: MonadIO m => m Point
115randomPoint = liftIO . getStdRandom $ \g ->
116 let (rx, g') = randomR (-1, 1) g
117 (ry, g'') = randomR (-1, 1) g'
118 in (Point rx ry, g'')
119```
121If we add static file serving, our server is now complete.
123``` haskell
124api :: Proxy API
125api = Proxy
127api' :: Proxy API'
128api' = Proxy
130server :: Server API
131server = randomPoint
132 :<|> searchBook
134server' :: Server API'
135server' = server
136 :<|> serveDirectory "tutorial/t9"
138app :: Application
139app = serve api' server'
140```
142Why two different API types, proxies and servers though? Simply because we don't want to generate javascript functions for the `Raw` part of our API type, so we need a `Proxy` for our API type `API'` without its `Raw` endpoint.
144Very similarly to how one can derive haskell functions, we can derive the javascript with just a simple function call to `jsForAPI` from `Servant.JQuery`.
146``` haskell
147apiJS :: String
148apiJS = jsForAPI api
149```
151This `String` contains 2 Javascript functions:
153``` javascript
155function getpoint(onSuccess, onError)
157 $.ajax(
158 { url: '/point'
159 , success: onSuccess
160 , error: onError
161 , method: 'GET'
162 });
165function getbooks(q, onSuccess, onError)
167 $.ajax(
168 { url: '/books' + '?q=' + encodeURIComponent(q)
169 , success: onSuccess
170 , error: onError
171 , method: 'GET'
172 });
174```
176Right before starting up our server, we will need to write this `String` to a file, say `api.js`, along with a copy of the *jQuery* library, as provided by the [js-jquery](http://hackage.haskell.org/package/js-jquery) package.
178``` haskell
179writeJSFiles :: IO ()
180writeJSFiles = do
181 writeFile "getting-started/gs9/api.js" apiJS
182 jq <- readFile =<< Language.Javascript.JQuery.file
183 writeFile "getting-started/gs9/jq.js" jq
184```
186And we're good to go. Start the server with `dist/build/tutorial/tutorial 9` and go to `http://localhost:8081/`. Start typing in the name of one of the authors in our database or part of a book title, and check out how long it takes to approximate &pi; using the method mentioned above.
188<div style="text-align: center;">
189 <p><a href="/tutorial/client.html">Previous page: Deriving Haskell functions to query an API</a></p>
190 <p><a href="/tutorial/docs.html">Next page: Generating documentation for APIs</a></p>
191</div>