6Enough chit-chat about type-level combinators and representing an API as a
7type. Can we have a webservice already?
9If you want to follow along with the code and run the examples while you read this guide:
12cabal get servant-examples
13cd servant-examples-<VERSION>
15cabal install --dependencies-only
16cabal configure && cabal build
19This will produce a `tutorial` executable in the
20`dist/build/tutorial` directory that just runs the example corresponding
21to the number specified as a command line argument:
24$ dist/build/tutorial/tutorial
26 where N is the number of the example you want to run.
32Equipped with some basic knowledge about the way we represent API, let's now write our first webservice.
34The source for this tutorial section is a literate haskell file, so first we
35need to have some language extensions and imports:
37> {-# LANGUAGE DataKinds #-}
38> {-# LANGUAGE DeriveGeneric #-}
39> {-# LANGUAGE FlexibleInstances #-}
40> {-# LANGUAGE GeneralizedNewtypeDeriving #-}
41> {-# LANGUAGE MultiParamTypeClasses #-}
42> {-# LANGUAGE OverloadedStrings #-}
43> {-# LANGUAGE ScopedTypeVariables #-}
44> {-# LANGUAGE TypeOperators #-}
48> import Control.Monad.IO.Class
49> import Control.Monad.Reader
50> import Control.Monad.Trans.Either
52> import Data.Aeson.Types
53> import Data.Attoparsec.ByteString
54> import Data.ByteString (ByteString)
57> import Data.String.Conversions
58> import Data.Time.Calendar
61> import Network.HTTP.Media ((//), (/:))
63> import Network.Wai.Handler.Warp
65> import System.Directory
67> import Text.Blaze.Html.Renderer.Utf8
68> import qualified Data.Aeson.Parser
69> import qualified Text.Blaze.Html
72{-# LANGUAGE TypeFamilies #-}
75**Important**: the `Servant` module comes from the *servant-server* package, the one that lets us run webservers that implement a particular API type. It reexports all the types from the *servant* package that let you declare API types as well as everything you need to turn your request handlers into a fully-fledged webserver. This means that in your applications, you can just add *servant-server* as a dependency, import `Servant` and not worry about anything else.
77We will write a server that will serve the following API.
79> type UserAPI1 = "users" :> Get '[JSON] [User]
81Here's what we would like to see when making a GET request to `/users`.
84[ {"name": "Isaac Newton", "age": 372, "email": "isaac@newton.co.uk", "registration_date": "1683-03-01"}
85, {"name": "Albert Einstein", "age": 136, "email": "ae@mc2.org", "registration_date": "1905-12-01"}
89Now let's define our `User` data type and write some instances for it.
95> , registration_date :: Day
96> } deriving (Eq, Show, Generic)
100Nothing funny going on here. But we now can define our list of two users.
104> [ User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
105> , User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
108Let's also write our API type.
111type UserAPI1 = "users" :> Get '[JSON] [User]
114We can now take care of writing the actual webservice that will handle requests
115to such an API. This one will be very simple, being reduced to just a single
116endpoint. The type of the web application is determined by the API type,
117through a *type family* named `Server`. (Type families are just functions that
118take types as input and return types.) The `Server` type family will compute
119the right type that a bunch of request handlers should have just from the
120corresponding API type.
122The first thing to know about the `Server` type family is that behind the
123scenes it will drive the routing, letting you focus only on the business
124logic. The second thing to know is that for each endpoint, your handlers will
125by default run in the `EitherT ServantErr IO` monad. This is overridable very
126easily, as explained near the end of this guide. Third thing, the type of the
127value returned in that monad must be the same as the second argument of the
128HTTP method combinator used for the corresponding endpoint. In our case, it
129means we must provide a handler of type `EitherT ServantErr IO [User]`. Well,
130we have a monad, let's just `return` our list:
132> server1 :: Server UserAPI1
133> server1 = return users1
135That's it. Now we can turn `server` into an actual webserver using [wai](http://hackage.haskell.org/package/wai) and [warp](http://hackage.haskell.org/package/warp):
137> userAPI :: Proxy UserAPI1
140> -- 'serve' comes from servant and hands you a WAI Application,
141> -- which you can think of as an "abstract" web application,
142> -- not yet a webserver.
144> app1 = serve userAPI server1
146The `userAPI` bit is, alas, boilerplate (we need it to guide type inference).
147But that's about as much boilerplate as you get.
149And we're done! Let's run our webservice on the port 8081.
152> main = run 8081 app1
154You can put this all into a file or just grab [servant's
155repo](http://github.com/haskell-servant/servant) and look at the
156*servant-examples* directory. The code we have just explored is in
157*tutorial/T1.hs*, runnable with
158`dist/build/tutorial/tutorial 1`.
160If you run it, you can go to `http://localhost:8081/users` in your browser or
161query it with curl and you see:
164$ curl http://localhost:8081/users
165[{"email":"isaac@newton.co.uk","registration_date":"1683-03-01","age":372,"name":"Isaac Newton"},{"email":"ae@mc2.org","registration_date":"1905-12-01","age":136,"name":"Albert Einstein"}]
171What if we want more than one endpoint? Let's add `/albert` and `/isaac` to view the corresponding users encoded in JSON.
173> type UserAPI2 = "users" :> Get '[JSON] [User]
174> :<|> "albert" :> Get '[JSON] User
175> :<|> "isaac" :> Get '[JSON] User
177And let's adapt our code a bit.
180> isaac = User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
183> albert = User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
186> users2 = [isaac, albert]
188Now, just like we separate the various endpoints in `UserAPI` with `:<|>`, we
189are going to separate the handlers with `:<|>` too! They must be provided in
190the same order as the one they appear in in the API type.
192> server2 :: Server UserAPI2
193> server2 = return users2
197And that's it! You can run this example with
198`dist/build/tutorial/tutorial 2` and check out the data available
199at `/users`, `/albert` and `/isaac`.
201From combinators to handler arguments
202=====================================
204Fine, we can write trivial webservices easily, but none of the two above use
205any "fancy" combinator from servant. Let's address this and use `QueryParam`,
206`Capture` and `ReqBody` right away. You'll see how each occurence of these
207combinators in an endpoint makes the corresponding handler receive an
208argument of the appropriate type automatically. You don't have to worry about
209manually looking up URL captures or query string parameters, or
210decoding/encoding data from/to JSON. Never.
212We are going to use the following data types and functions to implement a server for `API`.
214> type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
215> :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
216> :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
218> data Position = Position
223> instance ToJSON Position
225> newtype HelloMessage = HelloMessage { msg :: String }
228> instance ToJSON HelloMessage
230> data ClientInfo = ClientInfo
231> { clientName :: String
232> , clientEmail :: String
234> , clientInterestedIn :: [String]
237> instance FromJSON ClientInfo
238> instance ToJSON ClientInfo
247> instance ToJSON Email
249> emailForClient :: ClientInfo -> Email
250> emailForClient c = Email from' to' subject' body'
252> where from' = "great@company.com"
254> subject' = "Hey " ++ clientName c ++ ", we miss you!"
255> body' = "Hi " ++ clientName c ++ ",\n\n"
256> ++ "Since you've recently turned " ++ show (clientAge c)
257> ++ ", have you checked out our latest "
258> ++ intercalate ", " (clientInterestedIn c)
259> ++ " products? Give us a visit!"
261We can implement handlers for the three endpoints:
263> server3 :: Server API
268> where position :: Int -> Int -> EitherT ServantErr IO Position
269> position x y = return (Position x y)
271> hello :: Maybe String -> EitherT ServantErr IO HelloMessage
272> hello mname = return . HelloMessage $ case mname of
273> Nothing -> "Hello, anonymous coward"
274> Just n -> "Hello, " ++ n
276> marketing :: ClientInfo -> EitherT ServantErr IO Email
277> marketing clientinfo = return (emailForClient clientinfo)
279Did you see that? The types for your handlers changed to be just what we
280needed! In particular:
282 - a `Capture "something" a` becomes an argument of type `a` (for `position`);
283 - a `QueryParam "something" a` becomes an argument of type `Maybe a` (because
284an endpoint can technically be accessed without specifying any query
285string parameter, we decided to "force" handlers to be aware that the
286parameter might not always be there);
288 - a `ReqBody contentTypeList a` becomes an argument of type `a`;
290And that's it. You can see this example in action by running `dist/build/tutorial/tutorial 3`.
293$ curl http://localhost:8081/position/1/2
295$ curl http://localhost:8081/hello
296{"msg":"Hello, anonymous coward"}
297$ curl http://localhost:8081/hello?name=Alp
299$ curl -X POST -d '{"name":"Alp Mestanogullari", "email" : "alp@foo.com", "age": 25, "interested_in": ["haskell", "mathematics"]}' -H 'Accept: application/json' -H 'Content-type: application/json' http://localhost:8081/marketing
300{"subject":"Hey Alp Mestanogullari, we miss you!","body":"Hi Alp Mestanogullari,\n\nSince you've recently turned 25, have you checked out our latest haskell, mathematics products? Give us a visit!","to":"alp@foo.com","from":"great@company.com"}
303For reference, here's a list of some combinators from *servant* and for those
304that get turned into arguments to the handlers, the type of the argument.
306 > - `Delete`, `Get`, `Patch`, `Post`, `Put`: these do not become arguments. They provide the return type of handlers, which usually is `EitherT ServantErr IO <something>`.
307 > - `Capture "something" a` becomes an argument of type `a`.
308 > - `QueryParam "something" a`, `MatrixParam "something" a`, `Header "something" a` all become arguments of type `Maybe a`, because there might be no value at all specified by the client for these.
309 > - `QueryFlag "something"` and `MatrixFlag "something"` get turned into arguments of type `Bool`.
310 > - `QueryParams "something" a` and `MatrixParams "something" a` get turned into arguments of type `[a]`.
311 > - `ReqBody contentTypes a` gets turned into an argument of type `a`.
313The `FromText`/`ToText` classes
314===============================
316Wait... How does *servant* know how to decode the `Int`s from the URL? Or how
317to decode a `ClientInfo` value from the request body? This is what this and the
318following two sections address.
320`Capture`s and `QueryParam`s are represented by some textual value in URLs.
321`Header`s are similarly represented by a pair of a header name and a
322corresponding (textual) value in the request's "metadata". This is why we
323decided to provide a pair of typeclasses, `FromText` and `ToText` which just
324let you say that you can respectively *extract* or *encode* values of some type
325*from*/*to* text. Here are the definitions:
328class FromText a where
329 fromText :: Text -> Maybe a
335And as long as the type that a `Capture`/`QueryParam`/`Header`/etc will be
336decoded to provides a `FromText` instance, it will Just Work. *servant*
337provides a decent number of instances, but here are some examples of defining
340> -- A typical enumeration
347> instance FromText Direction where
348> -- requires {-# LANGUAGE OverloadedStrings #-}
349> fromText "up" = Just Up
350> fromText "down" = Just Down
351> fromText "left" = Just Server.Left
352> fromText "right" = Just Server.Right
353> fromText _ = Nothing
355> instance ToText Direction where
357> toText Down = "down"
358> toText Server.Left = "left"
359> toText Server.Right = "right"
361> newtype UserId = UserId Int64
362> deriving (FromText, ToText)
364or writing the instances by hand:
367instance FromText UserId where
368 fromText = fmap UserId fromText
370instance ToText UserId where
371 toText (UserId i) = toText i
374There's not much else to say about these classes. You will need instances for
375them when using `Capture`, `QueryParam`, `QueryParams`, `MatrixParam`,
376`MatrixParams` and `Header` with your types. You will need `FromText` instances
377for server-side request handlers and `ToText` instances only when using
378*servant-client*, as described in the [section about deriving haskell
379functions to query an API](/tutorial/client.html).
381Using content-types with your data types
382========================================
384The same principle was operating when decoding request bodies from JSON, and
385responses *into* JSON. (JSON is just the running example - you can do this with
388This section introduces a couple of typeclasses provided by *servant* that make
391The truth behind `JSON`
392-----------------------
394What exactly is `JSON`? Like the 3 other content types provided out of the box
395by *servant*, it's a really dumb data type.
404Obviously, this is not all there is to `JSON`, otherwise it would be quite
405pointless. Like most of the data types in *servant*, `JSON` is mostly there as
406a special *symbol* that's associated with encoding (resp. decoding) to (resp.
407from) the *JSON* format. The way this association is performed can be
408decomposed into two steps.
410The first step is to provide a proper
411[`MediaType`](https://hackage.haskell.org/package/http-media-0.6.2/docs/Network-HTTP-Media.html)
412representation for `JSON`, or for your own content types. If you look at the
413haddocks from this link, you can see that we just have to specify
414`application/json` using the appropriate functions. In our case, we can just
415use `(//) :: ByteString -> ByteString -> MediaType`. The precise way to specify
416the `MediaType` is to write an instance for the `Accept` class:
420class Accept ctype where
421 contentType :: Proxy ctype -> MediaType
423instance Accept JSON where
424 contentType _ = "application" // "json"
427The second step is centered around the `MimeRender` and `MimeUnrender` classes.
428These classes just let you specify a way to respectively encode and decode
429values respectively into or from your content-type's representation.
432class Accept ctype => MimeRender ctype a where
433 mimeRender :: Proxy ctype -> a -> ByteString
434 -- alternatively readable as:
435 mimeRender :: Proxy ctype -> (a -> ByteString)
438Given a content-type and some user type, `MimeRender` provides a function that
439encodes values of type `a` to lazy `ByteString`s.
441In the case of `JSON`, this is easily dealt with! For any type `a` with a
442`ToJSON` instance, we can render values of that type to JSON using
446instance ToJSON a => MimeRender JSON a where
447 mimeRender _ = encode
450And now the `MimeUnrender` class, which lets us extract values from lazy
451`ByteString`s, alternatively failing with an error string.
454class Accept ctype => MimeUnrender ctype a where
455 mimeUnrender :: Proxy ctype -> ByteString -> Either String a
457 mimeUnrender :: Proxy ctype -> (ByteString -> Either String a)
460We don't have much work to do there either, `Data.Aeson.eitherDecode` is
461precisely what we need. However, it only allows arrays and objects as toplevel
462JSON values and this has proven to get in our way more than help us so we wrote
463our own little function around *aeson* and *attoparsec* that allows any type of
464JSON value at the toplevel of a "JSON document". Here's the definition in case
467> eitherDecodeLenient :: FromJSON a => ByteString -> Either String a
468> eitherDecodeLenient input = do
469> v :: Value <- parseOnly (Data.Aeson.Parser.value <* endOfInput) (cs input)
470> parseEither parseJSON v
472This function is exactly what we need for our `MimeUnrender` instance.
475instance FromJSON a => MimeUnrender JSON a where
476 mimeUnrender _ = eitherDecodeLenient
479And this is all the code that lets you use `JSON` for with `ReqBody`, `Get`,
480`Post` and friends. We can check our understanding by implementing support
481for an `HTML` content type, so that users of your webservice can access an
482HTML representation of the data they want, ready to be included in any HTML
483document, e.g. using [jQuery's `load` function](https://api.jquery.com/load/), simply by adding `Accept:
484text/html` to their request headers.
486Case-studies: *servant-blaze* and *servant-lucid*
487-------------------------------------------------
489These days, most of the haskellers who write their HTML UIs directly from
490Haskell use either [blaze-html](http://hackage.haskell.org/package/blaze-html)
491or [lucid](http://hackage.haskell.org/package/lucid). The best option for
492*servant* is obviously to support both (and hopefully other templating
497Once again, the data type is just there as a symbol for the encoding/decoding
498functions, except that this time we will only worry about encoding since
499*blaze-html* and *lucid* don't provide a way to extract data from HTML.
501Both packages also have the same `Accept` instance for their `HTMLLucid` type.
503> instance Accept HTMLLucid where
504> contentType _ = "text" // "html" /: ("charset", "utf-8")
506Note that this instance uses the `(/:)` operator from *http-media* which lets
507us specify additional information about a content-type, like the charset here.
509The rendering instances for both packages both call similar functions that take
510types with an appropriate instance to an "abstract" HTML representation and
511then write that to a `ByteString`.
515> instance ToHtml a => MimeRender HTMLLucid a where
516> mimeRender _ = renderBS . toHtml
518> -- let's also provide an instance for lucid's
520> instance MimeRender HTMLLucid (Html a) where
521> mimeRender _ = renderBS
525> -- For this tutorial to compile 'HTMLLucid' and 'HTMLBlaze' have to be
526> -- distinct. Usually you would stick to one html rendering library and then
527> -- you can go with one 'HTML' type.
530> instance Accept HTMLBlaze where
531> contentType _ = "text" // "html" /: ("charset", "utf-8")
533> instance ToMarkup a => MimeRender HTMLBlaze a where
534> mimeRender _ = renderHtml . Text.Blaze.Html.toHtml
536> -- while we're at it, just like for lucid we can
537> -- provide an instance for rendering blaze's 'Html' type
538> instance MimeRender HTMLBlaze Text.Blaze.Html.Html where
539> mimeRender _ = renderHtml
541Both [servant-blaze](http://hackage.haskell.org/package/servant-blaze) and
542[servant-lucid](http://hackage.haskell.org/package/servant-lucid) let you use
543`HTMLLucid` in any content type list as long as you provide an instance of the
544appropriate class (`ToMarkup` for *blaze-html*, `ToHtml` for *lucid*).
546We can now write webservice that uses *servant-lucid* to show the `HTMLLucid`
547content type in action. First off, imports and pragmas as usual.
549We will be serving the following API:
551> type PersonAPI = "persons" :> Get '[JSON, HTMLLucid] [Person]
553where `Person` is defined as follows:
555> data Person = Person
556> { firstName :: String
557> , lastName :: String
558> } deriving Generic -- for the JSON instance
560> instance ToJSON Person
562Now, let's teach *lucid* how to render a `Person` as a row in a table, and then
563a list of `Person`s as a table with a row per person.
565> -- HTML serialization of a single person
566> instance ToHtml Person where
569> td_ (toHtml $ firstName person)
570> td_ (toHtml $ lastName person)
572> -- do not worry too much about this
575> -- HTML serialization of a list of persons
576> instance ToHtml [Person] where
577> toHtml persons = table_ $ do
582> -- this just calls toHtml on each person of the list
583> -- and concatenates the resulting pieces of HTML together
584> foldMap toHtml persons
588We create some `Person` values and serve them as a list:
592> [ Person "Isaac" "Newton"
593> , Person "Albert" "Einstein"
596> personAPI :: Proxy PersonAPI
599> server4 :: Server PersonAPI
600> server4 = return persons
603> app2 = serve personAPI server4
605And we're good to go. You can run this example with `dist/build/tutorial/tutorial 4`.
608 $ curl http://localhost:8081/persons
609 [{"lastName":"Newton","firstName":"Isaac"},{"lastName":"Einstein","firstName":"Albert"}]
610 $ curl -H 'Accept: text/html' http://localhost:8081/persons
611 <table><tr><td>first name</td><td>last name</td></tr><tr><td>Isaac</td><td>Newton</td></tr><tr><td>Albert</td><td>Einstein</td></tr></table>
612 # or just point your browser to http://localhost:8081/persons
615The `EitherT ServantErr IO` monad
616=================================
618At the heart of the handlers is the monad they run in, namely `EitherT
619ServantErr IO`. One might wonder: why this monad? The answer is that it is the
620simplest monad with the following properties:
622- it lets us both return a successful result (with the `Right` branch of
623`Either`) or "fail" with a descriptive error (with the `Left` branch of
625- it lets us perform IO, which is absolutely vital since most webservices exist
626as interfaces to databases that we interact with in `IO`;
628Let's recall some definitions.
632data Either e a = Left e | Right a
634-- from the 'either' package at
635-- http://hackage.haskell.org/package/either-4.3.3.2/docs/Control-Monad-Trans-Either.html
637 = EitherT { runEitherT :: m (Either e a) }
640In short, this means that a handler of type `EitherT ServantErr IO a` is simply
641equivalent to a computation of type `IO (Either ServantErr a)`, that is, an IO
642action that either returns an error or a result.
644The aforementioned `either` package is worth taking a look at. Perhaps most
648left :: Monad m => e -> EitherT e m a
650Allows you to return an error from your handler (whereas `return` is enough to
653Most of what you'll be doing in your handlers is running some IO and,
654depending on the result, you might sometimes want to throw an error of some
655kind and abort early. The next two sections cover how to do just that.
660Another important instance from the list above is `MonadIO m => MonadIO (EitherT e m)`. [`MonadIO`](http://hackage.haskell.org/package/transformers-0.4.3.0/docs/Control-Monad-IO-Class.html) is a class from the *transformers* package defined as:
663class Monad m => MonadIO m where
664 liftIO :: IO a -> m a
667Obviously, the `IO` monad provides a `MonadIO` instance. Hence for any type `e`, `EitherT e IO` has a `MonadIO` instance. So if you want to run any kind of IO computation in your handlers, just use `liftIO`:
669> type IOAPI1 = "myfile.txt" :> Get '[JSON] FileContent
671> newtype FileContent = FileContent
672> { content :: String }
675> instance ToJSON FileContent
677> server5 :: Server IOAPI1
679> filecontent <- liftIO (readFile "myfile.txt")
680> return (FileContent filecontent)
682Failing, through `ServantErr`
683-----------------------------
685If you want to explicitly fail at providing the result promised by an endpoint
686using the appropriate HTTP status code (not found, unauthorized, etc) and some
687error message, all you have to do is use the `left` function mentioned above
688and provide it with the appropriate value of type `ServantErr`, which is
692data ServantErr = ServantErr
694 , errReasonPhrase :: String
695 , errBody :: ByteString -- lazy bytestring
696 , errHeaders :: [Header]
700Many standard values are provided out of the box by the `Servant.Server`
701module. If you want to use these values but add a body or some headers, just
702use record update syntax:
704> failingHandler :: EitherT ServantErr IO ()
705> failingHandler = left myerr
707> where myerr :: ServantErr
708> myerr = err503 { errBody = "Sorry dear user." }
710Here's an example where we return a customised 404-Not-Found error message in
711the response body if "myfile.txt" isn't there:
713> server6 :: Server IOAPI1
715> exists <- liftIO (doesFileExist "myfile.txt")
717> then liftIO (readFile "myfile.txt") >>= return . FileContent
718> else left custom404Err
720> where custom404Err = err404 { errBody = "myfile.txt just isn't there, please leave this server alone." }
722Let's run this server (`dist/build/tutorial/tutorial 5`) and
723query it, first without the file and then with the file.
726 $ curl --verbose http://localhost:8081/myfile.txt
728 * Connected to localhost (127.0.0.1) port 8081 (#0)
729 > GET /myfile.txt HTTP/1.1
730 > User-Agent: curl/7.30.0
731 > Host: localhost:8081
734 < HTTP/1.1 404 Not Found
736 myfile.txt just isnt there, please leave this server alone.
738 $ echo Hello > myfile.txt
740 $ curl --verbose http://localhost:8081/myfile.txt
742 * Connected to localhost (127.0.0.1) port 8081 (#0)
743 > GET /myfile.txt HTTP/1.1
744 > User-Agent: curl/7.30.0
745 > Host: localhost:8081
750 < Content-Type: application/json
752 {"content":"Hello\n"}
758To add headers to your response, use [addHeader](http://hackage.haskell.org/package/servant-0.4.4/docs/Servant-API-ResponseHeaders.html).
759Note that this changes the type of your API, as we can see in the following example:
761> type MyHandler = Get '[JSON] (Headers '[Header "X-An-Int" Int] User)
763> myHandler :: Server MyHandler
764> myHandler = return $ addHeader 1797 albert
770*servant-server* also provides a way to just serve the content of a directory
771under some path in your web API. As mentioned earlier in this document, the
772`Raw` combinator can be used in your APIs to mean "plug here any WAI
773application". Well, servant-server provides a function to get a file and
774directory serving WAI application, namely:
777-- exported by Servant and Servant.Server
778serveDirectory :: FilePath -> Server Raw
781`serveDirectory`'s argument must be a path to a valid directory. You can see an
782example below, runnable with `dist/build/tutorial/tutorial 6`
783(you **must** run it from within the *servant-examples/* directory!), which is
784a webserver that serves the various bits of code covered in this
787The API type will be the following.
789> type CodeAPI = "code" :> Raw
793> codeAPI :: Proxy CodeAPI
796> server7 :: Server CodeAPI
797> server7 = serveDirectory "tutorial"
800> app3 = serve codeAPI server7
802This server will match any request whose path starts with `/code` and will look for a file at the path described by the rest of the request path, inside the *tutorial/* directory of the path you run the program from.
806- If a client requests `/code/foo.txt`, the server will look for a file at `./tutorial/foo.txt` (and fail)
807- If a client requests `/code/T1.hs`, the server will look for a file at `./tutorial/T1.hs` (and succeed)
808- If a client requests `/code/foo/bar/baz/movie.mp4`, the server will look for a file at `./tutorial/foo/bar/baz/movie.mp4` (and fail)
810Here is our little server in action.
813$ curl http://localhost:8081/code/T1.hs
814{-# LANGUAGE DataKinds #-}
815{-# LANGUAGE TypeFamilies #-}
816{-# LANGUAGE DeriveGeneric #-}
817{-# LANGUAGE TypeOperators #-}
821import Data.Time.Calendar
830 , registration_date :: Day
831 } deriving (Eq, Show, Generic)
833-- orphan ToJSON instance for Day. necessary to derive one for User
834instance ToJSON Day where
835 -- display a day in YYYY-mm-dd format
836 toJSON d = toJSON (showGregorian d)
840type UserAPI = "users" :> Get '[JSON] [User]
844 [ User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
845 , User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
848userAPI :: Proxy UserAPI
851server :: Server UserAPI
855app = serve userAPI server
856$ curl http://localhost:8081/code/tutorial.hs
858import Network.Wai.Handler.Warp
859import System.Environment
871app :: String -> (Application -> IO ()) -> IO ()
881 "9" -> T9.writeJSFiles >> f T9.app
889 [n] -> app n (run 8081)
894 putStrLn "Usage:\t tutorial N"
895 putStrLn "\t\twhere N is the number of the example you want to run."
897$ curl http://localhost:8081/foo
904Let's see how you can define APIs in a modular way, while avoiding repetition. Consider this simple example:
906> type UserAPI3 = -- view the user with given userid, in JSON
907> Capture "userid" Int :> Get '[JSON] User
909> :<|> -- delete the user with given userid. empty response
910> Capture "userid" Int :> Delete '[] ()
912We can instead factor out the `userid`:
914> type UserAPI4 = Capture "userid" Int :>
919However, you have to be aware that this has an effect on the type of the corresponding `Server`:
922Server UserAPI3 = (Int -> EitherT ServantErr IO User)
923 :<|> (Int -> EitherT ServantErr IO ())
925Server UserAPI4 = Int -> ( EitherT ServantErr IO User
926 :<|> EitherT ServantErr IO ()
930In the first case, each handler receives the *userid* argument. In the latter,
931the whole `Server` takes the *userid* and has handlers that are just computations in `EitherT`, with no arguments. In other words:
933> server8 :: Server UserAPI3
934> server8 = getUser :<|> deleteUser
936> where getUser :: Int -> EitherT ServantErr IO User
937> getUser _userid = error "..."
939> deleteUser :: Int -> EitherT ServantErr IO ()
940> deleteUser _userid = error "..."
942> -- notice how getUser and deleteUser
943> -- have a different type! no argument anymore,
944> -- the argument directly goes to the whole Server
945> server9 :: Server UserAPI4
946> server9 userid = getUser userid :<|> deleteUser userid
948> where getUser :: Int -> EitherT ServantErr IO User
949> getUser = error "..."
951> deleteUser :: Int -> EitherT ServantErr IO ()
952> deleteUser = error "..."
954Note that there's nothing special about `Capture` that lets you "factor it out": this can be done with any combinator. Here are a few examples of APIs with a combinator factored out for which we can write a perfectly valid `Server`.
956> -- we just factor out the "users" path fragment
957> type API1 = "users" :>
958> ( Get '[JSON] [User] -- user listing
959> :<|> Capture "userid" Int :> Get '[JSON] User -- view a particular user
962> -- we factor out the Request Body
963> type API2 = ReqBody '[JSON] User :>
964> ( Get '[JSON] User -- just display the same user back, don't register it
965> :<|> Post '[JSON] () -- register the user. empty response
968> -- we factor out a Header
969> type API3 = Header "Authorization" Token :>
970> ( Get '[JSON] SecretData -- get some secret data, if authorized
971> :<|> ReqBody '[JSON] SecretData :> Post '[] () -- add some secret data, if authorized
974> newtype Token = Token ByteString
975> newtype SecretData = SecretData ByteString
977This approach lets you define APIs modularly and assemble them all into one big API type only at the end.
980> Get '[JSON] [User] -- list users
981> :<|> ReqBody '[JSON] User :> Post '[] () -- add a user
982> :<|> Capture "userid" Int :>
983> ( Get '[JSON] User -- view a user
984> :<|> ReqBody '[JSON] User :> Put '[] () -- update a user
985> :<|> Delete '[] () -- delete a user
988> usersServer :: Server UsersAPI
989> usersServer = getUsers :<|> newUser :<|> userOperations
991> where getUsers :: EitherT ServantErr IO [User]
992> getUsers = error "..."
994> newUser :: User -> EitherT ServantErr IO ()
995> newUser = error "..."
997> userOperations userid =
998> viewUser userid :<|> updateUser userid :<|> deleteUser userid
1001> viewUser :: Int -> EitherT ServantErr IO User
1002> viewUser = error "..."
1004> updateUser :: Int -> User -> EitherT ServantErr IO ()
1005> updateUser = error "..."
1007> deleteUser :: Int -> EitherT ServantErr IO ()
1008> deleteUser = error "..."
1011> Get '[JSON] [Product] -- list products
1012> :<|> ReqBody '[JSON] Product :> Post '[] () -- add a product
1013> :<|> Capture "productid" Int :>
1014> ( Get '[JSON] Product -- view a product
1015> :<|> ReqBody '[JSON] Product :> Put '[] () -- update a product
1016> :<|> Delete '[] () -- delete a product
1019> data Product = Product { productId :: Int }
1021> productsServer :: Server ProductsAPI
1022> productsServer = getProducts :<|> newProduct :<|> productOperations
1024> where getProducts :: EitherT ServantErr IO [Product]
1025> getProducts = error "..."
1027> newProduct :: Product -> EitherT ServantErr IO ()
1028> newProduct = error "..."
1030> productOperations productid =
1031> viewProduct productid :<|> updateProduct productid :<|> deleteProduct productid
1034> viewProduct :: Int -> EitherT ServantErr IO Product
1035> viewProduct = error "..."
1037> updateProduct :: Int -> Product -> EitherT ServantErr IO ()
1038> updateProduct = error "..."
1040> deleteProduct :: Int -> EitherT ServantErr IO ()
1041> deleteProduct = error "..."
1043> type CombinedAPI = "users" :> UsersAPI
1044> :<|> "products" :> ProductsAPI
1046> server10 :: Server CombinedAPI
1047> server10 = usersServer :<|> productsServer
1049Finally, we can realize the user and product APIs are quite similar and abstract that away:
1051> -- API for values of type 'a'
1052> -- indexed by values of type 'i'
1054> Get '[JSON] [a] -- list 'a's
1055> :<|> ReqBody '[JSON] a :> Post '[] () -- add an 'a'
1056> :<|> Capture "id" i :>
1057> ( Get '[JSON] a -- view an 'a' given its "identifier" of type 'i'
1058> :<|> ReqBody '[JSON] a :> Put '[] () -- update an 'a'
1059> :<|> Delete '[] () -- delete an 'a'
1062> -- Build the appropriate 'Server'
1063> -- given the handlers of the right type.
1064> serverFor :: EitherT ServantErr IO [a] -- handler for listing of 'a's
1065> -> (a -> EitherT ServantErr IO ()) -- handler for adding an 'a'
1066> -> (i -> EitherT ServantErr IO a) -- handler for viewing an 'a' given its identifier of type 'i'
1067> -> (i -> a -> EitherT ServantErr IO ()) -- updating an 'a' with given id
1068> -> (i -> EitherT ServantErr IO ()) -- deleting an 'a' given its id
1069> -> Server (APIFor a i)
1070> serverFor = error "..."
1071> -- implementation left as an exercise. contact us on IRC
1072> -- or the mailing list if you get stuck!
1074Using another monad for your handlers
1075=====================================
1077Remember how `Server` turns combinators for HTTP methods into `EitherT ServantErr IO`? Well, actually, there's more to that. `Server` is actually a simple type synonym.
1080type Server api = ServerT api (EitherT ServantErr IO)
1083`ServerT` is the actual type family that computes the required types for the handlers that's part of the `HasServer` class. It's like `Server` except that it takes a third parameter which is the monad you want your handlers to run in, or more generally the return types of your handlers. This third parameter is used for specifying the return type of the handler for an endpoint, e.g when computing `ServerT (Get '[JSON] Person) SomeMonad`. The result would be `SomeMonad Person`.
1085The first and main question one might have then is: how do we write handlers that run in another monad? How can we "bring back" the value from a given monad into something *servant* can understand?
1087Natural transformations
1088-----------------------
1090If we have a function that gets us from an `m a` to an `n a`, for any `a`, what
1094newtype m :~> n = Nat { unNat :: forall a. m a -> n a}
1097-- listToMaybeNat ::`[] :~> Maybe`
1098-- listToMaybeNat = Nat listToMaybe -- from Data.Maybe
1100(`Nat` comes from "natural transformation", in case you're wondering.)
1102So if you want to write handlers using another monad/type than `EitherT
1103ServantErr IO`, say the `Reader String` monad, the first thing you have to
1104prepare is a function:
1107readerToEither :: Reader String :~> EitherT ServantErr IO
1110Let's start with `readerToEither'`. We obviously have to run the `Reader`
1111computation by supplying it with a `String`, like `"hi"`. We get an `a` out
1112from that and can then just `return` it into `EitherT`. We can then just wrap
1113that function with the `Nat` constructor to make it have the fancier type.
1115> readerToEither' :: forall a. Reader String a -> EitherT ServantErr IO a
1116> readerToEither' r = return (runReader r "hi")
1118> readerToEither :: Reader String :~> EitherT ServantErr IO
1119> readerToEither = Nat readerToEither'
1121We can write some simple webservice with the handlers running in `Reader String`.
1123> type ReaderAPI = "a" :> Get '[JSON] Int
1124> :<|> "b" :> Get '[JSON] String
1126> readerAPI :: Proxy ReaderAPI
1129> readerServerT :: ServerT ReaderAPI (Reader String)
1130> readerServerT = a :<|> b
1132> where a :: Reader String Int
1135> b :: Reader String String
1138We unfortunately can't use `readerServerT` as an argument of `serve`, because
1139`serve` wants a `Server ReaderAPI`, i.e., with handlers running in `EitherT
1140ServantErr IO`. But there's a simple solution to this.
1145That's right. We have just written `readerToEither`, which is exactly what we
1146would need to apply to the results of all handlers to make the handlers have the
1147right type for `serve`. Being cumbersome to do by hand, we provide a function
1148`enter` which takes a natural transformation between two parametrized types `m`
1149and `n` and a `ServerT someapi m`, and returns a `ServerT someapi n`.
1151In our case, we can wrap up our little webservice by using `enter readerToEither` on our handlers.
1153> readerServer :: Server ReaderAPI
1154> readerServer = enter readerToEither readerServerT
1156> app4 :: Application
1157> app4 = serve readerAPI readerServer
1159And we can indeed see this webservice in action by running `dist/build/tutorial/tutorial 7`.
1162$ curl http://localhost:8081/a
1164$ curl http://localhost:8081/b
1171You're now equipped to write any kind of webservice/web-application using *servant*. One thing not covered here is how to incorporate your own combinators and will be the topic of a page on the website. The rest of this document focuses on *servant-client*, *servant-jquery* and *servant-docs*.
1173<div style="text-align: center;">
1174 <p><a href="/tutorial/api-type.html">Previous page: A web API as a type</a></p>
1175 <p><a href="/tutorial/client.html">Next page: Deriving Haskell functions to query an API</a></p>
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