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)
98Let's also write our API type.
101type UserAPI1 = "users" :> Get '[JSON] [User]
104We can now take care of writing the actual webservice that will handle requests
105to such an API. This one will be very simple, being reduced to just a single
106endpoint. The type of the web application is determined by the API type,
107through a *type family* named `Server`. (Type families are just functions that
108take types as input and return types.) The `Server` type family will compute
109the right type that a bunch of request handlers should have just from the
110corresponding API type.
112The first thing to know about the `Server` type family is that behind the
113scenes it will drive the routing, letting you focus only on the business
114logic. The second thing to know is that for each endpoint, your handlers will
115by default run in the `Handler` monad. This is overridable very
116easily, as explained near the end of this guide. Third thing, the type of the
117value returned in that monad must be the same as the second argument of the
118HTTP method combinator used for the corresponding endpoint. In our case, it
119means we must provide a handler of type `Handler [User]`. Well,
120we have a monad, let's just `return` our list:
123server1 :: Server UserAPI1
124server1 = return users1
127That's it. Now we can turn `server` into an actual webserver using
128[wai](http://hackage.haskell.org/package/wai) and
129[warp](http://hackage.haskell.org/package/warp):
132userAPI :: Proxy UserAPI1
135-- 'serve' comes from servant and hands you a WAI Application,
136-- which you can think of as an "abstract" web application,
137-- not yet a webserver.
139app1 = serve userAPI server1
142The `userAPI` bit is, alas, boilerplate (we need it to guide type inference).
143But that's about as much boilerplate as you get.
145And we're done! Let's run our webservice on the port 8081.
152You can put this all into a file or just grab [servant's
153repo](http://github.com/haskell-servant/servant) and look at the
154*doc/tutorial* directory. This code (the source of this web page) is in
155*doc/tutorial/Server.lhs*.
157If you run it, you can go to `http://localhost:8081/users` in your browser or
158query it with curl and you see:
161$ curl http://localhost:8081/users
162[{"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"}]
167What if we want more than one endpoint? Let's add `/albert` and `/isaac` to
168view the corresponding users encoded in JSON.
171type UserAPI2 = "users" :> Get '[JSON] [User]
172 :<|> "albert" :> Get '[JSON] User
173 :<|> "isaac" :> Get '[JSON] User
176And let's adapt our code a bit.
180isaac = User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
183albert = User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
186users2 = [isaac, albert]
189Now, just like we separate the various endpoints in `UserAPI` with `:<|>`, we
190are going to separate the handlers with `:<|>` too! They must be provided in
191the same order as in in the API type.
194server2 :: Server UserAPI2
195server2 = return users2
200And that's it! You can run this example in the same way that we showed for
201`server1` and check out the data available at `/users`, `/albert` and `/isaac`.
203## From combinators to handler arguments
205Fine, we can write trivial webservices easily, but none of the two above use
206any "fancy" combinator from servant. Let's address this and use `QueryParam`,
207`Capture` and `ReqBody` right away. You'll see how each occurence of these
208combinators in an endpoint makes the corresponding handler receive an
209argument of the appropriate type automatically. You don't have to worry about
210manually looking up URL captures or query string parameters, or
211decoding/encoding data from/to JSON. Never.
213We are going to use the following data types and functions to implement a
217type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
218 :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
219 :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
221data Position = Position
226instance ToJSON Position
228newtype HelloMessage = HelloMessage { msg :: String }
231instance ToJSON HelloMessage
233data ClientInfo = ClientInfo
234 { clientName :: String
235 , clientEmail :: String
237 , clientInterestedIn :: [String]
240instance FromJSON ClientInfo
241instance ToJSON ClientInfo
252emailForClient :: ClientInfo -> Email
253emailForClient c = Email from' to' subject' body'
255 where from' = "great@company.com"
257 subject' = "Hey " ++ clientName c ++ ", we miss you!"
258 body' = "Hi " ++ clientName c ++ ",\n\n"
259 ++ "Since you've recently turned " ++ show (clientAge c)
260 ++ ", have you checked out our latest "
261 ++ intercalate ", " (clientInterestedIn c)
262 ++ " products? Give us a visit!"
265We can implement handlers for the three endpoints:
273 where position :: Int -> Int -> Handler Position
274 position x y = return (Position x y)
276 hello :: Maybe String -> Handler HelloMessage
277 hello mname = return . HelloMessage $ case mname of
278 Nothing -> "Hello, anonymous coward"
279 Just n -> "Hello, " ++ n
281 marketing :: ClientInfo -> Handler Email
282 marketing clientinfo = return (emailForClient clientinfo)
285Did you see that? The types for your handlers changed to be just what we
286needed! In particular:
288 - a `Capture "something" a` becomes an argument of type `a` (for `position`);
289 - a `QueryParam "something" a` becomes an argument of type `Maybe a` (because
290an endpoint can technically be accessed without specifying any query
291string parameter, we decided to "force" handlers to be aware that the
292parameter might not always be there);
294 - a `ReqBody contentTypeList a` becomes an argument of type `a`;
296And that's it. Here's the example in action:
299$ curl http://localhost:8081/position/1/2
300{"xCoord":1,"yCoord":2}
301$ curl http://localhost:8081/hello
302{"msg":"Hello, anonymous coward"}
303$ curl http://localhost:8081/hello?name=Alp
305$ 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
306{"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"}
309For reference, here's a list of some combinators from **servant**:
311 > - `Delete`, `Get`, `Patch`, `Post`, `Put`: these do not become arguments. They provide the return type of handlers, which usually is `Handler <something>`.
312 > - `Capture "something" a` becomes an argument of type `a`.
313 > - `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.
314 > - `QueryFlag "something"` gets turned into an argument of type `Bool`.
315 > - `QueryParams "something" a` gets turned into an argument of type `[a]`.
316 > - `ReqBody contentTypes a` gets turned into an argument of type `a`.
318## The `FromHttpApiData`/`ToHttpApiData` classes
320Wait... How does **servant** know how to decode the `Int`s from the URL? Or how
321to decode a `ClientInfo` value from the request body? This is what this and the
322following two sections address.
324`Capture`s and `QueryParam`s are represented by some textual value in URLs.
325`Header`s are similarly represented by a pair of a header name and a
326corresponding (textual) value in the request's "metadata". How types are
327decoded from headers, captures, and query params is expressed in a class
328`FromHttpApiData` (from the package
329[**http-api-data**](http://hackage.haskell.org/package/http-api-data)):
332class FromHttpApiData a where
333 {-# MINIMAL parseUrlPiece | parseQueryParam #-}
334 -- | Parse URL path piece.
335 parseUrlPiece :: Text -> Either Text a
336 parseUrlPiece = parseQueryParam
338 -- | Parse HTTP header value.
339 parseHeader :: ByteString -> Either Text a
340 parseHeader = parseUrlPiece . decodeUtf8
342 -- | Parse query param value.
343 parseQueryParam :: Text -> Either Text a
344 parseQueryParam = parseUrlPiece
347As you can see, as long as you provide either `parseUrlPiece` (for `Capture`s)
348or `parseQueryParam` (for `QueryParam`s), the other methods will be defined in
351**http-api-data** provides a decent number of instances, helpers for defining new
352ones, and wonderful documentation.
354There's not much else to say about these classes. You will need instances for
355them when using `Capture`, `QueryParam`, `QueryParams`, and `Header` with your
356types. You will need `FromHttpApiData` instances for server-side request
357handlers and `ToHttpApiData` instances only when using
358**servant-client**, as described in the [section about deriving haskell
359functions to query an API](Client.html).
361## Using content-types with your data types
363The same principle was operating when decoding request bodies from JSON, and
364responses *into* JSON. (JSON is just the running example - you can do this with
367This section introduces a couple of typeclasses provided by **servant** that make
370### The truth behind `JSON`
373What exactly is `JSON` (the type as used in `Get '[JSON] User`)? Like the 3
374other content-types provided out of the box by **servant**, it's a really dumb
384Obviously, this is not all there is to `JSON`, otherwise it would be quite
385pointless. Like most of the data types in **servant**, `JSON` is mostly there as
386a special *symbol* that's associated with encoding (resp. decoding) to (resp.
387from) the *JSON* format. The way this association is performed can be
388decomposed into two steps.
390The first step is to provide a proper
392[**http-media**](https://hackage.haskell.org/package/http-media-0.6.2/docs/Network-HTTP-Media.html))
393representation for `JSON`, or for your own content-types. If you look at the
394haddocks from this link, you can see that we just have to specify
395`application/json` using the appropriate functions. In our case, we can just
396use `(//) :: ByteString -> ByteString -> MediaType`. The precise way to specify
397the `MediaType` is to write an instance for the `Accept` class:
401class Accept ctype where
402 contentType :: Proxy ctype -> MediaType
404instance Accept JSON where
405 contentType _ = "application" // "json"
408The second step is centered around the `MimeRender` and `MimeUnrender` classes.
409These classes just let you specify a way to encode and decode
410values into or from your content-type's representation.
413class Accept ctype => MimeRender ctype a where
414 mimeRender :: Proxy ctype -> a -> ByteString
415 -- alternatively readable as:
416 mimeRender :: Proxy ctype -> (a -> ByteString)
419Given a content-type and some user type, `MimeRender` provides a function that
420encodes values of type `a` to lazy `ByteString`s.
422In the case of `JSON`, this is easily dealt with! For any type `a` with a
423`ToJSON` instance, we can render values of that type to JSON using
427instance ToJSON a => MimeRender JSON a where
428 mimeRender _ = encode
431And now the `MimeUnrender` class, which lets us extract values from lazy
432`ByteString`s, alternatively failing with an error string.
435class Accept ctype => MimeUnrender ctype a where
436 mimeUnrender :: Proxy ctype -> ByteString -> Either String a
439We don't have much work to do there either, `Data.Aeson.eitherDecode` is
440precisely what we need. However, it only allows arrays and objects as toplevel
441JSON values and this has proven to get in our way more than help us so we wrote
442our own little function around **aeson** and **attoparsec** that allows any type of
443JSON value at the toplevel of a "JSON document". Here's the definition in case
447eitherDecodeLenient :: FromJSON a => ByteString -> Either String a
448eitherDecodeLenient input = do
449 v :: Value <- parseOnly (Data.Aeson.Parser.value <* endOfInput) (cs input)
450 parseEither parseJSON v
453This function is exactly what we need for our `MimeUnrender` instance.
456instance FromJSON a => MimeUnrender JSON a where
457 mimeUnrender _ = eitherDecodeLenient
460And this is all the code that lets you use `JSON` with `ReqBody`, `Get`,
461`Post` and friends. We can check our understanding by implementing support
462for an `HTML` content-type, so that users of your webservice can access an
463HTML representation of the data they want, ready to be included in any HTML
464document, e.g. using [jQuery's `load` function](https://api.jquery.com/load/),
465simply by adding `Accept: text/html` to their request headers.
467### Case-studies: **servant-blaze** and **servant-lucid**
469These days, most of the haskellers who write their HTML UIs directly from
470Haskell use either [**blaze-html**](http://hackage.haskell.org/package/blaze-html)
471or [**lucid**](http://hackage.haskell.org/package/lucid). The best option for
472**servant** is obviously to support both (and hopefully other templating
473solutions!). We're first going to look at **lucid**:
479Once again, the data type is just there as a symbol for the encoding/decoding
480functions, except that this time we will only worry about encoding since
481**lucid** doesn't provide a way to extract data from HTML.
484instance Accept HTMLLucid where
485 contentType _ = "text" // "html" /: ("charset", "utf-8")
488Note that this instance uses the `(/:)` operator from **http-media** which lets
489us specify additional information about a content-type, like the charset here.
491The rendering instances call similar functions that take
492types with an appropriate instance to an "abstract" HTML representation and
493then write that to a `ByteString`.
496instance ToHtml a => MimeRender HTMLLucid a where
497 mimeRender _ = renderBS . toHtml
499-- let's also provide an instance for lucid's
501instance MimeRender HTMLLucid (Html a) where
502 mimeRender _ = renderBS
505For **blaze-html** everything works very similarly:
508-- For this tutorial to compile 'HTMLLucid' and 'HTMLBlaze' have to be
509-- distinct. Usually you would stick to one html rendering library and then
510-- you can go with one 'HTML' type.
513instance Accept HTMLBlaze where
514 contentType _ = "text" // "html" /: ("charset", "utf-8")
516instance ToMarkup a => MimeRender HTMLBlaze a where
517 mimeRender _ = renderHtml . Text.Blaze.Html.toHtml
519-- while we're at it, just like for lucid we can
520-- provide an instance for rendering blaze's 'Html' type
521instance MimeRender HTMLBlaze Text.Blaze.Html.Html where
522 mimeRender _ = renderHtml
525Both [**servant-blaze**](http://hackage.haskell.org/package/servant-blaze) and
526[**servant-lucid**](http://hackage.haskell.org/package/servant-lucid) let you use
527`HTMLLucid` and `HTMLBlaze` in any content-type list as long as you provide an instance of the
528appropriate class (`ToMarkup` for **blaze-html**, `ToHtml` for **lucid**).
530We can now write a webservice that uses **servant-lucid** to show the `HTMLLucid`
531content-type in action. We will be serving the following API:
534type PersonAPI = "persons" :> Get '[JSON, HTMLLucid] [Person]
537where `Person` is defined as follows:
541 { firstName :: String
543 } deriving Generic -- for the JSON instance
545instance ToJSON Person
548Now, let's teach **lucid** how to render a `Person` as a row in a table, and then
549a list of `Person`s as a table with a row per person.
552-- HTML serialization of a single person
553instance ToHtml Person where
556 td_ (toHtml $ firstName person)
557 td_ (toHtml $ lastName person)
559 -- do not worry too much about this
562-- HTML serialization of a list of persons
563instance ToHtml [Person] where
564 toHtml persons = table_ $ do
569 -- this just calls toHtml on each person of the list
570 -- and concatenates the resulting pieces of HTML together
571 foldMap toHtml persons
576We create some `Person` values and serve them as a list:
581 [ Person "Isaac" "Newton"
582 , Person "Albert" "Einstein"
585personAPI :: Proxy PersonAPI
588server4 :: Server PersonAPI
589server4 = return people
592app2 = serve personAPI server4
598$ curl http://localhost:8081/persons
599[{"lastName":"Newton","firstName":"Isaac"},{"lastName":"Einstein","firstName":"Albert"}]
600$ curl -H 'Accept: text/html' http://localhost:8081/persons
601<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>
602# or just point your browser to http://localhost:8081/persons
605## The `Handler` monad
607At the heart of the handlers is the monad they run in, namely a newtype `Handler` around `ExceptT ServantErr IO`
608([haddock documentation for `ExceptT`](http://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html#t:ExceptT)).
609One might wonder: why this monad? The answer is that it is the
610simplest monad with the following properties:
612- it lets us both return a successful result (using `return`)
613or "fail" with a descriptive error (using `throwError`);
614- it lets us perform IO, which is absolutely vital since most webservices exist
615as interfaces to databases that we interact with in `IO`.
617Let's recall some definitions.
620-- from the 'mtl' package at
621newtype ExceptT e m a = ExceptT (m (Either e a))
624In short, this means that a handler of type `Handler a` is simply
625equivalent to a computation of type `IO (Either ServantErr a)`, that is, an IO
626action that either returns an error or a result.
628The module [`Control.Monad.Except`](https://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html#t:ExceptT)
629from which `ExceptT` comes is worth looking at.
630Perhaps most importantly, `ExceptT` and `Handler` are an instances of `MonadError`, so
631`throwError` can be used to return an error from your handler (whereas `return`
632 is enough to return a success).
634Most of what you'll be doing in your handlers is running some IO and,
635depending on the result, you might sometimes want to throw an error of some
636kind and abort early. The next two sections cover how to do just that.
640Another important instances from the list above are `MonadIO m => MonadIO
641(ExceptT e m)`, and therefore also `MonadIO Handler` as there is `MonadIO IO` instance..
642[`MonadIO`](http://hackage.haskell.org/package/transformers-0.4.3.0/docs/Control-Monad-IO-Class.html)
643is a class from the **transformers** package defined as:
646class Monad m => MonadIO m where
647 liftIO :: IO a -> m a
650So if you want to run any kind of
651IO computation in your handlers, just use `liftIO`:
654type IOAPI1 = "myfile.txt" :> Get '[JSON] FileContent
656newtype FileContent = FileContent
657 { content :: String }
660instance ToJSON FileContent
662server5 :: Server IOAPI1
664 filecontent <- liftIO (readFile "myfile.txt")
665 return (FileContent filecontent)
668### Failing, through `ServantErr`
670If you want to explicitly fail at providing the result promised by an endpoint
671using the appropriate HTTP status code (not found, unauthorized, etc) and some
672error message, all you have to do is use the `throwError` function mentioned above
673and provide it with the appropriate value of type `ServantErr`, which is
677data ServantErr = ServantErr
679 , errReasonPhrase :: String
680 , errBody :: ByteString -- lazy bytestring
681 , errHeaders :: [Header]
685Many standard values are provided out of the box by the `Servant.Server`
686module. If you want to use these values but add a body or some headers, just
687use record update syntax:
690failingHandler :: Handler ()
691failingHandler = throwError myerr
693 where myerr :: ServantErr
694 myerr = err503 { errBody = "Sorry dear user." }
697Here's an example where we return a customised 404-Not-Found error message in
698the response body if "myfile.txt" isn't there:
701server6 :: Server IOAPI1
703 exists <- liftIO (doesFileExist "myfile.txt")
705 then liftIO (readFile "myfile.txt") >>= return . FileContent
706 else throwError custom404Err
708 where custom404Err = err404 { errBody = "myfile.txt just isn't there, please leave this server alone." }
711Here's how that server looks in action:
714$ curl --verbose http://localhost:8081/myfile.txt
716* Connected to localhost (127.0.0.1) port 8081 (#0)
717> GET /myfile.txt HTTP/1.1
718> User-Agent: curl/7.30.0
719> Host: localhost:8081
722< HTTP/1.1 404 Not Found
724myfile.txt just isnt there, please leave this server alone.
726$ echo Hello > myfile.txt
728$ curl --verbose http://localhost:8081/myfile.txt
730* Connected to localhost (127.0.0.1) port 8081 (#0)
731> GET /myfile.txt HTTP/1.1
732> User-Agent: curl/7.30.0
733> Host: localhost:8081
738< Content-Type: application/json
745To add headers to your response, use
746[addHeader](http://hackage.haskell.org/package/servant/docs/Servant-API-ResponseHeaders.html).
747Note that this changes the type of your API, as we can see in the following example:
750type MyHandler = Get '[JSON] (Headers '[Header "X-An-Int" Int] User)
752myHandler :: Server MyHandler
753myHandler = return $ addHeader 1797 albert
756Note that the type of `addHeader header x` is different than the type of `x`!
757And if you add more headers, more headers will appear in the header list:
760type MyHeadfulHandler = Get '[JSON] (Headers '[Header "X-A-Bool" Bool, Header "X-An-Int" Int] User)
762myHeadfulHandler :: Server MyHeadfulHandler
763myHeadfulHandler = return $ addHeader True $ addHeader 1797 albert
766But what if your handler only *sometimes* adds a header? If you declare that
767your handler adds headers, and you don't add one, the return type of your
768handler will be different than expected. To solve this, you have to explicitly
769*not* add a header by using `noHeader`:
772type MyMaybeHeaderHandler
773 = Capture "withHeader" Bool :> Get '[JSON] (Headers '[Header "X-An-Int" Int] User)
775myMaybeHeaderHandler :: Server MyMaybeHeaderHandler
776myMaybeHeaderHandler x = return $ if x then addHeader 1797 albert
780## Serving static files
782**servant-server** also provides a way to just serve the content of a directory
783under some path in your web API. As mentioned earlier in this document, the
784`Raw` combinator can be used in your APIs to mean "plug here any WAI
785application". Well, **servant-server** provides a function to get a file and
786directory serving WAI application, namely:
789-- exported by Servant and Servant.Server
790serveDirectoryWebApp :: FilePath -> Server Raw
793`serveDirectoryWebApp`'s argument must be a path to a valid directory.
795Here's an example API that will serve some static files:
798type StaticAPI = "static" :> Raw
804staticAPI :: Proxy StaticAPI
809server7 :: Server StaticAPI
810server7 = serveDirectoryWebApp "static-files"
813app3 = serve staticAPI server7
816This server will match any request whose path starts with `/static` and will look
817for a file at the path described by the rest of the request path, inside the
818 *static-files/* directory of the path you run the program from.
820In other words: If a client requests `/static/foo.txt`, the server will look for a file at
821`./static-files/foo.txt`. If that file exists it'll succeed and serve the file.
822If it doesn't exist, the handler will fail with a `404` status code.
824`serveDirectoryWebApp` uses some standard settings that fit the use case of
825serving static files for most web apps. You can find out about the other
826options in the documentation of the `Servant.Utils.StaticFiles` module.
830Let's see how you can define APIs in a modular way, while avoiding repetition.
831Consider this simple example:
834type UserAPI3 = -- view the user with given userid, in JSON
835 Capture "userid" Int :> Get '[JSON] User
837 :<|> -- delete the user with given userid. empty response
838 Capture "userid" Int :> DeleteNoContent '[JSON] NoContent
841We can instead factor out the `userid`:
844type UserAPI4 = Capture "userid" Int :>
846 :<|> DeleteNoContent '[JSON] NoContent
850However, you have to be aware that this has an effect on the type of the
851corresponding `Server`:
854Server UserAPI3 = (Int -> Handler User)
855 :<|> (Int -> Handler NoContent)
857Server UserAPI4 = Int -> ( Handler User
858 :<|> Handler NoContent
862In the first case, each handler receives the *userid* argument. In the latter,
863the whole `Server` takes the *userid* and has handlers that are just
864computations in `Handler`, with no arguments. In other words:
867server8 :: Server UserAPI3
868server8 = getUser :<|> deleteUser
870 where getUser :: Int -> Handler User
871 getUser _userid = error "..."
873 deleteUser :: Int -> Handler NoContent
874 deleteUser _userid = error "..."
876-- notice how getUser and deleteUser
877-- have a different type! no argument anymore,
878-- the argument directly goes to the whole Server
879server9 :: Server UserAPI4
880server9 userid = getUser userid :<|> deleteUser userid
882 where getUser :: Int -> Handler User
883 getUser = error "..."
885 deleteUser :: Int -> Handler NoContent
886 deleteUser = error "..."
889Note that there's nothing special about `Capture` that lets you "factor it
890out": this can be done with any combinator. Here are a few examples of APIs
891with a combinator factored out for which we can write a perfectly valid
895-- we just factor out the "users" path fragment
896type API1 = "users" :>
897 ( Get '[JSON] [User] -- user listing
898 :<|> Capture "userid" Int :> Get '[JSON] User -- view a particular user
901-- we factor out the Request Body
902type API2 = ReqBody '[JSON] User :>
903 ( Get '[JSON] User -- just display the same user back, don't register it
904 :<|> PostNoContent '[JSON] NoContent -- register the user. empty response
907-- we factor out a Header
908type API3 = Header "Authorization" Token :>
909 ( Get '[JSON] SecretData -- get some secret data, if authorized
910 :<|> ReqBody '[JSON] SecretData :> PostNoContent '[JSON] NoContent -- add some secret data, if authorized
913newtype Token = Token ByteString
914newtype SecretData = SecretData ByteString
917This approach lets you define APIs modularly and assemble them all into one big
918API type only at the end.
922 Get '[JSON] [User] -- list users
923 :<|> ReqBody '[JSON] User :> PostNoContent '[JSON] NoContent -- add a user
924 :<|> Capture "userid" Int :>
925 ( Get '[JSON] User -- view a user
926 :<|> ReqBody '[JSON] User :> PutNoContent '[JSON] NoContent -- update a user
927 :<|> DeleteNoContent '[JSON] NoContent -- delete a user
930usersServer :: Server UsersAPI
931usersServer = getUsers :<|> newUser :<|> userOperations
933 where getUsers :: Handler [User]
934 getUsers = error "..."
936 newUser :: User -> Handler NoContent
937 newUser = error "..."
939 userOperations userid =
940 viewUser userid :<|> updateUser userid :<|> deleteUser userid
943 viewUser :: Int -> Handler User
944 viewUser = error "..."
946 updateUser :: Int -> User -> Handler NoContent
947 updateUser = error "..."
949 deleteUser :: Int -> Handler NoContent
950 deleteUser = error "..."
955 Get '[JSON] [Product] -- list products
956 :<|> ReqBody '[JSON] Product :> PostNoContent '[JSON] NoContent -- add a product
957 :<|> Capture "productid" Int :>
958 ( Get '[JSON] Product -- view a product
959 :<|> ReqBody '[JSON] Product :> PutNoContent '[JSON] NoContent -- update a product
960 :<|> DeleteNoContent '[JSON] NoContent -- delete a product
963data Product = Product { productId :: Int }
965productsServer :: Server ProductsAPI
966productsServer = getProducts :<|> newProduct :<|> productOperations
968 where getProducts :: Handler [Product]
969 getProducts = error "..."
971 newProduct :: Product -> Handler NoContent
972 newProduct = error "..."
974 productOperations productid =
975 viewProduct productid :<|> updateProduct productid :<|> deleteProduct productid
978 viewProduct :: Int -> Handler Product
979 viewProduct = error "..."
981 updateProduct :: Int -> Product -> Handler NoContent
982 updateProduct = error "..."
984 deleteProduct :: Int -> Handler NoContent
985 deleteProduct = error "..."
989type CombinedAPI = "users" :> UsersAPI
990 :<|> "products" :> ProductsAPI
992server10 :: Server CombinedAPI
993server10 = usersServer :<|> productsServer
996Finally, we can realize the user and product APIs are quite similar and
1000-- API for values of type 'a'
1001-- indexed by values of type 'i'
1003 Get '[JSON] [a] -- list 'a's
1004 :<|> ReqBody '[JSON] a :> PostNoContent '[JSON] NoContent -- add an 'a'
1005 :<|> Capture "id" i :>
1006 ( Get '[JSON] a -- view an 'a' given its "identifier" of type 'i'
1007 :<|> ReqBody '[JSON] a :> PutNoContent '[JSON] NoContent -- update an 'a'
1008 :<|> DeleteNoContent '[JSON] NoContent -- delete an 'a'
1011-- Build the appropriate 'Server'
1012-- given the handlers of the right type.
1013serverFor :: Handler [a] -- handler for listing of 'a's
1014 -> (a -> Handler NoContent) -- handler for adding an 'a'
1015 -> (i -> Handler a) -- handler for viewing an 'a' given its identifier of type 'i'
1016 -> (i -> a -> Handler NoContent) -- updating an 'a' with given id
1017 -> (i -> Handler NoContent) -- deleting an 'a' given its id
1018 -> Server (APIFor a i)
1019serverFor = error "..."
1020-- implementation left as an exercise. contact us on IRC
1021-- or the mailing list if you get stuck!
1024When your API contains the `EmptyAPI` combinator, you'll want to use
1025`emptyServer` in the corresponding slot for your server, which will simply fail
1026with 404 whenever a request reaches it:
1029type CombinedAPI2 = API :<|> "empty" :> EmptyAPI
1031server11 :: Server CombinedAPI2
1032server11 = server3 :<|> emptyServer
1035## Using another monad for your handlers
1037Remember how `Server` turns combinators for HTTP methods into `Handler`? Well, actually, there's more to that. `Server` is actually a
1041type Server api = ServerT api Handler
1044`ServerT` is the actual type family that computes the required types for the
1045handlers that's part of the `HasServer` class. It's like `Server` except that
1046it takes another parameter which is the monad you want your handlers to run in,
1047or more generally the return types of your handlers. This third parameter is
1048used for specifying the return type of the handler for an endpoint, e.g when
1049computing `ServerT (Get '[JSON] Person) SomeMonad`. The result would be
1052The first and main question one might have then is: how do we write handlers
1053that run in another monad? How can we "bring back" the value from a given monad
1054into something **servant** can understand?
1056### Natural transformations
1058If we have a function that gets us from an `m a` to an `n a`, for any `a`, what
1062type (~>) m n = forall a. m a -> n a
1068listToMaybe' :: [] ~> Maybe
1069listToMaybe' = listToMaybe -- from Data.Maybe
1072Note that `servant` doesn't declare the `~>` type-alias, as the unfolded
1073variant isn't much longer to write, as we'll see shortly.
1075So if you want to write handlers using another monad/type than `Handler`, say the `Reader String` monad, the first thing you have to
1076prepare is a function:
1079readerToHandler :: Reader String a -> Handler a
1082We obviously have to run the `Reader` computation by supplying it with a
1083`String`, like `"hi"`. We get an `a` out from that and can then just `return`
1087readerToHandler :: Reader String a -> Handler a
1088readerToHandler r = return (runReader r "hi")
1091We can write some simple webservice with the handlers running in `Reader String`.
1094type ReaderAPI = "a" :> Get '[JSON] Int
1095 :<|> "b" :> ReqBody '[JSON] Double :> Get '[JSON] Bool
1097readerAPI :: Proxy ReaderAPI
1100readerServerT :: ServerT ReaderAPI (Reader String)
1101readerServerT = a :<|> b where
1102 a :: Reader String Int
1105 b :: Double -> Reader String Bool
1106 b _ = asks (== "hi")
1109We unfortunately can't use `readerServerT` as an argument of `serve`, because
1110`serve` wants a `Server ReaderAPI`, i.e., with handlers running in `Handler`. But there's a simple solution to this.
1112### Welcome `hoistServer`
1114That's right. We have just written `readerToHandler`, which is exactly what we
1115would need to apply to all handlers to make the handlers have the
1116right type for `serve`. Being cumbersome to do by hand, we provide a function
1117`hoistServer` which takes a natural transformation between two parametrized types `m`
1118and `n` and a `ServerT someapi m`, and returns a `ServerT someapi n`.
1120In our case, we can wrap up our little webservice by using
1121`hoistServer readerAPI readerToHandler` on our handlers.
1124readerServer :: Server ReaderAPI
1125readerServer = hoistServer readerAPI readerToHandler readerServerT
1128app4 = serve readerAPI readerServer
1131This is the webservice in action:
1134$ curl http://localhost:8081/a
1136$ curl http://localhost:8081/b
1140### An arrow is a reader too.
1142In previous versions of `servant` we had an `enter` to do what `hoistServer`
1143does now. `enter` had a ambitious design goals, but was problematic in practice.
1145One problematic situation was when the source monad was `(->) r`, yet it's
1146handy in practice, because `(->) r` is isomorphic to `Reader r`.
1148We can rewrite the previous example without `Reader`:
1151funServerT :: ServerT ReaderAPI ((->) String)
1152funServerT = a :<|> b where
1156 -- unfortunately, we cannot make `String` the first argument.
1157 b :: Double -> String -> Bool
1160funToHandler :: (String -> a) -> Handler a
1161funToHandler f = return (f "hi")
1164app5 = serve readerAPI (hoistServer readerAPI funToHandler funServerT)
1169You're now equipped to write webservices/web-applications using
1170**servant**. The rest of this document focuses on **servant-client**,
1171**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