3Enough chit-chat about type-level combinators and representing an API as a
4type. Can we have a webservice already?
8Equipped with some basic knowledge about the way we represent APIs, let's now
9write our first webservice.
11The source for this tutorial section is a literate haskell file, so first we
12need to have some language extensions and imports:
15{-# LANGUAGE DataKinds #-}
16{-# LANGUAGE DeriveGeneric #-}
17{-# LANGUAGE FlexibleInstances #-}
18{-# LANGUAGE GeneralizedNewtypeDeriving #-}
19{-# LANGUAGE MultiParamTypeClasses #-}
20{-# LANGUAGE OverloadedStrings #-}
21{-# LANGUAGE RankNTypes #-}
22{-# LANGUAGE ScopedTypeVariables #-}
23{-# LANGUAGE TypeOperators #-}
30import Control.Monad.Except
31import Control.Monad.Reader
32import Data.Aeson.Compat
33import Data.Aeson.Types
34import Data.Attoparsec.ByteString
35import Data.ByteString (ByteString)
38import Data.String.Conversions
39import Data.Time.Calendar
42import Network.HTTP.Media ((//), (/:))
44import Network.Wai.Handler.Warp
46import System.Directory
48import Text.Blaze.Html.Renderer.Utf8
49import qualified Data.Aeson.Parser
50import qualified Text.Blaze.Html
53**Important**: the `Servant` module comes from the **servant-server** package,
54the one that lets us run webservers that implement a particular API type. It
55reexports all the types from the **servant** package that let you declare API
56types as well as everything you need to turn your request handlers into a
57fully-fledged webserver. This means that in your applications, you can just add
58**servant-server** as a dependency, import `Servant` and not worry about anything
61We will write a server that will serve the following API.
64type UserAPI1 = "users" :> Get '[JSON] [User]
67Here's what we would like to see when making a GET request to `/users`.
70[ {"name": "Isaac Newton", "age": 372, "email": "isaac@newton.co.uk", "registration_date": "1683-03-01"}
71, {"name": "Albert Einstein", "age": 136, "email": "ae@mc2.org", "registration_date": "1905-12-01"}
75Now let's define our `User` data type and write some instances for it.
82 , registration_date :: Day
83 } deriving (Eq, Show, Generic)
88Nothing funny going on here. But we now can define our list of two users.
93 [ User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
94 , User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
98We can now take care of writing the actual webservice that will handle requests
99to such an API. This one will be very simple, being reduced to just a single
100endpoint. The type of the web application is determined by the API type,
101through a *type family* named `Server`. (Type families are just functions that
102take types as input and return types.) The `Server` type family will compute
103the right type that a bunch of request handlers should have just from the
104corresponding API type.
106The first thing to know about the `Server` type family is that behind the
107scenes it will drive the routing, letting you focus only on the business
108logic. The second thing to know is that for each endpoint, your handlers will
109by default run in the `Handler` monad. This is overridable very
110easily, as explained near the end of this guide. Third thing, the type of the
111value returned in that monad must be the same as the second argument of the
112HTTP method combinator used for the corresponding endpoint. In our case, it
113means we must provide a handler of type `Handler [User]`. Well,
114we have a monad, let's just `return` our list:
117server1 :: Server UserAPI1
118server1 = return users1
121That's it. Now we can turn `server` into an actual webserver using
122[wai](http://hackage.haskell.org/package/wai) and
123[warp](http://hackage.haskell.org/package/warp):
126userAPI :: Proxy UserAPI1
129-- 'serve' comes from servant and hands you a WAI Application,
130-- which you can think of as an "abstract" web application,
131-- not yet a webserver.
133app1 = serve userAPI server1
136The `userAPI` bit is, alas, boilerplate (we need it to guide type inference).
137But that's about as much boilerplate as you get.
139And we're done! Let's run our webservice on the port 8081.
146You can put this all into a file or just grab [servant's
147repo](http://github.com/haskell-servant/servant) and look at the
148*doc/tutorial* directory. This code (the source of this web page) is in
149*doc/tutorial/Server.lhs*.
151If you run it, you can go to `http://localhost:8081/users` in your browser or
152query it with curl and you see:
155$ curl http://localhost:8081/users
156[{"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"}]
161What if we want more than one endpoint? Let's add `/albert` and `/isaac` to
162view the corresponding users encoded in JSON.
165type UserAPI2 = "users" :> Get '[JSON] [User]
166 :<|> "albert" :> Get '[JSON] User
167 :<|> "isaac" :> Get '[JSON] User
170And let's adapt our code a bit.
174isaac = User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
177albert = User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
180users2 = [isaac, albert]
183Now, just like we separate the various endpoints in `UserAPI` with `:<|>`, we
184are going to separate the handlers with `:<|>` too! They must be provided in
185the same order as in in the API type.
188server2 :: Server UserAPI2
189server2 = return users2
194And that's it! You can run this example in the same way that we showed for
195`server1` and check out the data available at `/users`, `/albert` and `/isaac`.
197## From combinators to handler arguments
199Fine, we can write trivial webservices easily, but none of the two above use
200any "fancy" combinator from servant. Let's address this and use `QueryParam`,
201`Capture` and `ReqBody` right away. You'll see how each occurrence of these
202combinators in an endpoint makes the corresponding handler receive an
203argument of the appropriate type automatically. You don't have to worry about
204manually looking up URL captures or query string parameters, or
205decoding/encoding data from/to JSON. Never.
207We are going to use the following data types and functions to implement a
211type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
212 :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
213 :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
215data Position = Position
220instance ToJSON Position
222newtype HelloMessage = HelloMessage { msg :: String }
225instance ToJSON HelloMessage
227data ClientInfo = ClientInfo
228 { clientName :: String
229 , clientEmail :: String
231 , clientInterestedIn :: [String]
234instance FromJSON ClientInfo
235instance ToJSON ClientInfo
246emailForClient :: ClientInfo -> Email
247emailForClient c = Email from' to' subject' body'
249 where from' = "great@company.com"
251 subject' = "Hey " ++ clientName c ++ ", we miss you!"
252 body' = "Hi " ++ clientName c ++ ",\n\n"
253 ++ "Since you've recently turned " ++ show (clientAge c)
254 ++ ", have you checked out our latest "
255 ++ intercalate ", " (clientInterestedIn c)
256 ++ " products? Give us a visit!"
259We can implement handlers for the three endpoints:
267 where position :: Int -> Int -> Handler Position
268 position x y = return (Position x y)
270 hello :: Maybe String -> Handler HelloMessage
271 hello mname = return . HelloMessage $ case mname of
272 Nothing -> "Hello, anonymous coward"
273 Just n -> "Hello, " ++ n
275 marketing :: ClientInfo -> Handler Email
276 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. Here's the example in action:
293$ curl http://localhost:8081/position/1/2
294{"xCoord":1,"yCoord":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 '{"clientName":"Alp Mestanogullari", "clientEmail" : "alp@foo.com", "clientAge": 25, "clientInterestedIn": ["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**:
305 > - `Delete`, `Get`, `Patch`, `Post`, `Put`: these do not become arguments. They provide the return type of handlers, which usually is `Handler <something>`.
306 > - `Capture "something" a` becomes an argument of type `a`.
307 > - `QueryParam "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.
308 > - `QueryFlag "something"` gets turned into an argument of type `Bool`.
309 > - `QueryParams "something" a` gets turned into an argument of type `[a]`.
310 > - `ReqBody contentTypes a` gets turned into an argument of type `a`.
312## The `FromHttpApiData`/`ToHttpApiData` classes
314Wait... How does **servant** know how to decode the `Int`s from the URL? Or how
315to decode a `ClientInfo` value from the request body? This is what this and the
316following two sections address.
318`Capture`s and `QueryParam`s are represented by some textual value in URLs.
319`Header`s are similarly represented by a pair of a header name and a
320corresponding (textual) value in the request's "metadata". How types are
321decoded from headers, captures, and query params is expressed in a class
322`FromHttpApiData` (from the package
323[**http-api-data**](http://hackage.haskell.org/package/http-api-data)):
326class FromHttpApiData a where
327 {-# MINIMAL parseUrlPiece | parseQueryParam #-}
328 -- | Parse URL path piece.
329 parseUrlPiece :: Text -> Either Text a
330 parseUrlPiece = parseQueryParam
332 -- | Parse HTTP header value.
333 parseHeader :: ByteString -> Either Text a
334 parseHeader = parseUrlPiece . decodeUtf8
336 -- | Parse query param value.
337 parseQueryParam :: Text -> Either Text a
338 parseQueryParam = parseUrlPiece
341As you can see, as long as you provide either `parseUrlPiece` (for `Capture`s)
342or `parseQueryParam` (for `QueryParam`s), the other methods will be defined in
345**http-api-data** provides a decent number of instances, helpers for defining new
346ones, and wonderful documentation.
348There's not much else to say about these classes. You will need instances for
349them when using `Capture`, `QueryParam`, `QueryParams`, and `Header` with your
350types. You will need `FromHttpApiData` instances for server-side request
351handlers and `ToHttpApiData` instances only when using
352**servant-client**, as described in the [section about deriving haskell
353functions to query an API](Client.html).
355## Using content-types with your data types
357The same principle was operating when decoding request bodies from JSON, and
358responses *into* JSON. (JSON is just the running example - you can do this with
361This section introduces a couple of typeclasses provided by **servant** that make
364### The truth behind `JSON`
367What exactly is `JSON` (the type as used in `Get '[JSON] User`)? Like the 3
368other content-types provided out of the box by **servant**, it's a really dumb
378Obviously, this is not all there is to `JSON`, otherwise it would be quite
379pointless. Like most of the data types in **servant**, `JSON` is mostly there as
380a special *symbol* that's associated with encoding (resp. decoding) to (resp.
381from) the *JSON* format. The way this association is performed can be
382decomposed into two steps.
384The first step is to provide a proper
386[**http-media**](https://hackage.haskell.org/package/http-media-0.6.2/docs/Network-HTTP-Media.html))
387representation for `JSON`, or for your own content-types. If you look at the
388haddocks from this link, you can see that we just have to specify
389`application/json` using the appropriate functions. In our case, we can just
390use `(//) :: ByteString -> ByteString -> MediaType`. The precise way to specify
391the `MediaType` is to write an instance for the `Accept` class:
395class Accept ctype where
396 contentType :: Proxy ctype -> MediaType
398instance Accept JSON where
399 contentType _ = "application" // "json"
402The second step is centered around the `MimeRender` and `MimeUnrender` classes.
403These classes just let you specify a way to encode and decode
404values into or from your content-type's representation.
407class Accept ctype => MimeRender ctype a where
408 mimeRender :: Proxy ctype -> a -> ByteString
409 -- alternatively readable as:
410 mimeRender :: Proxy ctype -> (a -> ByteString)
413Given a content-type and some user type, `MimeRender` provides a function that
414encodes values of type `a` to lazy `ByteString`s.
416In the case of `JSON`, this is easily dealt with! For any type `a` with a
417`ToJSON` instance, we can render values of that type to JSON using
421instance ToJSON a => MimeRender JSON a where
422 mimeRender _ = encode
425And now the `MimeUnrender` class, which lets us extract values from lazy
426`ByteString`s, alternatively failing with an error string.
429class Accept ctype => MimeUnrender ctype a where
430 mimeUnrender :: Proxy ctype -> ByteString -> Either String a
433We don't have much work to do there either, `Data.Aeson.eitherDecode` is
434precisely what we need. However, it only allows arrays and objects as toplevel
435JSON values and this has proven to get in our way more than help us so we wrote
436our own little function around **aeson** and **attoparsec** that allows any type of
437JSON value at the toplevel of a "JSON document". Here's the definition in case
441eitherDecodeLenient :: FromJSON a => ByteString -> Either String a
442eitherDecodeLenient input = do
443 v :: Value <- parseOnly (Data.Aeson.Parser.value <* endOfInput) (cs input)
444 parseEither parseJSON v
447This function is exactly what we need for our `MimeUnrender` instance.
450instance FromJSON a => MimeUnrender JSON a where
451 mimeUnrender _ = eitherDecodeLenient
454And this is all the code that lets you use `JSON` with `ReqBody`, `Get`,
455`Post` and friends. We can check our understanding by implementing support
456for an `HTML` content-type, so that users of your webservice can access an
457HTML representation of the data they want, ready to be included in any HTML
458document, e.g. using [jQuery's `load` function](https://api.jquery.com/load/),
459simply by adding `Accept: text/html` to their request headers.
461### Case-studies: **servant-blaze** and **servant-lucid**
463These days, most of the haskellers who write their HTML UIs directly from
464Haskell use either [**blaze-html**](http://hackage.haskell.org/package/blaze-html)
465or [**lucid**](http://hackage.haskell.org/package/lucid). The best option for
466**servant** is obviously to support both (and hopefully other templating
467solutions!). We're first going to look at **lucid**:
473Once again, the data type is just there as a symbol for the encoding/decoding
474functions, except that this time we will only worry about encoding since
475**lucid** doesn't provide a way to extract data from HTML.
478instance Accept HTMLLucid where
479 contentType _ = "text" // "html" /: ("charset", "utf-8")
482Note that this instance uses the `(/:)` operator from **http-media** which lets
483us specify additional information about a content-type, like the charset here.
485The rendering instances call similar functions that take
486types with an appropriate instance to an "abstract" HTML representation and
487then write that to a `ByteString`.
490instance ToHtml a => MimeRender HTMLLucid a where
491 mimeRender _ = renderBS . toHtml
493-- let's also provide an instance for lucid's
495instance MimeRender HTMLLucid (Html a) where
496 mimeRender _ = renderBS
499For **blaze-html** everything works very similarly:
502-- For this tutorial to compile 'HTMLLucid' and 'HTMLBlaze' have to be
503-- distinct. Usually you would stick to one html rendering library and then
504-- you can go with one 'HTML' type.
507instance Accept HTMLBlaze where
508 contentType _ = "text" // "html" /: ("charset", "utf-8")
510instance ToMarkup a => MimeRender HTMLBlaze a where
511 mimeRender _ = renderHtml . Text.Blaze.Html.toHtml
513-- while we're at it, just like for lucid we can
514-- provide an instance for rendering blaze's 'Html' type
515instance MimeRender HTMLBlaze Text.Blaze.Html.Html where
516 mimeRender _ = renderHtml
519Both [**servant-blaze**](http://hackage.haskell.org/package/servant-blaze) and
520[**servant-lucid**](http://hackage.haskell.org/package/servant-lucid) let you use
521`HTMLLucid` and `HTMLBlaze` in any content-type list as long as you provide an instance of the
522appropriate class (`ToMarkup` for **blaze-html**, `ToHtml` for **lucid**).
524We can now write a webservice that uses **servant-lucid** to show the `HTMLLucid`
525content-type in action. We will be serving the following API:
528type PersonAPI = "persons" :> Get '[JSON, HTMLLucid] [Person]
531where `Person` is defined as follows:
535 { firstName :: String
537 } deriving Generic -- for the JSON instance
539instance ToJSON Person
542Now, let's teach **lucid** how to render a `Person` as a row in a table, and then
543a list of `Person`s as a table with a row per person.
546-- HTML serialization of a single person
547instance ToHtml Person where
550 td_ (toHtml $ firstName person)
551 td_ (toHtml $ lastName person)
553 -- do not worry too much about this
556-- HTML serialization of a list of persons
557instance ToHtml [Person] where
558 toHtml persons = table_ $ do
563 -- this just calls toHtml on each person of the list
564 -- and concatenates the resulting pieces of HTML together
565 foldMap toHtml persons
570We create some `Person` values and serve them as a list:
575 [ Person "Isaac" "Newton"
576 , Person "Albert" "Einstein"
579personAPI :: Proxy PersonAPI
582server4 :: Server PersonAPI
583server4 = return people
586app2 = serve personAPI server4
592$ curl http://localhost:8081/persons
593[{"lastName":"Newton","firstName":"Isaac"},{"lastName":"Einstein","firstName":"Albert"}]
594$ curl -H 'Accept: text/html' http://localhost:8081/persons
595<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>
596# or just point your browser to http://localhost:8081/persons
599## The `Handler` monad
601At the heart of the handlers is the monad they run in, namely a newtype `Handler` around `ExceptT ServantErr IO`
602([haddock documentation for `ExceptT`](http://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html#t:ExceptT)).
603One might wonder: why this monad? The answer is that it is the
604simplest monad with the following properties:
606- it lets us both return a successful result (using `return`)
607or "fail" with a descriptive error (using `throwError`);
608- it lets us perform IO, which is absolutely vital since most webservices exist
609as interfaces to databases that we interact with in `IO`.
611Let's recall some definitions.
614-- from the 'mtl' package at
615newtype ExceptT e m a = ExceptT (m (Either e a))
618In short, this means that a handler of type `Handler a` is simply
619equivalent to a computation of type `IO (Either ServantErr a)`, that is, an IO
620action that either returns an error or a result.
622The module [`Control.Monad.Except`](https://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html#t:ExceptT)
623from which `ExceptT` comes is worth looking at.
624Perhaps most importantly, `ExceptT` and `Handler` are an instances of `MonadError`, so
625`throwError` can be used to return an error from your handler (whereas `return`
626 is enough to return a success).
628Most of what you'll be doing in your handlers is running some IO and,
629depending on the result, you might sometimes want to throw an error of some
630kind and abort early. The next two sections cover how to do just that.
634Another important instances from the list above are `MonadIO m => MonadIO
635(ExceptT e m)`, and therefore also `MonadIO Handler` as there is `MonadIO IO` instance.
636[`MonadIO`](http://hackage.haskell.org/package/transformers-0.4.3.0/docs/Control-Monad-IO-Class.html)
637is a class from the **transformers** package defined as:
640class Monad m => MonadIO m where
641 liftIO :: IO a -> m a
644So if you want to run any kind of
645IO computation in your handlers, just use `liftIO`:
648type IOAPI1 = "myfile.txt" :> Get '[JSON] FileContent
650newtype FileContent = FileContent
651 { content :: String }
654instance ToJSON FileContent
656server5 :: Server IOAPI1
658 filecontent <- liftIO (readFile "myfile.txt")
659 return (FileContent filecontent)
662### Failing, through `ServantErr`
664If you want to explicitly fail at providing the result promised by an endpoint
665using the appropriate HTTP status code (not found, unauthorized, etc) and some
666error message, all you have to do is use the `throwError` function mentioned above
667and provide it with the appropriate value of type `ServantErr`, which is
671data ServantErr = ServantErr
673 , errReasonPhrase :: String
674 , errBody :: ByteString -- lazy bytestring
675 , errHeaders :: [Header]
679Many standard values are provided out of the box by the `Servant.Server`
680module. If you want to use these values but add a body or some headers, just
681use record update syntax:
684failingHandler :: Handler ()
685failingHandler = throwError myerr
687 where myerr :: ServantErr
688 myerr = err503 { errBody = "Sorry dear user." }
691Here's an example where we return a customised 404-Not-Found error message in
692the response body if "myfile.txt" isn't there:
695server6 :: Server IOAPI1
697 exists <- liftIO (doesFileExist "myfile.txt")
699 then liftIO (readFile "myfile.txt") >>= return . FileContent
700 else throwError custom404Err
702 where custom404Err = err404 { errBody = "myfile.txt just isn't there, please leave this server alone." }
705Here's how that server looks in action:
708$ curl --verbose http://localhost:8081/myfile.txt
710* Connected to localhost (127.0.0.1) port 8081 (#0)
711> GET /myfile.txt HTTP/1.1
712> User-Agent: curl/7.30.0
713> Host: localhost:8081
716< HTTP/1.1 404 Not Found
718myfile.txt just isnt there, please leave this server alone.
720$ echo Hello > myfile.txt
722$ curl --verbose http://localhost:8081/myfile.txt
724* Connected to localhost (127.0.0.1) port 8081 (#0)
725> GET /myfile.txt HTTP/1.1
726> User-Agent: curl/7.30.0
727> Host: localhost:8081
732< Content-Type: application/json
739To add headers to your response, use
740[addHeader](http://hackage.haskell.org/package/servant/docs/Servant-API-ResponseHeaders.html).
741Note that this changes the type of your API, as we can see in the following example:
744type MyHandler = Get '[JSON] (Headers '[Header "X-An-Int" Int] User)
746myHandler :: Server MyHandler
747myHandler = return $ addHeader 1797 albert
750Note that the type of `addHeader header x` is different than the type of `x`!
751And if you add more headers, more headers will appear in the header list:
754type MyHeadfulHandler = Get '[JSON] (Headers '[Header "X-A-Bool" Bool, Header "X-An-Int" Int] User)
756myHeadfulHandler :: Server MyHeadfulHandler
757myHeadfulHandler = return $ addHeader True $ addHeader 1797 albert
760But what if your handler only *sometimes* adds a header? If you declare that
761your handler adds headers, and you don't add one, the return type of your
762handler will be different than expected. To solve this, you have to explicitly
763*not* add a header by using `noHeader`:
766type MyMaybeHeaderHandler
767 = Capture "withHeader" Bool :> Get '[JSON] (Headers '[Header "X-An-Int" Int] User)
769myMaybeHeaderHandler :: Server MyMaybeHeaderHandler
770myMaybeHeaderHandler x = return $ if x then addHeader 1797 albert
774## Serving static files
776**servant-server** also provides a way to just serve the content of a directory
777under some path in your web API. As mentioned earlier in this document, the
778`Raw` combinator can be used in your APIs to mean "plug here any WAI
779application". Well, **servant-server** provides a function to get a file and
780directory serving WAI application, namely:
783-- exported by Servant and Servant.Server
784serveDirectoryWebApp :: FilePath -> Server Raw
787`serveDirectoryWebApp`'s argument must be a path to a valid directory.
789Here's an example API that will serve some static files:
792type StaticAPI = "static" :> Raw
798staticAPI :: Proxy StaticAPI
803server7 :: Server StaticAPI
804server7 = serveDirectoryWebApp "static-files"
807app3 = serve staticAPI server7
810This server will match any request whose path starts with `/static` and will look
811for a file at the path described by the rest of the request path, inside the
812 *static-files/* directory of the path you run the program from.
814In other words: If a client requests `/static/foo.txt`, the server will look for a file at
815`./static-files/foo.txt`. If that file exists it'll succeed and serve the file.
816If it doesn't exist, the handler will fail with a `404` status code.
818`serveDirectoryWebApp` uses some standard settings that fit the use case of
819serving static files for most web apps. You can find out about the other
820options in the documentation of the `Servant.Utils.StaticFiles` module.
824Let's see how you can define APIs in a modular way, while avoiding repetition.
825Consider this simple example:
828type UserAPI3 = -- view the user with given userid, in JSON
829 Capture "userid" Int :> Get '[JSON] User
831 :<|> -- delete the user with given userid. empty response
832 Capture "userid" Int :> DeleteNoContent '[JSON] NoContent
835We can instead factor out the `userid`:
838type UserAPI4 = Capture "userid" Int :>
840 :<|> DeleteNoContent '[JSON] NoContent
844However, you have to be aware that this has an effect on the type of the
845corresponding `Server`:
848Server UserAPI3 = (Int -> Handler User)
849 :<|> (Int -> Handler NoContent)
851Server UserAPI4 = Int -> ( Handler User
852 :<|> Handler NoContent
856In the first case, each handler receives the *userid* argument. In the latter,
857the whole `Server` takes the *userid* and has handlers that are just
858computations in `Handler`, with no arguments. In other words:
861server8 :: Server UserAPI3
862server8 = getUser :<|> deleteUser
864 where getUser :: Int -> Handler User
865 getUser _userid = error "..."
867 deleteUser :: Int -> Handler NoContent
868 deleteUser _userid = error "..."
870-- notice how getUser and deleteUser
871-- have a different type! no argument anymore,
872-- the argument directly goes to the whole Server
873server9 :: Server UserAPI4
874server9 userid = getUser userid :<|> deleteUser userid
876 where getUser :: Int -> Handler User
877 getUser = error "..."
879 deleteUser :: Int -> Handler NoContent
880 deleteUser = error "..."
883Note that there's nothing special about `Capture` that lets you "factor it
884out": this can be done with any combinator. Here are a few examples of APIs
885with a combinator factored out for which we can write a perfectly valid
889-- we just factor out the "users" path fragment
890type API1 = "users" :>
891 ( Get '[JSON] [User] -- user listing
892 :<|> Capture "userid" Int :> Get '[JSON] User -- view a particular user
895-- we factor out the Request Body
896type API2 = ReqBody '[JSON] User :>
897 ( Get '[JSON] User -- just display the same user back, don't register it
898 :<|> PostNoContent '[JSON] NoContent -- register the user. empty response
901-- we factor out a Header
902type API3 = Header "Authorization" Token :>
903 ( Get '[JSON] SecretData -- get some secret data, if authorized
904 :<|> ReqBody '[JSON] SecretData :> PostNoContent '[JSON] NoContent -- add some secret data, if authorized
907newtype Token = Token ByteString
908newtype SecretData = SecretData ByteString
911This approach lets you define APIs modularly and assemble them all into one big
912API type only at the end.
916 Get '[JSON] [User] -- list users
917 :<|> ReqBody '[JSON] User :> PostNoContent '[JSON] NoContent -- add a user
918 :<|> Capture "userid" Int :>
919 ( Get '[JSON] User -- view a user
920 :<|> ReqBody '[JSON] User :> PutNoContent '[JSON] NoContent -- update a user
921 :<|> DeleteNoContent '[JSON] NoContent -- delete a user
924usersServer :: Server UsersAPI
925usersServer = getUsers :<|> newUser :<|> userOperations
927 where getUsers :: Handler [User]
928 getUsers = error "..."
930 newUser :: User -> Handler NoContent
931 newUser = error "..."
933 userOperations userid =
934 viewUser userid :<|> updateUser userid :<|> deleteUser userid
937 viewUser :: Int -> Handler User
938 viewUser = error "..."
940 updateUser :: Int -> User -> Handler NoContent
941 updateUser = error "..."
943 deleteUser :: Int -> Handler NoContent
944 deleteUser = error "..."
949 Get '[JSON] [Product] -- list products
950 :<|> ReqBody '[JSON] Product :> PostNoContent '[JSON] NoContent -- add a product
951 :<|> Capture "productid" Int :>
952 ( Get '[JSON] Product -- view a product
953 :<|> ReqBody '[JSON] Product :> PutNoContent '[JSON] NoContent -- update a product
954 :<|> DeleteNoContent '[JSON] NoContent -- delete a product
957data Product = Product { productId :: Int }
959productsServer :: Server ProductsAPI
960productsServer = getProducts :<|> newProduct :<|> productOperations
962 where getProducts :: Handler [Product]
963 getProducts = error "..."
965 newProduct :: Product -> Handler NoContent
966 newProduct = error "..."
968 productOperations productid =
969 viewProduct productid :<|> updateProduct productid :<|> deleteProduct productid
972 viewProduct :: Int -> Handler Product
973 viewProduct = error "..."
975 updateProduct :: Int -> Product -> Handler NoContent
976 updateProduct = error "..."
978 deleteProduct :: Int -> Handler NoContent
979 deleteProduct = error "..."
983type CombinedAPI = "users" :> UsersAPI
984 :<|> "products" :> ProductsAPI
986server10 :: Server CombinedAPI
987server10 = usersServer :<|> productsServer
990Finally, we can realize the user and product APIs are quite similar and
994-- API for values of type 'a'
995-- indexed by values of type 'i'
997 Get '[JSON] [a] -- list 'a's
998 :<|> ReqBody '[JSON] a :> PostNoContent '[JSON] NoContent -- add an 'a'
999 :<|> Capture "id" i :>
1000 ( Get '[JSON] a -- view an 'a' given its "identifier" of type 'i'
1001 :<|> ReqBody '[JSON] a :> PutNoContent '[JSON] NoContent -- update an 'a'
1002 :<|> DeleteNoContent '[JSON] NoContent -- delete an 'a'
1005-- Build the appropriate 'Server'
1006-- given the handlers of the right type.
1007serverFor :: Handler [a] -- handler for listing of 'a's
1008 -> (a -> Handler NoContent) -- handler for adding an 'a'
1009 -> (i -> Handler a) -- handler for viewing an 'a' given its identifier of type 'i'
1010 -> (i -> a -> Handler NoContent) -- updating an 'a' with given id
1011 -> (i -> Handler NoContent) -- deleting an 'a' given its id
1012 -> Server (APIFor a i)
1013serverFor = error "..."
1014-- implementation left as an exercise. contact us on IRC
1015-- or the mailing list if you get stuck!
1018When your API contains the `EmptyAPI` combinator, you'll want to use
1019`emptyServer` in the corresponding slot for your server, which will simply fail
1020with 404 whenever a request reaches it:
1023type CombinedAPI2 = API :<|> "empty" :> EmptyAPI
1025server11 :: Server CombinedAPI2
1026server11 = server3 :<|> emptyServer
1029## Using another monad for your handlers
1031Remember how `Server` turns combinators for HTTP methods into `Handler`? Well, actually, there's more to that. `Server` is actually a
1035type Server api = ServerT api Handler
1038`ServerT` is the actual type family that computes the required types for the
1039handlers that's part of the `HasServer` class. It's like `Server` except that
1040it takes another parameter which is the monad you want your handlers to run in,
1041or more generally the return types of your handlers. This third parameter is
1042used for specifying the return type of the handler for an endpoint, e.g when
1043computing `ServerT (Get '[JSON] Person) SomeMonad`. The result would be
1046The first and main question one might have then is: how do we write handlers
1047that run in another monad? How can we "bring back" the value from a given monad
1048into something **servant** can understand?
1050### Natural transformations
1052If we have a function that gets us from an `m a` to an `n a`, for any `a`, what
1056type (~>) m n = forall a. m a -> n a
1062listToMaybe' :: [] ~> Maybe
1063listToMaybe' = listToMaybe -- from Data.Maybe
1066Note that `servant` doesn't declare the `~>` type-alias, as the unfolded
1067variant isn't much longer to write, as we'll see shortly.
1069So if you want to write handlers using another monad/type than `Handler`, say the `Reader String` monad, the first thing you have to
1070prepare is a function:
1073readerToHandler :: Reader String a -> Handler a
1076We obviously have to run the `Reader` computation by supplying it with a
1077`String`, like `"hi"`. We get an `a` out from that and can then just `return`
1081readerToHandler :: Reader String a -> Handler a
1082readerToHandler r = return (runReader r "hi")
1085We can write some simple webservice with the handlers running in `Reader String`.
1088type ReaderAPI = "a" :> Get '[JSON] Int
1089 :<|> "b" :> ReqBody '[JSON] Double :> Get '[JSON] Bool
1091readerAPI :: Proxy ReaderAPI
1094readerServerT :: ServerT ReaderAPI (Reader String)
1095readerServerT = a :<|> b where
1096 a :: Reader String Int
1099 b :: Double -> Reader String Bool
1100 b _ = asks (== "hi")
1103We unfortunately can't use `readerServerT` as an argument of `serve`, because
1104`serve` wants a `Server ReaderAPI`, i.e., with handlers running in `Handler`. But there's a simple solution to this.
1106### Welcome `hoistServer`
1108That's right. We have just written `readerToHandler`, which is exactly what we
1109would need to apply to all handlers to make the handlers have the
1110right type for `serve`. Being cumbersome to do by hand, we provide a function
1111`hoistServer` which takes a natural transformation between two parameterized types `m`
1112and `n` and a `ServerT someapi m`, and returns a `ServerT someapi n`.
1114In our case, we can wrap up our little webservice by using
1115`hoistServer readerAPI readerToHandler` on our handlers.
1118readerServer :: Server ReaderAPI
1119readerServer = hoistServer readerAPI readerToHandler readerServerT
1122app4 = serve readerAPI readerServer
1125This is the webservice in action:
1128$ curl http://localhost:8081/a
1130$ curl http://localhost:8081/b
1134### An arrow is a reader too.
1136In previous versions of `servant` we had an `enter` to do what `hoistServer`
1137does now. `enter` had a ambitious design goals, but was problematic in practice.
1139One problematic situation was when the source monad was `(->) r`, yet it's
1140handy in practice, because `(->) r` is isomorphic to `Reader r`.
1142We can rewrite the previous example without `Reader`:
1145funServerT :: ServerT ReaderAPI ((->) String)
1146funServerT = a :<|> b where
1150 -- unfortunately, we cannot make `String` the first argument.
1151 b :: Double -> String -> Bool
1154funToHandler :: (String -> a) -> Handler a
1155funToHandler f = return (f "hi")
1158app5 = serve readerAPI (hoistServer readerAPI funToHandler funServerT)
1161## Streaming endpoints
1163We can create endpoints that don't just give back a single result, but give back a *stream* of results, served one at a time. Stream endpoints only provide a single content type, and also specify what framing strategy is used to delineate the results. To serve these results, we need to give back a stream producer. Adapters can be written to `Pipes`, `Conduit` and the like, or written directly as `StreamGenerator`s. StreamGenerators are IO-based continuations that are handed two functions -- the first to write the first result back, and the second to write all subsequent results back. (This is to allow handling of situations where the entire stream is prefixed by a header, or where a boundary is written between elements, but not prior to the first element). The API of a streaming endpoint needs to explicitly specify which sort of generator it produces. Note that the generator itself is returned by a `Handler` action, so that additional IO may be done in the creation of one.
1166type StreamAPI = "userStream" :> StreamGet NewlineFraming JSON (StreamGenerator User)
1167streamAPI :: Proxy StreamAPI
1170streamUsers :: StreamGenerator User
1171streamUsers = StreamGenerator $ \sendFirst sendRest -> do
1177app6 = serve streamAPI (return streamUsers)
1180This simple application returns a stream of `User` values encoded in JSON format, with each value separated by a newline. In this case, the stream will consist of the value of `isaac`, followed by the value of `albert`, then the value of `albert` a third time. Importantly, the stream is written back as results are produced, rather than all at once. This means first that results are delivered when they are available, and second, that if an exception interrupts production of the full stream, nonetheless partial results have already been written back.
1184You're now equipped to write webservices/web-applications using
1185**servant**. The rest of this document focuses on **servant-client**,
1186**servant-js** and **servant-docs**.
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