git.iko.soy/typeable/servant
on master help sign in
Merge pull request #867 from haskell-servant/cookbook

[Docs] Cookbook

commit68bc41b41dd72658bd39dad9c05898eabc73caa4 authorOleg Grenrus <oleg.grenrus@iki.fi> dateWed 27 Dec 2017, 14:11 (9 years ago) parentcded17e8e0cb61
21 files changed+1366−2
.gitignore−2 cabal.project+7 doc/cookbook/basic-auth/BasicAuth.lhs+179 doc/cookbook/basic-auth/basic-auth.cabal+30 doc/cookbook/cabal.project+10 doc/cookbook/db-postgres-pool/PostgresPool.lhs+143 doc/cookbook/db-postgres-pool/db-postgres-pool.cabal+32 doc/cookbook/db-sqlite-simple/DBConnection.lhs+100 doc/cookbook/db-sqlite-simple/db-sqlite-simple.cabal+30 doc/cookbook/file-upload/FileUpload.lhs+140 doc/cookbook/file-upload/file-upload.cabal+30 doc/cookbook/https/Https.lhs+62 doc/cookbook/https/https.cabal+25 doc/cookbook/index.rst+26 doc/cookbook/jwt-and-basic-auth/JWTAndBasicAuth.lhs+252 doc/cookbook/jwt-and-basic-auth/jwt-and-basic-auth.cabal+36 doc/cookbook/structuring-apis/StructuringApis.lhs+195 doc/cookbook/structuring-apis/structuring-apis.cabal+24 doc/index.rst+1 nix/README.md+23 nix/shell.nix+21
.gitignore−2
@@ -25,8 +25,6 @@ cabal.config
2525*.hp
2626Setup
2727.stack-work
28−shell.nix
29−default.nix
3028doc/_build
3129doc/venv
3230doc/tutorial/static/api.js
cabal.project+7
@@ -5,6 +5,13 @@ packages: servant/
55 servant-foreign/
66 servant-server/
77 doc/tutorial/
8+ doc/cookbook/basic-auth/
9+ doc/cookbook/db-postgres-pool/
10+ doc/cookbook/db-sqlite-simple/
11+ doc/cookbook/jwt-and-basic-auth/
12+ doc/cookbook/file-upload/
13+ doc/cookbook/structuring-apis/
14+ doc/cookbook/https/
815
916allow-newer: servant-js:servant-foreign
1017
doc/cookbook/basic-auth/BasicAuth.lhsnew file+179
@@ -0,0 +1,179 @@
1+# Basic Authentication
2+
3+Let's see a simple example of a web application with a
4+single endpoint, protected by
5+[Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication).
6+
7+First, some throat clearing.
8+
9+``` haskell
10+{-# LANGUAGE DataKinds #-}
11+{-# LANGUAGE OverloadedStrings #-}
12+{-# LANGUAGE TypeOperators #-}
13+import Control.Concurrent
14+import Control.Exception
15+import qualified Data.Map as Map
16+import qualified Data.Text as T
17+import Data.Text.Encoding (decodeUtf8)
18+import Network.HTTP.Client (newManager, defaultManagerSettings)
19+import Network.Wai.Handler.Warp
20+import Servant
21+import Servant.Client
22+```
23+
24+We will be dealing with a very simple model of users, as shown below.
25+Our "user database" will just be a map from usernames to full user details.
26+For the sake of simplicity, it will just be read only but the same code could
27+be used with mutable references, database connections, files and more in place
28+of our `Map`.
29+
30+``` haskell
31+type Username = T.Text
32+type Password = T.Text
33+type Website = T.Text
34+
35+data User = User
36+ { user :: Username
37+ , pass :: Password
38+ , site :: Website
39+ } deriving (Eq, Show)
40+
41+-- could be a postgres connection, a file, anything.
42+type UserDB = Map.Map Username User
43+
44+-- create a "database" from a list of users
45+createUserDB :: [User] -> UserDB
46+createUserDB users = Map.fromList [ (user u, u) | u <- users ]
47+
48+-- our test database
49+userDB :: UserDB
50+userDB = createUserDB
51+ [ User "john" "shhhh" "john.com"
52+ , User "foo" "bar" "foobar.net"
53+ ]
54+```
55+
56+Our API will contain a single endpoint, returning the authenticated
57+user's own website.
58+
59+``` haskell
60+-- a 'GET /mysite' endpoint, protected by basic authentication
61+type API = BasicAuth "People's websites" User :> "mysite" :> Get '[JSON] Website
62+
63+{- if there were more endpoints to be protected, one could write:
64+type API = BasicAuth "People's websites" User :>
65+ ( "foo" :> Get '[JSON] Foo
66+ :<|> "bar" :> Get '[JSON] Bar
67+ )
68+-}
69+
70+api :: Proxy API
71+api = Proxy
72+
73+server :: Server API
74+server usr = return (site usr)
75+```
76+
77+In order to protect our endpoint (`"mysite" :> Get '[JSON] Website`), we simply
78+drop the `BasicAuth` combinator in front of it. Its first parameter,
79+`"People's websites"` in our example, is the realm, which is an arbitrary string
80+identifying the protected resources. The second parameter, `User` in our example,
81+corresponds to the type we want to use to represent authenticated users. It could
82+be anything.
83+
84+When using `BasicAuth` in an API, the server implementation "gets" an argument
85+of the authenticated user type used with `BasicAuth`, `User` in our case, in the
86+"corresponding spot". In this example, the server implementation simply returns
87+the `site` field of the authenticated user. More realistic applications would
88+have endpoints that take other arguments and where a lot more logic would
89+be implemented. But in a sense, `BasicAuth` adds an argument just like `Capture`,
90+`QueryParam`, `ReqBody` and friends. But instead of performing some form of
91+decoding logic behind the scenes, servant runs some "basic auth check" that the
92+user provides.
93+
94+In our case, we need access to our user database, so we simply
95+take it as an argument. A more serious implementation would probably take
96+a database connection or even a connection pool.
97+
98+``` haskell
99+-- provided we are given a user database, we can supply
100+-- a function that checks the basic auth credentials
101+-- against our database.
102+checkBasicAuth :: UserDB -> BasicAuthCheck User
103+checkBasicAuth db = BasicAuthCheck $ \basicAuthData ->
104+ let username = decodeUtf8 (basicAuthUsername basicAuthData)
105+ password = decodeUtf8 (basicAuthPassword basicAuthData)
106+ in
107+ case Map.lookup username db of
108+ Nothing -> return NoSuchUser
109+ Just u -> if pass u == password
110+ then return (Authorized u)
111+ else return BadPassword
112+```
113+
114+This check simply looks up the user in the "database" and makes sure the
115+right password was used. For reference, here are the definitions of
116+`BasicAuthResult` and `BasicAuthCheck`:
117+
118+```
119+-- | The result of authentication/authorization
120+data BasicAuthResult usr
121+ = Unauthorized
122+ | BadPassword
123+ | NoSuchUser
124+ | Authorized usr
125+ deriving (Eq, Show, Read, Generic, Typeable, Functor)
126+
127+-- | Datatype wrapping a function used to check authentication.
128+newtype BasicAuthCheck usr = BasicAuthCheck
129+ { unBasicAuthCheck :: BasicAuthData
130+ -> IO (BasicAuthResult usr)
131+ }
132+ deriving (Generic, Typeable, Functor)
133+```
134+
135+This is all great, but how is our `BasicAuth` combinator supposed to know
136+that it should use our `checkBasicAuth` from above? The answer is that it
137+simply expects to find a `BasicAuthCheck` value for the right user type in
138+the `Context` with which we serve the application, where `Context` is just
139+servant's way to allow users to communicate some configuration of sorts to
140+combinators. It is nothing more than an heterogeneous list and we can create
141+a context with our auth check and run our application with it with the following
142+code:
143+
144+``` haskell
145+runApp :: UserDB -> IO ()
146+runApp db = run 8080 (serveWithContext api ctx server)
147+
148+ where ctx = checkBasicAuth db :. EmptyContext
149+```
150+
151+`ctx` above is just a context with one element, `checkBasicAuth db`,
152+whose type is `BasicAuthCheck User`. In order to say that we want to serve our
153+application using the supplied context, we just have to use `serveWithContext`
154+in place of `serve`.
155+
156+Finally, let's derive a client to this endpoint as well in order to see our
157+server in action!
158+
159+``` haskell
160+getSite :: BasicAuthData -> ClientM Website
161+getSite = client api
162+
163+main :: IO ()
164+main = do
165+ mgr <- newManager defaultManagerSettings
166+ bracket (forkIO $ runApp userDB) killThread $ \_ ->
167+ runClientM (getSite u) (ClientEnv mgr (BaseUrl Http "localhost" 8080 ""))
168+ >>= print
169+
170+ where u = BasicAuthData "foo" "bar"
171+```
172+
173+This program prints `Right "foobar.net"`, as expected. Feel free to change this
174+code and see what happens when you specify credentials that are not in the
175+database.
176+
177+The entire program covered here is available as a literate Haskell file
178+[here](https://github.com/haskell-servant/servant/tree/master/doc/cookbook/basic-auth),
179+along with a `cabal` project.
doc/cookbook/basic-auth/basic-auth.cabalnew file+30
@@ -0,0 +1,30 @@
1+name: cookbook-basic-auth
2+version: 0.1
3+synopsis: Basic Authentication cookbook example
4+homepage: http://haskell-servant.readthedocs.org/
5+license: BSD3
6+license-file: ../../../servant/LICENSE
7+author: Servant Contributors
8+maintainer: haskell-servant-maintainers@googlegroups.com
9+build-type: Simple
10+cabal-version: >=1.10
11+
12+executable cookbook-basic-auth
13+ if impl(ghc < 7.10.1)
14+ buildable: False
15+ main-is: BasicAuth.lhs
16+ build-depends: base == 4.*
17+ , text >= 1.2
18+ , aeson >= 1.2
19+ , containers >= 0.5
20+ , servant
21+ , servant-client
22+ , servant-server
23+ , warp >= 3.2
24+ , wai >= 3.2
25+ , http-types >= 0.10
26+ , markdown-unlit >= 0.4
27+ , http-client >= 0.5
28+ default-language: Haskell2010
29+ ghc-options: -Wall -pgmL markdown-unlit
30+ build-tool-depends: markdown-unlit:markdown-unlit
doc/cookbook/cabal.projectnew file+10
@@ -0,0 +1,10 @@
1+packages:
2+ basic-auth/
3+ db-sqlite-simple/
4+ db-postgres-pool/
5+ ../../servant
6+ ../../servant-server
7+ ../../servant-client-core
8+ ../../servant-client
9+ ../../servant-docs
10+ ../../servant-foreign
doc/cookbook/db-postgres-pool/PostgresPool.lhsnew file+143
@@ -0,0 +1,143 @@
1+# PostgreSQL connection pool
2+
3+Let's see how we can write a simple web application that uses a
4+[PostgreSQL](https://www.postgresql.org/) database to store simple textual
5+messages, just like in the SQLite cookbook recipe. The main difference,
6+besides the database technology, is that in this example we will be using
7+a pool of connections to talk to the database server. The pool abstraction
8+will be provided by the
9+[resource-pool](https://hackage.haskell.org/package/resource-pool) library.
10+
11+As usual, we start with a little bit of throat clearing.
12+
13+``` haskell
14+{-# LANGUAGE DataKinds #-}
15+{-# LANGUAGE OverloadedStrings #-}
16+{-# LANGUAGE TypeOperators #-}
17+import Data.ByteString (ByteString)
18+import Control.Concurrent
19+import Control.Exception (bracket)
20+import Control.Monad.IO.Class
21+import Data.Pool
22+import Database.PostgreSQL.Simple
23+import Network.HTTP.Client (newManager, defaultManagerSettings)
24+import Network.Wai.Handler.Warp
25+import Servant
26+import Servant.Client
27+
28+type DBConnectionString = ByteString
29+```
30+
31+We will only care about a single type here, the messages. We want to
32+be able to add a new one and retrieve them all, using two different
33+endpoints.
34+
35+``` haskell
36+type Message = String
37+
38+type API = ReqBody '[PlainText] Message :> Post '[JSON] NoContent
39+ :<|> Get '[JSON] [Message]
40+
41+api :: Proxy API
42+api = Proxy
43+```
44+
45+We proceed with a simple function for creating a table
46+for holding our messages if it doesn't already exist, given
47+a PostgreSQL connection string.
48+
49+``` haskell
50+initDB :: DBConnectionString -> IO ()
51+initDB connstr = bracket (connectPostgreSQL connstr) close $ \conn -> do
52+ execute_ conn
53+ "CREATE TABLE IF NOT EXISTS messages (msg text not null)"
54+ return ()
55+```
56+
57+Next, our server implementation. It will be parametrised (take as
58+argument) by the pool of database connections that handlers can use to
59+talk to the PostgreSQL database. The resource pool abstraction allows us
60+to flexibly set up a whole bunch of PostgreSQL connections tailored to our
61+needs and then to forget about it all by simply asking for a connection
62+using `withResource`.
63+
64+The handlers are straighforward. One takes care of inserting a new
65+value in the database while the other fetches all messages and returns
66+them. We also provide a function for serving our web app given a PostgreSQL
67+connection pool, which simply calls servant-server's `serve` function.
68+
69+``` haskell
70+server :: Pool Connection -> Server API
71+server conns = postMessage :<|> getMessages
72+
73+ where postMessage :: Message -> Handler NoContent
74+ postMessage msg = do
75+ liftIO . withResource conns $ \conn ->
76+ execute conn
77+ "INSERT INTO messages VALUES (?)"
78+ (Only msg)
79+ return NoContent
80+
81+ getMessages :: Handler [Message]
82+ getMessages = fmap (map fromOnly) . liftIO $
83+ withResource conns $ \conn ->
84+ query_ conn "SELECT msg FROM messages"
85+
86+runApp :: Pool Connection -> IO ()
87+runApp conns = run 8080 (serve api $ server conns)
88+```
89+
90+We will also need a function for initialising our connection pool.
91+`resource-pool` is quite configurable, feel free to wander in
92+[its documentation](https://hackage.haskell.org/package/resource-pool)
93+to gain a better understanding of how it works and what the configuration
94+knobs are. I will be using some dummy values in this example.
95+
96+``` haskell
97+initConnectionPool :: DBConnectionString -> IO (Pool Connection)
98+initConnectionPool connStr =
99+ createPool (connectPostgreSQL connStr)
100+ close
101+ 2 -- stripes
102+ 60 -- unused connections are kept open for a minute
103+ 10 -- max. 10 connections open per stripe
104+```
105+
106+Let's finally derive some clients for our API and use them to
107+insert two messages and retrieve them in `main`, after setting up
108+our pool of database connections.
109+
110+``` haskell
111+postMsg :: Message -> ClientM NoContent
112+getMsgs :: ClientM [Message]
113+postMsg :<|> getMsgs = client api
114+
115+main :: IO ()
116+main = do
117+ -- you could read this from some configuration file,
118+ -- environment variable or somewhere else instead.
119+ -- you will need to either change this connection string OR
120+ -- set some environment variables (see
121+ -- https://www.postgresql.org/docs/9.5/static/libpq-envars.html)
122+ -- to point to a running PostgreSQL server for this example to work.
123+ let connStr = ""
124+ pool <- initConnectionPool connStr
125+ initDB connStr
126+ mgr <- newManager defaultManagerSettings
127+ bracket (forkIO $ runApp pool) killThread $ \_ -> do
128+ ms <- flip runClientM (ClientEnv mgr (BaseUrl Http "localhost" 8080 "")) $ do
129+ postMsg "hello"
130+ postMsg "world"
131+ getMsgs
132+ print ms
133+```
134+
135+This program prints `Right ["hello","world"]` the first time it is executed,
136+`Right ["hello","world","hello","world"]` the second time and so on.
137+
138+You could alternatively have the handlers live in `ReaderT (Pool Connection)`
139+and access the pool using e.g `ask`, but this would be more complicated
140+than simply taking the pool as argument.
141+
142+The entire source for this example is available as a cabal project
143+[here](https://github.com/haskell-servant/servant/tree/master/doc/cookbook/db-postgres-pool).
doc/cookbook/db-postgres-pool/db-postgres-pool.cabalnew file+32
@@ -0,0 +1,32 @@
1+name: cookbook-db-postgres-pool
2+version: 0.1
3+synopsis: Simple PostgreSQL connection pool cookbook example
4+homepage: http://haskell-servant.readthedocs.org/
5+license: BSD3
6+license-file: ../../../servant/LICENSE
7+author: Servant Contributors
8+maintainer: haskell-servant-maintainers@googlegroups.com
9+build-type: Simple
10+cabal-version: >=1.10
11+
12+executable cookbook-db-postgres-pool
13+ if impl(ghc < 7.10.1)
14+ buildable: False
15+ main-is: PostgresPool.lhs
16+ build-depends: base == 4.*
17+ , bytestring >= 0.10
18+ , text >= 1.2
19+ , aeson >= 1.2
20+ , servant
21+ , servant-client
22+ , servant-server
23+ , warp >= 3.2
24+ , wai >= 3.2
25+ , http-types >= 0.10
26+ , markdown-unlit >= 0.4
27+ , http-client >= 0.5
28+ , postgresql-simple >= 0.5
29+ , resource-pool >= 0.2
30+ default-language: Haskell2010
31+ ghc-options: -Wall -pgmL markdown-unlit
32+ build-tool-depends: markdown-unlit:markdown-unlit
doc/cookbook/db-sqlite-simple/DBConnection.lhsnew file+100
@@ -0,0 +1,100 @@
1+# SQLite database
2+
3+Let's see how we can write a simple web application that uses an
4+[SQLite](https://www.sqlite.org/) database to store simple textual
5+messages. As usual, we start with a little bit of throat clearing.
6+
7+``` haskell
8+{-# LANGUAGE DataKinds #-}
9+{-# LANGUAGE OverloadedStrings #-}
10+{-# LANGUAGE TypeOperators #-}
11+import Control.Concurrent
12+import Control.Exception (bracket)
13+import Control.Monad.IO.Class
14+import Database.SQLite.Simple
15+import Network.HTTP.Client (newManager, defaultManagerSettings)
16+import Network.Wai.Handler.Warp
17+import Servant
18+import Servant.Client
19+```
20+
21+We will only care about a single type here, the messages. We want to
22+be able to add a new one and retrieve them all, using two different
23+endpoints.
24+
25+``` haskell
26+type Message = String
27+
28+type API = ReqBody '[PlainText] Message :> Post '[JSON] NoContent
29+ :<|> Get '[JSON] [Message]
30+
31+api :: Proxy API
32+api = Proxy
33+```
34+
35+We proceed with a simple function for creating a table
36+for holding our messages if it doesn't already exist.
37+
38+``` haskell
39+initDB :: FilePath -> IO ()
40+initDB dbfile = withConnection dbfile $ \conn ->
41+ execute_ conn
42+ "CREATE TABLE IF NOT EXISTS messages (msg text not null)"
43+```
44+
45+Next, our server implementation. It will be parametrised (take as
46+argument) by the name of the file that contains our SQLite database.
47+The handlers are straighforward. One takes care of inserting a new
48+value in the database while the other fetches all messages and returns
49+them. We also provide a function for serving our web app given an
50+SQLite database file, which simply calls servant-server's `serve` function.
51+
52+``` haskell
53+server :: FilePath -> Server API
54+server dbfile = postMessage :<|> getMessages
55+
56+ where postMessage :: Message -> Handler NoContent
57+ postMessage msg = do
58+ liftIO . withConnection dbfile $ \conn ->
59+ execute conn
60+ "INSERT INTO messages VALUES (?)"
61+ (Only msg)
62+ return NoContent
63+
64+ getMessages :: Handler [Message]
65+ getMessages = fmap (map fromOnly) . liftIO $
66+ withConnection dbfile $ \conn ->
67+ query_ conn "SELECT msg FROM messages"
68+
69+runApp :: FilePath -> IO ()
70+runApp dbfile = run 8080 (serve api $ server dbfile)
71+```
72+
73+Let's also derive some clients for our API and use them to
74+insert two messages and retrieve them in `main`.
75+
76+``` haskell
77+postMsg :: Message -> ClientM NoContent
78+getMsgs :: ClientM [Message]
79+postMsg :<|> getMsgs = client api
80+
81+main :: IO ()
82+main = do
83+ -- you could read this from some configuration file,
84+ -- environment variable or somewhere else instead.
85+ let dbfile = "test.db"
86+ initDB dbfile
87+ mgr <- newManager defaultManagerSettings
88+ bracket (forkIO $ runApp dbfile) killThread $ \_ -> do
89+ ms <- flip runClientM (ClientEnv mgr (BaseUrl Http "localhost" 8080 "")) $ do
90+ postMsg "hello"
91+ postMsg "world"
92+ getMsgs
93+ print ms
94+```
95+
96+This program prints `Right ["hello","world"]` the first time it is executed,
97+`Right ["hello","world","hello","world"]` the second time and so on.
98+
99+The entire source for this example is available as a cabal project
100+[here](https://github.com/haskell-servant/servant/tree/master/doc/cookbook/db-sqlite-simple).
doc/cookbook/db-sqlite-simple/db-sqlite-simple.cabalnew file+30
@@ -0,0 +1,30 @@
1+name: cookbook-db-sqlite-simple
2+version: 0.1
3+synopsis: Simple SQLite DB cookbook example
4+homepage: http://haskell-servant.readthedocs.org/
5+license: BSD3
6+license-file: ../../../servant/LICENSE
7+author: Servant Contributors
8+maintainer: haskell-servant-maintainers@googlegroups.com
9+build-type: Simple
10+cabal-version: >=1.10
11+
12+executable cookbook-db-sqlite-simple
13+ if impl(ghc < 7.10.1)
14+ buildable: False
15+ main-is: DBConnection.lhs
16+ build-depends: base == 4.*
17+ , text >= 1.2
18+ , aeson >= 1.2
19+ , servant
20+ , servant-client
21+ , servant-server
22+ , warp >= 3.2
23+ , wai >= 3.2
24+ , http-types >= 0.10
25+ , markdown-unlit >= 0.4
26+ , http-client >= 0.5
27+ , sqlite-simple >= 0.4
28+ default-language: Haskell2010
29+ ghc-options: -Wall -pgmL markdown-unlit
30+ build-tool-depends: markdown-unlit:markdown-unlit
doc/cookbook/file-upload/FileUpload.lhsnew file+140
@@ -0,0 +1,140 @@
1+# File Upload (`multipart/form-data`)
2+
3+In this recipe, we will implement a web application
4+with a single endpoint that can process
5+`multipart/form-data` request bodies, which most
6+commonly come from HTML forms that allow file upload.
7+
8+As usual, a bit of throat clearing.
9+
10+``` haskell
11+{-# LANGUAGE DataKinds #-}
12+{-# LANGUAGE TypeOperators #-}
13+{-# LANGUAGE OverloadedStrings #-}
14+
15+import Control.Concurrent
16+import Control.Exception
17+import Control.Monad
18+import Control.Monad.IO.Class
19+import Data.Text.Encoding (encodeUtf8)
20+import Network (withSocketsDo)
21+import Network.HTTP.Client hiding (Proxy)
22+import Network.HTTP.Client.MultipartFormData
23+import Network.Wai.Handler.Warp
24+import Servant
25+import Servant.Multipart
26+
27+import qualified Data.ByteString.Lazy as LBS
28+```
29+
30+Our API consists in a single `POST` endpoint at `/`
31+that takes a `multipart/form-data` request body and
32+pretty-prints the data it got to stdout before returning `0`
33+(because why not).
34+
35+``` haskell
36+type API = MultipartForm Mem (MultipartData Mem) :> Post '[JSON] Integer
37+
38+api :: Proxy API
39+api = Proxy
40+```
41+
42+Because of some technicalities, multipart form data is not
43+represented as a good old content type like `JSON` in servant,
44+that one could use with `ReqBody`, but instead is its own
45+dedicated `ReqBody`-like combinator named
46+[`MultiPartForm`](https://hackage.haskell.org/package/servant-multipart-0.11/docs/Servant-Multipart.html#t:MultipartForm).
47+
48+This combinator takes two parameters. The first one is the
49+"backend" to use. Currently, you only have the choice between
50+`Mem` and `Tmp`. The former loads the entire input in memory,
51+even the uploadedd files, while `Tmp` will stream uploaded
52+files to some temporary directory.
53+
54+The second parameter is the type you want the multipart data
55+to be decoded to. Indeed there is a `FromMultipart` class that
56+allows you to specify how to decode multipart form data from
57+`MultipartData` to a custom type of yours. Here we use the
58+trivial "decoding" to `MultipartData` itself, and simply
59+will get our hands on the raw input. If you want to use
60+a type of yours, see the documentation for
61+[`FromMultipart`](https://hackage.haskell.org/package/servant-multipart-0.11/docs/Servant-Multipart.html#t:FromMultipart).
62+
63+Our only request handler has type `MultipartData Mem -> Handler Integer`.
64+All it does is list the textual and file inputs that
65+were sent in the multipart request body. The textual
66+inputs are in the `inputs` field while the file inputs
67+are in the `files` field of `multipartData`.
68+
69+``` haskell
70+-- MultipartData consists in textual inputs,
71+-- accessible through its "inputs" field, as well
72+-- as files, accessible through its "files" field.
73+upload :: Server API
74+upload multipartData = do
75+ liftIO $ do
76+ putStrLn "Inputs:"
77+ forM_ (inputs multipartData) $ \input ->
78+ putStrLn $ " " ++ show (iName input)
79+ ++ " -> " ++ show (iValue input)
80+
81+ forM_ (files multipartData) $ \file -> do
82+ let content = fdPayload file
83+ putStrLn $ "Content of " ++ show (fdFileName file)
84+ LBS.putStr content
85+ return 0
86+
87+startServer :: IO ()
88+startServer = run 8080 (serve api upload)
89+```
90+
91+Finally, a main function that brings up our server and
92+sends some test request with `http-client` (and not
93+servant-client this time, has servant-multipart does not
94+yet have support for client generation.
95+
96+``` haskell
97+main :: IO ()
98+main = withSocketsDo . bracket (forkIO startServer) killThread $ \_threadid -> do
99+ -- we fork the server in a separate thread and send a test
100+ -- request to it from the main thread.
101+ manager <- newManager defaultManagerSettings
102+ req <- parseRequest "http://localhost:8080/"
103+ resp <- flip httpLbs manager =<< formDataBody form req
104+ print resp
105+
106+ where form =
107+ [ partBS "title" "World"
108+ , partBS "text" $ encodeUtf8 "Hello"
109+ , partFileSource "file" "./README.md"
110+ ]
111+```
112+
113+If you run this, you should get:
114+
115+``` bash
116+$ cabal new-build cookbook-file-upload
117+[...]
118+$ dist-newstyle/build/x86_64-linux/ghc-8.2.1/cookbook-file-upload-0.1/x/cookbook-file-upload/build/cookbook-file-upload/cookbook-file-upload
119+Inputs:
120+ "title" -> "World"
121+ "text" -> "Hello"
122+Content of "README.md"
123+# servant - A Type-Level Web DSL
124+
125+![servant](https://raw.githubusercontent.com/haskell-servant/servant/master/servant.png)
126+
127+## Getting Started
128+
129+We have a [tutorial](http://haskell-servant.readthedocs.org/en/stable/tutorial/index.html) that
130+introduces the core features of servant. After this article, you should be able
131+to write your first servant webservices, learning the rest from the haddocks'
132+examples.
133+
134+[...]
135+
136+Response {responseStatus = Status {statusCode = 200, statusMessage = "OK"}, responseVersion = HTTP/1.1, responseHeaders = [("Transfer-Encoding","chunked"),("Date","Fri, 08 Dec 2017 16:50:14 GMT"),("Server","Warp/3.2.13"),("Content-Type","application/json;charset=utf-8")], responseBody = "0", responseCookieJar = CJ {expose = []}, responseClose' = ResponseClose}
137+```
138+
139+As usual, the code for this recipe is available in a cabal project
140+[here](https://github.com/haskell-servant/servant/tree/master/doc/cookbook/file-upload).
doc/cookbook/file-upload/file-upload.cabalnew file+30
@@ -0,0 +1,30 @@
1+name: cookbook-file-upload
2+version: 0.1
3+synopsis: File upload cookbook example
4+homepage: http://haskell-servant.readthedocs.org/
5+license: BSD3
6+license-file: ../../../servant/LICENSE
7+author: Servant Contributors
8+maintainer: haskell-servant-maintainers@googlegroups.com
9+build-type: Simple
10+cabal-version: >=1.10
11+
12+executable cookbook-file-upload
13+ if impl(ghc < 7.10.1)
14+ buildable: False
15+ main-is: FileUpload.lhs
16+ build-depends: base == 4.*
17+ , text >= 1.2
18+ , mtl >= 2.2
19+ , network >= 2.6
20+ , bytestring >= 0.10
21+ , servant
22+ , servant-server
23+ , servant-multipart
24+ , warp >= 3.2
25+ , wai >= 3.2
26+ , markdown-unlit >= 0.4
27+ , http-client >= 0.5
28+ default-language: Haskell2010
29+ ghc-options: -Wall -pgmL markdown-unlit
30+ build-tool-depends: markdown-unlit:markdown-unlit
doc/cookbook/https/Https.lhsnew file+62
@@ -0,0 +1,62 @@
1+# Serving web applications over HTTPS
2+
3+This short recipe shows how one can serve a servant application
4+over HTTPS, by simply using `warp-tls` instead of `warp` to provide
5+us a `run` function for running the `Application` that we get by
6+calling `serve`.
7+
8+As usual, we start by clearing our throat of a few language extensions
9+and imports.
10+
11+``` haskell
12+{-# LANGUAGE DataKinds #-}
13+{-# LANGUAGE TypeOperators #-}
14+import Network.Wai
15+import Network.Wai.Handler.Warp
16+import Network.Wai.Handler.WarpTLS
17+import Servant
18+```
19+
20+No need to work with a complicated API here, let's
21+make it as simple as it gets:
22+
23+``` haskell
24+type API = Get '[JSON] Int
25+
26+api :: Proxy API
27+api = Proxy
28+
29+server :: Server API
30+server = return 10
31+
32+app :: Application
33+app = serve api server
34+```
35+
36+It's now time to actually run the `Application`.
37+The [`warp-tls`](https://hackage.haskell.org/package/warp-tls-3.2.4/docs/Network-Wai-Handler-WarpTLS.html)
38+package provides two functions for running an `Application`, called
39+[`runTLS`](https://hackage.haskell.org/package/warp-tls-3.2.4/docs/Network-Wai-Handler-WarpTLS.html#v:runTLS)
40+and [`runTLSSocket`](https://hackage.haskell.org/package/warp-tls-3.2.4/docs/Network-Wai-Handler-WarpTLS.html#v:runTLSSocket).
41+We will be using the first one.
42+
43+It takes two arguments,
44+[the TLS settings](https://hackage.haskell.org/package/warp-tls-3.2.4/docs/Network-Wai-Handler-WarpTLS.html#t:TLSSettings)
45+(certificates, keys, ciphers, etc)
46+and [the warp settings](https://hackage.haskell.org/package/warp-3.2.12/docs/Network-Wai-Handler-Warp-Internal.html#t:Settings)
47+(port, logger, etc).
48+
49+We will be using very simple settings for this example but you are of
50+course invited to read the documentation for those types to find out
51+about all the knobs that you can play with.
52+
53+``` haskell
54+main :: IO ()
55+main = runTLS tlsOpts warpOpts app
56+
57+ where tlsOpts = tlsSettings "cert.pem" "secret-key.pem"
58+ warpOpts = setPort 8080 defaultSettings
59+```
60+
61+This program is available as a cabal project
62+[here](https://github.com/haskell-servant/servant/tree/master/doc/cookbook/https).
doc/cookbook/https/https.cabalnew file+25
@@ -0,0 +1,25 @@
1+name: cookbook-https
2+version: 0.1
3+synopsis: HTTPS cookbook example
4+homepage: http://haskell-servant.readthedocs.org/
5+license: BSD3
6+license-file: ../../../servant/LICENSE
7+author: Servant Contributors
8+maintainer: haskell-servant-maintainers@googlegroups.com
9+build-type: Simple
10+cabal-version: >=1.10
11+
12+executable cookbook-https
13+ if impl(ghc < 7.10.1)
14+ buildable: False
15+ main-is: Https.lhs
16+ build-depends: base == 4.*
17+ , servant
18+ , servant-server
19+ , wai >= 3.2
20+ , warp >= 3.2
21+ , warp-tls >= 3.2
22+ , markdown-unlit >= 0.4
23+ default-language: Haskell2010
24+ ghc-options: -Wall -pgmL markdown-unlit
25+ build-tool-depends: markdown-unlit:markdown-unlit
doc/cookbook/index.rstnew file+26
@@ -0,0 +1,26 @@
1+Cookbook
2+========
3+
4+This page is a *collective effort* whose goal is to show
5+how to solve many common problems with servant. If you're
6+interested in contributing examples of your own, feel free
7+to open an issue or a pull request on
8+`our github repository <https://github.com/haskell-servant/servant>`_
9+or even to just get in touch with us on the **#servant** IRC channel
10+on freenode or on
11+`the mailing list <https://groups.google.com/forum/#!forum/haskell-servant>`_.
12+
13+The scope is very wide. Simple and fancy authentication schemes,
14+file upload, type-safe links, working with CSV, .zip archives,
15+you name it!
16+
17+.. toctree::
18+ :maxdepth: 1
19+
20+ structuring-apis/StructuringApis.lhs
21+ https/Https.lhs
22+ db-sqlite-simple/DBConnection.lhs
23+ db-postgres-pool/PostgresPool.lhs
24+ basic-auth/BasicAuth.lhs
25+ jwt-and-basic-auth/JWTAndBasicAuth.lhs
26+ file-upload/FileUpload.lhs
doc/cookbook/jwt-and-basic-auth/JWTAndBasicAuth.lhsnew file+252
@@ -0,0 +1,252 @@
1+# Combining JWT-based authentication with basic access authentication
2+
3+In this example we will make a service with
4+[basic HTTP authentication](https://en.wikipedia.org/wiki/Basic_access_authentication)
5+for Haskell clients and other programs, as well as
6+with [JWT](https://en.wikipedia.org/wiki/JSON_Web_Token)-based
7+authentication for web browsers. Web browsers will still use basic
8+HTTP authentication to retrieve JWTs though.
9+
10+**Warning**: this is insecure when done over plain HTTP,
11+so [TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security)
12+should be used.
13+See [warp-tls](https://hackage.haskell.org/package/warp-tls) for that.
14+
15+While basic authentication comes with Servant itself,
16+[servant-auth](https://hackage.haskell.org/package/servant-auth) and
17+[servant-auth-server](https://hackage.haskell.org/package/servant-auth-server)
18+packages are needed for the JWT-based one.
19+
20+This recipe uses the following ingredients:
21+
22+```haskell
23+{-# LANGUAGE OverloadedStrings, TypeFamilies, DataKinds,
24+ DeriveGeneric, TypeOperators #-}
25+import Data.Aeson
26+import GHC.Generics
27+import Data.Proxy
28+import System.IO
29+import Network.HTTP.Client (newManager, defaultManagerSettings)
30+import Network.Wai.Handler.Warp
31+import Servant as S
32+import Servant.Client
33+import Servant.Auth as SA
34+import Servant.Auth.Server as SAS
35+import Control.Monad.IO.Class (liftIO)
36+import Data.Map as M
37+import Data.ByteString (ByteString)
38+
39+port :: Int
40+port = 3001
41+```
42+
43+
44+## Authentication
45+
46+Below is how we'll represent a user: usually user identifier is handy
47+to keep around, along with their role if
48+[role-based access control](https://en.wikipedia.org/wiki/Role-based_access_control)
49+is used, and other commonly needed information, such as an
50+organization identifier:
51+
52+```haskell
53+data AuthenticatedUser = AUser { auID :: Int
54+ , auOrgID :: Int
55+ } deriving (Show, Generic)
56+```
57+
58+The following instances are needed for JWT:
59+
60+```haskell
61+instance ToJSON AuthenticatedUser
62+instance FromJSON AuthenticatedUser
63+instance ToJWT AuthenticatedUser
64+instance FromJWT AuthenticatedUser
65+```
66+
67+We'll have to use a bit of imagination to pretend that the following
68+`Map` is a database connection pool:
69+
70+```haskell
71+type Login = ByteString
72+type Password = ByteString
73+type DB = Map (Login, Password) AuthenticatedUser
74+type Connection = DB
75+type Pool a = a
76+
77+initConnPool :: IO (Pool Connection)
78+initConnPool = pure $ fromList [ (("user", "pass"), AUser 1 1)
79+ , (("user2", "pass2"), AUser 2 1) ]
80+```
81+
82+See the "PostgreSQL connection pool" recipe for actual connection
83+pooling, and we proceed to an authentication function that would use
84+our improvised DB connection pool and credentials provided by a user:
85+
86+```haskell
87+authCheck :: Pool Connection
88+ -> BasicAuthData
89+ -> IO (AuthResult AuthenticatedUser)
90+authCheck connPool (BasicAuthData login password) = pure $
91+ maybe SAS.Indefinite Authenticated $ M.lookup (login, password) connPool
92+```
93+
94+**Warning**: make sure to use a proper password hashing function in
95+functions like this: see [bcrypt](https://en.wikipedia.org/wiki/Bcrypt),
96+[scrypt](https://en.wikipedia.org/wiki/Scrypt),
97+[pgcrypto](https://www.postgresql.org/docs/current/static/pgcrypto.html).
98+
99+Unlike `Servant.BasicAuth`, `Servant.Auth` uses `FromBasicAuthData`
100+type class for the authentication process itself. But since our
101+connection pool will be initialized elsewhere, we'll have to pass it
102+somehow: it can be done via a context entry and `BasicAuthCfg` type
103+family. We can actually pass a function at once, to make it a bit more
104+generic:
105+
106+
107+```haskell
108+type instance BasicAuthCfg = BasicAuthData -> IO (AuthResult AuthenticatedUser)
109+
110+instance FromBasicAuthData AuthenticatedUser where
111+ fromBasicAuthData authData authCheckFunction = authCheckFunction authData
112+```
113+
114+
115+## API
116+
117+Test API with a couple of endpoints:
118+
119+```haskell
120+type TestAPI = "foo" :> Capture "i" Int :> Get '[JSON] ()
121+ :<|> "bar" :> Get '[JSON] ()
122+```
123+
124+We'll use this for server-side functions, listing the allowed
125+authentication methods using the `Auth` combinator:
126+
127+```haskell
128+type TestAPIServer =
129+ Auth '[SA.JWT, SA.BasicAuth] AuthenticatedUser :> TestAPI
130+```
131+
132+But `Servant.Auth.Client` only supports JWT-based authentication, so
133+we'll have to use regular `Servant.BasicAuth` to derive client
134+functions that use basic access authentication:
135+
136+```haskell
137+type TestAPIClient = S.BasicAuth "test" AuthenticatedUser :> TestAPI
138+```
139+
140+
141+## Client
142+
143+Client code in this setting is the same as it would be with just
144+`Servant.BasicAuth`, using
145+[servant-client](https://hackage.haskell.org/package/servant-client):
146+
147+```haskell
148+testClient :: IO ()
149+testClient = do
150+ mgr <- newManager defaultManagerSettings
151+ let (foo :<|> _) = client (Proxy :: Proxy TestAPIClient)
152+ (BasicAuthData "name" "pass")
153+ res <- runClientM (foo 42)
154+ (ClientEnv mgr (BaseUrl Http "localhost" port ""))
155+ hPutStrLn stderr $ case res of
156+ Left err -> "Error: " ++ show err
157+ Right r -> "Success: " ++ show r
158+```
159+
160+
161+## Server
162+
163+Server code is slightly different -- we're getting `AuthResult` here:
164+
165+```haskell
166+server :: Server TestAPIServer
167+server (Authenticated user) = handleFoo :<|> handleBar
168+ where
169+ handleFoo :: Int -> Handler ()
170+ handleFoo n = liftIO $ hPutStrLn stderr $
171+ concat ["foo: ", show user, " / ", show n]
172+ handleBar :: Handler ()
173+ handleBar = liftIO testClient
174+```
175+
176+Catch-all for `BadPassword`, `NoSuchUser`, and `Indefinite`:
177+
178+```haskell
179+server _ = throwAll err401
180+```
181+
182+With `Servant.Auth`, we'll have to put both `CookieSettings` and
183+`JWTSettings` into context even if we're not using those, and we'll
184+put a partially applied `authCheck` function there as well, so that
185+`FromBasicAuthData` will be able to use it, while it will use our
186+connection pool. Otherwise it is similar to the usual way:
187+
188+```haskell
189+mkApp :: Pool Connection -> IO Application
190+mkApp connPool = do
191+ myKey <- generateKey
192+ let jwtCfg = defaultJWTSettings myKey
193+ authCfg = authCheck connPool
194+ cfg = jwtCfg :. defaultCookieSettings :. authCfg :. EmptyContext
195+ api = Proxy :: Proxy TestAPIServer
196+ pure $ serveWithContext api cfg server
197+```
198+
199+Finally, the main function:
200+
201+```haskell
202+main :: IO ()
203+main = do
204+ connPool <- initConnPool
205+ let settings =
206+ setPort port $
207+ setBeforeMainLoop (hPutStrLn stderr
208+ ("listening on port " ++ show port)) $
209+ defaultSettings
210+ runSettings settings =<< mkApp connPool
211+```
212+
213+
214+## Usage
215+
216+Now we can try it out with `curl`. First of all, let's ensure that it
217+fails with `err401` if we're not authenticated:
218+
219+```
220+$ curl -v 'http://localhost:3001/bar'
221+…
222+< HTTP/1.1 401 Unauthorized
223+```
224+
225+```
226+$ curl -v 'http://user:wrong_password@localhost:3001/bar'
227+…
228+< HTTP/1.1 401 Unauthorized
229+```
230+
231+Now let's see that basic HTTP authentication works, and that we get
232+JWTs:
233+
234+```
235+$ curl -v 'http://user:pass@localhost:3001/bar'
236+…
237+< HTTP/1.1 200 OK
238+…
239+< Set-Cookie: XSRF-TOKEN=lQE/sb1fW4rZ/FYUQZskI6RVRllG0CWZrQ0d3fXU4X0=; Path=/; Secure
240+< Set-Cookie: JWT-Cookie=eyJhbGciOiJIUzUxMiJ9.eyJkYXQiOnsiYXVPcmdJRCI6MSwiYXVJRCI6MX19.6ZQba-Co5Ul4wpmU34zXlI75wmasxDfaGRmO3BsOx-ONupX93OBfyYBCIJ3tbWMXKBVVqMDt0Pz-5CakyF2wng; Path=/; HttpOnly; Secure
241+```
242+
243+And authenticate using JWTs alone, using the token from `JWT-Cookie`:
244+
245+```
246+curl -v -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJkYXQiOnsiYXVPcmdJRCI6MSwiYXVJRCI6MX19.6ZQba-Co5Ul4wpmU34zXlI75wmasxDfaGRmO3BsOx-ONupX93OBfyYBCIJ3tbWMXKBVVqMDt0Pz-5CakyF2wng' 'http://localhost:3001/bar'
247+…
248+< HTTP/1.1 200 OK
249+```
250+
251+This program is available as a cabal project
252+[here](https://github.com/haskell-servant/servant/tree/master/doc/cookbook/jwt-and-basic-auth).
doc/cookbook/jwt-and-basic-auth/jwt-and-basic-auth.cabalnew file+36
@@ -0,0 +1,36 @@
1+name: cookbook-jwt-and-basic-auth
2+version: 0.0.1
3+synopsis: JWT and basic access authentication cookbook example
4+description: Using servant-auth to support both JWT-based and basic
5+ authentication.
6+homepage: http://haskell-servant.readthedocs.org/
7+license: BSD3
8+license-file: ../../../servant/LICENSE
9+author: Servant Contributors
10+maintainer: haskell-servant-maintainers@googlegroups.com
11+category: Servant
12+build-type: Simple
13+cabal-version: >=1.10
14+
15+executable cookbook-jwt-and-basic-auth
16+ if impl(ghc < 7.10.1)
17+ buildable: False
18+ main-is: JWTAndBasicAuth.lhs
19+ build-depends: base == 4.*
20+ , text >= 1.2
21+ , aeson >= 1.2
22+ , containers >= 0.5
23+ , servant
24+ , servant-client
25+ , servant-server
26+ , servant-auth
27+ , servant-auth-server
28+ , warp >= 3.2
29+ , wai >= 3.2
30+ , http-types >= 0.10
31+ , markdown-unlit >= 0.4
32+ , http-client >= 0.5
33+ , bytestring >= 0.10
34+ default-language: Haskell2010
35+ ghc-options: -Wall -pgmL markdown-unlit
36+ build-tool-depends: markdown-unlit:markdown-unlit
doc/cookbook/structuring-apis/StructuringApis.lhsnew file+195
@@ -0,0 +1,195 @@
1+# Structuring APIs
2+
3+In this recipe, we will see a few simple ways to
4+structure your APIs by splitting them up into smaller
5+"sub-APIs" or by sharing common structure between
6+different parts. Let's start with the usual throat
7+clearing.
8+
9+``` haskell
10+{-# LANGUAGE DataKinds #-}
11+{-# LANGUAGE DeriveGeneric #-}
12+{-# LANGUAGE TypeOperators #-}
13+{-# LANGUAGE KindSignatures #-}
14+import Data.Aeson
15+import GHC.Generics
16+import GHC.TypeLits
17+import Network.Wai.Handler.Warp
18+import Servant
19+```
20+
21+Our application will consist of three different
22+"sub-APIs", with a few endpoints in each of them.
23+Our global API is defined as follows.
24+
25+``` haskell
26+type API = FactoringAPI
27+ :<|> SimpleAPI "users" User UserId
28+ :<|> SimpleAPI "products" Product ProductId
29+```
30+
31+We simply join the three different parts with `:<|>`,
32+as if each sub-API was just a simple endpoint.
33+The first part, `FactoringAPI`, shows how we can
34+"factor out" combinators that are common to several
35+endpoints, just like we turn `a * b + a * c` into
36+`a * (b + c)` in algebra.
37+
38+``` haskell
39+-- Two endpoints:
40+-- - GET /x/<some 'Int'>[?y=<some 'Int'>]
41+-- - POST /x/<some 'Int'>
42+type FactoringAPI =
43+ "x" :> Capture "x" Int :>
44+ ( QueryParam "y" Int :> Get '[JSON] Int
45+ :<|> Post '[JSON] Int
46+ )
47+
48+{- this is equivalent to:
49+
50+type FactoringAPI' =
51+ "x" :> Capture "x" Int :> QueryParam "y" Int :> Get '[JSON] Int :<|>
52+ "x" :> Capture "x" Int :> Post '[JSON] Int
53+-}
54+```
55+
56+You can see that both endpoints start with a static
57+path fragment, `/"x"`, then capture some arbitrary
58+`Int` until they finally differ. Now, this also has
59+an effect on the server for such an API, and its type
60+in particular. While the server for `FactoringAPI'` would
61+be made of a function of type `Int -> Maybe Int -> Handler Int`
62+and a function of type `Int -> Handler Int` glued with `:<|>`,
63+a server for `FactoringAPI` (without the `'`) reflects the
64+"factorisation" and therefore, `Server FactoringAPI` is
65+`Int -> (Maybe Int -> Handler Int :<|> Handler Int)`. That is, the
66+server must be a function that takes an `Int` (the `Capture`) and
67+returns two values glued with `:<|>`, one of type `Maybe Int -> Handler Int`
68+and the other of type `Handler Int`. Let's provide such a server
69+implementation.
70+
71+_Note_: you can load this module in ghci and ask for the concrete
72+type that `Server FactoringAPI` "resolves to" by typing
73+`:kind! Server FactoringAPI`.
74+
75+``` haskell
76+factoringServer :: Server FactoringAPI
77+factoringServer x = getXY :<|> postX
78+
79+ where getXY Nothing = return x
80+ getXY (Just y) = return (x + y)
81+
82+ postX = return (x - 1)
83+```
84+
85+Next come the two sub-APIs defined in terms of this `SimpleAPI`
86+type, but with different parameters. That type is just a good old
87+Haskell type synonym that abstracts away a pretty common structure in
88+web services, where you have:
89+
90+ - one endpoint for listing a bunch of entities of some type
91+ - one endpoint for accessing the entity with a given identifier
92+ - one endpoint for creating a new entity
93+
94+There are many variants on this theme (endpoints for deleting,
95+paginated listings, etc). The simple definition below reproduces
96+such a structure, but instead of picking concrete types for
97+the entities and their identifiers, we simply let the user
98+of the type decide, by making those types parameters of
99+`SimpleAPI`. While we're at it, we'll put all our endpoints
100+under a common prefix that we also take as a parameter.
101+
102+``` haskell
103+-- Three endpoints:
104+-- - GET /<name>
105+-- - GET /<name>/<some 'i'>
106+-- - POST /<name>
107+type SimpleAPI (name :: Symbol) a i = name :>
108+ ( Get '[JSON] [a]
109+ :<|> Capture "id" i :> Get '[JSON] a
110+ :<|> ReqBody '[JSON] a :> Post '[JSON] NoContent
111+ )
112+```
113+
114+`Symbol` is the [kind](https://wiki.haskell.org/Kind)
115+of type-level strings, which is what servant uses for
116+representing static path fragments. We can even provide
117+a little helper function for creating a server for that API
118+given one handler for each endpoint as arguments.
119+
120+``` haskell
121+simpleServer
122+ :: Handler [a]
123+ -> (i -> Handler a)
124+ -> (a -> Handler NoContent)
125+ -> Server (SimpleAPI name a i)
126+simpleServer listAs getA postA =
127+ listAs :<|> getA :<|> postA
128+
129+{- you could alternatively provide such a definition
130+ but with the handlers running in another monad,
131+ or even an arbitrary one!
132+
133+simpleAPIServer
134+ :: m [a]
135+ -> (i -> m a)
136+ -> (a -> m NoContent)
137+ -> Server (SimpleAPI name a i) m
138+simpleAPIServer listAs getA postA =
139+ listAs :<|> getA :<|> postA
140+
141+ and use 'hoistServer' on the result of `simpleAPIServer`
142+ applied to your handlers right before you call `serve`.
143+-}
144+```
145+
146+We can use this to define servers for the user and product
147+related sections of the API.
148+
149+``` haskell
150+userServer :: Server (SimpleAPI "users" User UserId)
151+userServer = simpleServer
152+ (return [])
153+ (\userid -> return $
154+ if userid == 0
155+ then User "john" 64
156+ else User "everybody else" 10
157+ )
158+ (\_user -> return NoContent)
159+
160+productServer :: Server (SimpleAPI "products" Product ProductId)
161+productServer = simpleServer
162+ (return [])
163+ (\_productid -> return $ Product "Great stuff")
164+ (\_product -> return NoContent)
165+```
166+
167+Finally, some dummy types and the serving part.
168+
169+``` haskell
170+type UserId = Int
171+
172+data User = User { username :: String, age :: Int }
173+ deriving Generic
174+
175+instance FromJSON User
176+instance ToJSON User
177+
178+type ProductId = Int
179+
180+data Product = Product { productname :: String }
181+ deriving Generic
182+
183+instance FromJSON Product
184+instance ToJSON Product
185+
186+api :: Proxy API
187+api = Proxy
188+
189+main :: IO ()
190+main = run 8080 . serve api $
191+ factoringServer :<|> userServer :<|> productServer
192+```
193+
194+This program is available as a cabal project
195+[here](https://github.com/haskell-servant/servant/tree/master/doc/cookbook/structuring-apis).
doc/cookbook/structuring-apis/structuring-apis.cabalnew file+24
@@ -0,0 +1,24 @@
1+name: cookbook-structuring-apis
2+version: 0.1
3+synopsis: Example that shows how APIs can be structured
4+homepage: http://haskell-servant.readthedocs.org/
5+license: BSD3
6+license-file: ../../../servant/LICENSE
7+author: Servant Contributors
8+maintainer: haskell-servant-maintainers@googlegroups.com
9+build-type: Simple
10+cabal-version: >=1.10
11+
12+executable cookbook-structuring-apis
13+ if impl(ghc < 7.10.1)
14+ buildable: False
15+ main-is: StructuringApis.lhs
16+ build-depends: base == 4.*
17+ , aeson >= 1.2
18+ , servant
19+ , servant-server
20+ , warp >= 3.2
21+ , markdown-unlit >= 0.4
22+ default-language: Haskell2010
23+ ghc-options: -Wall -pgmL markdown-unlit
24+ build-tool-depends: markdown-unlit:markdown-unlit
doc/index.rst+1
@@ -19,5 +19,6 @@ All in a type-safe manner.
1919
2020 introduction.rst
2121 tutorial/index.rst
22+ cookbook/index.rst
2223 examples.md
2324 links.rst
nix/README.mdnew file+23
@@ -0,0 +1,23 @@
1+You can use the `shell.nix` from this directory
2+to build the servant packages or even the tutorial
3+or cookbook if you want to, optionally.
4+
5+Just the servant packages:
6+
7+``` sh
8+$ nix-shell nix/shell.nix
9+```
10+
11+Everything needed for the tutorial and the
12+cookbook too:
13+
14+``` sh
15+$ nix-shell nix/shell.nix --arg tutorial true
16+```
17+
18+The `shell.nix` file also supports specifying
19+a particular ghc version, e.g:
20+
21+``` sh
22+$ nix-shell nix/shell.nix --argstr compiler ghcHEAD
23+```
nix/shell.nixnew file+21
@@ -0,0 +1,21 @@
1+{ pkgs ? import <nixpkgs> {}
2+, compiler ? "ghc821"
3+, tutorial ? false
4+}:
5+
6+with pkgs;
7+
8+let
9+ ghc = haskell.packages.${compiler}.ghcWithPackages (_: []);
10+ docstuffs = python3.withPackages (ps: with ps; [ recommonmark sphinx sphinx_rtd_theme ]);
11+in
12+
13+stdenv.mkDerivation {
14+ name = "servant-dev";
15+ buildInputs = [ ghc zlib python3 wget ]
16+ ++ (if tutorial then [docstuffs postgresql] else []);
17+ shellHook = ''
18+ eval $(grep export ${ghc}/bin/ghc)
19+ export LD_LIBRARY_PATH="${zlib}/lib";
20+ '';
21+}