3The source for this tutorial section is a literate haskell file, so first we
4need to have some language extensions and imports:
7{-# LANGUAGE DataKinds #-}
8{-# LANGUAGE TypeOperators #-}
16Consider the following informal specification of an API:
18 > The endpoint at `/users` expects a GET request with query string parameter
19 > `sortby` whose value can be one of `age` or `name` and returns a
20 > list/array of JSON objects describing users, with fields `age`, `name`,
21 > `email`, `registration_date`".
23You *should* be able to formalize that. And then use the formalized version to
24get you much of the way towards writing a web app. And all the way towards
25getting some client libraries, and documentation, and more.
27How would we describe it with **servant**? An endpoint description is a good old
31type UserAPI = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User]
33data SortBy = Age | Name
43- `"users"` says that our endpoint will be accessible under `/users`;
44- `QueryParam "sortby" SortBy`, where `SortBy` is defined by `data SortBy = Age | Name`,
45 says that the endpoint has a query string parameter named `sortby`
46 whose value will be extracted as a value of type `SortBy`.
47- `Get '[JSON] [User]` says that the endpoint will be accessible through HTTP
48 GET requests, returning a list of users encoded as JSON. You will see
49 later how you can make use of this to make your data available under different
50 formats, the choice being made depending on the [Accept
51 header](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html) specified in
53- The `:>` operator that separates the various "combinators" just lets you
54 sequence static path fragments, URL captures and other combinators. The
55 ordering only matters for static path fragments and URL captures. `"users" :>
56 "list-all" :> Get '[JSON] [User]`, equivalent to `/users/list-all`, is
57 obviously not the same as `"list-all" :> "users" :> Get '[JSON] [User]`, which
58 is equivalent to `/list-all/users`. This means that sometimes `:>` is somehow
59 equivalent to `/`, but sometimes it just lets you chain another combinator.
61Tip: If your endpoint responds to `/` (the root path), just omit any combinators
62that introduce path segments. E.g. the following api has only one endpoint on `/`:
69We can also describe APIs with multiple endpoints by using the `:<|>`
70combinators. Here's an example:
73type UserAPI2 = "users" :> "list-all" :> Get '[JSON] [User]
74 :<|> "list-all" :> "users" :> Get '[JSON] [User]
77**servant** provides a fair amount of combinators out-of-the-box, but you can
78always write your own when you need it. Here's a quick overview of the most
79often needed the combinators that **servant** comes with.
85As you've already seen, you can use type-level strings (enabled with the
86`DataKinds` language extension) for static path fragments. Chaining
87them amounts to `/`-separating them in a URL.
90type UserAPI3 = "users" :> "list-all" :> "now" :> Get '[JSON] [User]
91 -- describes an endpoint reachable at:
92 -- /users/list-all/now
95### `Delete`, `Get`, `Patch`, `Post` and `Put`
97The `Get` combinator is defined in terms of the more general `Verb`:
99data Verb method (statusCode :: Nat) (contentType :: [*]) a
100type Get = Verb 'GET 200
103There are other predefined type synonyms for other common HTTP methods,
106type Delete = Verb 'DELETE 200
107type Patch = Verb 'PATCH 200
108type Post = Verb 'POST 200
109type Put = Verb 'PUT 200
112There are also variants that do not return a 200 status code, such
115type PostCreated = Verb 'POST 201
116type PostAccepted = Verb 'POST 202
119An endpoint always ends with a variant of the `Verb` combinator
120(unless you write your own combinators). Examples:
123type UserAPI4 = "users" :> Get '[JSON] [User]
124 :<|> "admins" :> Get '[JSON] [User]
129URL captures are segments of the path of a URL that are variable and whose actual value is
130captured and passed to the request handlers. In many web frameworks, you'll see
131it written as in `/users/:userid`, with that leading `:` denoting that `userid`
132is just some kind of variable name or placeholder. For instance, if `userid` is
133supposed to range over all integers greater or equal to 1, our endpoint will
134match requests made to `/users/1`, `/users/143` and so on.
136The `Capture` combinator in **servant** takes a (type-level) string representing
137the "name of the variable" and a type, which indicates the type we want to
138decode the "captured value" to.
141data Capture (s :: Symbol) a
142-- s :: Symbol just says that 's' must be a type-level string.
145In some web frameworks, you use regexes for captures. We use a
146[`FromHttpApiData`](https://hackage.haskell.org/package/http-api-data/docs/Web-HttpApiData.html#t:FromHttpApiData)
147class, which the captured value must be an instance of.
152type UserAPI5 = "user" :> Capture "userid" Integer :> Get '[JSON] User
153 -- equivalent to 'GET /user/:userid'
154 -- except that we explicitly say that "userid"
155 -- must be an integer
157 :<|> "user" :> Capture "userid" Integer :> DeleteNoContent '[JSON] NoContent
158 -- equivalent to 'DELETE /user/:userid'
161In the second case, `DeleteNoContent` specifies a 204 response code,
162`JSON` specifies the content types on which the handler will match,
163and `NoContent` says that the response will always be empty.
165### `QueryParam`, `QueryParams`, `QueryFlag`
167`QueryParam`, `QueryParams` and `QueryFlag` are about parameters in the query string,
168i.e., those parameters that come after the question mark
169(`?`) in URLs, like `sortby` in `/users?sortby=age`, whose value is
170set to `age`. `QueryParams` lets you specify that the query parameter
171is actually a list of values, which can be specified using
172`?param=value1¶m=value2`. This represents a list of values
173composed of `value1` and `value2`. `QueryFlag` lets you specify a
174boolean-like query parameter where a client isn't forced to specify a
175value. The absence or presence of the parameter's name in the query
176string determines whether the parameter is considered to have the
177value `True` or `False`. For instance, `/users?active` would list only
178active users whereas `/users` would list them all.
180Here are the corresponding data type declarations:
183data QueryParam (sym :: Symbol) a
184data QueryParams (sym :: Symbol) a
185data QueryFlag (sym :: Symbol)
191type UserAPI6 = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User]
192 -- equivalent to 'GET /users?sortby={age, name}'
196Again, your handlers don't have to deserialize these things (into, for example,
197a `SortBy`). **servant** takes care of it.
201Each HTTP request can carry some additional data that the server can use in its
202*body*, and this data can be encoded in any format -- as long as the server
203understands it. This can be used for example for an endpoint for creating new
204users: instead of passing each field of the user as a separate query string
205parameter or something dirty like that, we can group all the data into a JSON
206object. This has the advantage of supporting nested objects.
208**servant**'s `ReqBody` combinator takes a list of content types in which the
209data encoded in the request body can be represented and the type of that data.
210And, as you might have guessed, you don't have to check the content type
211header, and do the deserialization yourself. We do it for you. And return `Bad
212Request` or `Unsupported Content Type` as appropriate.
214Here's the data type declaration for it:
217data ReqBody (contentTypes :: [*]) a
223type UserAPI7 = "users" :> ReqBody '[JSON] User :> Post '[JSON] User
224 -- - equivalent to 'POST /users' with a JSON object
225 -- describing a User in the request body
226 -- - returns a User encoded in JSON
228 :<|> "users" :> Capture "userid" Integer
229 :> ReqBody '[JSON] User
231 -- - equivalent to 'PUT /users/:userid' with a JSON
232 -- object describing a User in the request body
233 -- - returns a User encoded in JSON
238Request headers are used for various purposes, from caching to carrying
239auth-related data. They consist of a header name and an associated value. An
240example would be `Accept: application/json`.
242The `Header` combinator in **servant** takes a type-level string for the header
243name and the type to which we want to decode the header's value (from some
244textual representation), as illustrated below:
247data Header (sym :: Symbol) a
250Here's an example where we declare that an endpoint makes use of the
251`User-Agent` header which specifies the name of the software/library used by
252the client to send the request.
255type UserAPI8 = "users" :> Header "User-Agent" Text :> Get '[JSON] [User]
260So far, whenever we have used a combinator that carries a list of content
261types, we've always specified `'[JSON]`. However, **servant** lets you use several
262content types, and also lets you define your own content types.
264Four content types are provided out-of-the-box by the core **servant** package:
265`JSON`, `PlainText`, `FormUrlEncoded` and `OctetStream`. If for some obscure
266reason you wanted one of your endpoints to make your user data available under
267those 4 formats, you would write the API type as below:
270type UserAPI9 = "users" :> Get '[JSON, PlainText, FormUrlEncoded, OctetStream] [User]
273(There are other packages that provide other content types. For example
274**servant-lucid** and **servant-blaze** allow to generate html pages (using
275**lucid** and **blaze-html**) and both come with a content type for html.)
277We will further explain how these content types and your data types can play
278together in the [section about serving an API](Server.html).
280### Response `Headers`
282Just like an HTTP request, the response generated by a webserver can carry
283headers too. **servant** provides a `Headers` combinator that carries a list of
284`Header` types and can be used by simply wrapping the "return type" of an endpoint
288data Headers (ls :: [*]) a
291If you want to describe an endpoint that returns a "User-Count" header in each
292response, you could write it as below:
295type UserAPI10 = "users" :> Get '[JSON] (Headers '[Header "User-Count" Integer] [User])
298### Basic Authentication
300Once you've established the basic routes and semantics of your API, it's time
301to consider protecting parts of it. Authentication and authorization are broad
302and nuanced topics; as servant began to explore this space we started small
303with one of HTTP's earliest authentication schemes: [Basic
304Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication).
306When protecting endpoints with basic authentication, we need to specify two items:
3081. The **realm** of authentication as per the Basic Authentication spec.
3092. The datatype returned by the server after authentication is verified. This
310 is usually a `User` or `Customer` type datatype.
312With those two items in mind, *servant* provides the following combinator:
315data BasicAuth (realm :: Symbol) (userData :: *)
318Which is used like so:
322 = UserAPI -- this is public
323 :<|> BasicAuth "my-realm" User :> UserAPI2 -- this is protected by auth
326### Interoperability with `wai`: `Raw`
328Finally, we also include a combinator named `Raw` that provides an escape hatch
329to the underlying low-level web library `wai`. It can be used when
330you want to plug a [wai `Application`](http://hackage.haskell.org/package/wai)
334type UserAPI11 = "users" :> Get '[JSON] [User]
338 -- requests to anything else than /users
339 -- go here, where the server will try to
340 -- find a file with the right name
344One example for this is if you want to serve a directory of static files along
345with the rest of your API. But you can plug in everything that is an
346`Application`, e.g. a whole web application written in any of the web
347frameworks that support `wai`.
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