on 7af73d6 help sign in
servant/doc/tutorial/javascript.lhs
175 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> {-# LANGUAGE DataKinds #-}
26> {-# LANGUAGE DeriveGeneric #-}
27> {-# LANGUAGE OverloadedStrings #-}
28> {-# LANGUAGE TypeOperators #-}
30> module Javascript where
32> import Control.Monad.IO.Class
33> import Data.Aeson
34> import Data.Proxy
35> import Data.Text (Text)
36> import qualified Data.Text as T
37> import GHC.Generics
38> import Language.Javascript.JQuery
39> import Network.Wai
40> import Servant
41> import Servant.JQuery
42> import System.Random
44Now let's have the API type(s) and the accompanying datatypes.
46> type API = "point" :> Get '[JSON] Point
47> :<|> "books" :> QueryParam "q" Text :> Get '[JSON] (Search Book)
49> type API' = API :<|> Raw
51> data Point = Point
52> { x :: Double
53> , y :: Double
54> } deriving Generic
56> instance ToJSON Point
58> data Search a = Search
59> { query :: Text
60> , results :: [a]
61> } deriving Generic
63> mkSearch :: Text -> [a] -> Search a
64> mkSearch = Search
66> instance ToJSON a => ToJSON (Search a)
68> data Book = Book
69> { author :: Text
70> , title :: Text
71> , year :: Int
72> } deriving Generic
74> instance ToJSON Book
76> book :: Text -> Text -> Int -> Book
77> book = Book
79We need a "book database". For the purpose of this guide, let's restrict ourselves to the following books.
81> books :: [Book]
82> books =
83> [ book "Paul Hudak" "The Haskell School of Expression: Learning Functional Programming through Multimedia" 2000
84> , book "Bryan O'Sullivan, Don Stewart, and John Goerzen" "Real World Haskell" 2008
85> , book "Miran Lipovača" "Learn You a Haskell for Great Good!" 2011
86> , book "Graham Hutton" "Programming in Haskell" 2007
87> , book "Simon Marlow" "Parallel and Concurrent Programming in Haskell" 2013
88> , book "Richard Bird" "Introduction to Functional Programming using Haskell" 1998
89> ]
91Now, 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.
93> searchBook :: Monad m => Maybe Text -> m (Search Book)
94> searchBook Nothing = return (mkSearch "" books)
95> searchBook (Just q) = return (mkSearch q books')
97> where books' = filter (\b -> q' `T.isInfixOf` T.toLower (author b)
98> || q' `T.isInfixOf` T.toLower (title b)
99> )
100> books
101> q' = T.toLower q
103We 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`.
105> randomPoint :: MonadIO m => m Point
106> randomPoint = liftIO . getStdRandom $ \g ->
107> let (rx, g') = randomR (-1, 1) g
108> (ry, g'') = randomR (-1, 1) g'
109> in (Point rx ry, g'')
111If we add static file serving, our server is now complete.
113> api :: Proxy API
114> api = Proxy
116> api' :: Proxy API'
117> api' = Proxy
119> server :: Server API
120> server = randomPoint
121> :<|> searchBook
123> server' :: Server API'
124> server' = server
125> :<|> serveDirectory "tutorial/t9"
127> app :: Application
128> app = serve api' server'
130Why 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.
132Very similarly to how one can derive haskell functions, we can derive the javascript with just a simple function call to `jsForAPI` from `Servant.JQuery`.
134> apiJS :: String
135> apiJS = jsForAPI api
137This `String` contains 2 Javascript functions:
139``` javascript
141function getpoint(onSuccess, onError)
143 $.ajax(
144 { url: '/point'
145 , success: onSuccess
146 , error: onError
147 , method: 'GET'
148 });
151function getbooks(q, onSuccess, onError)
153 $.ajax(
154 { url: '/books' + '?q=' + encodeURIComponent(q)
155 , success: onSuccess
156 , error: onError
157 , method: 'GET'
158 });
160```
162Right 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.
164> writeJSFiles :: IO ()
165> writeJSFiles = do
166> writeFile "getting-started/gs9/api.js" apiJS
167> jq <- readFile =<< Language.Javascript.JQuery.file
168> writeFile "getting-started/gs9/jq.js" jq
170And 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.
172<div style="text-align: center;">
173 <p><a href="/tutorial/client.html">Previous page: Deriving Haskell functions to query an API</a></p>
174 <p><a href="/tutorial/docs.html">Next page: Generating documentation for APIs</a></p>
175</div>