2title: A web API as a type
6The source for this tutorial section is a literate haskell file, so first we
7need to have some language extensions and imports:
9> {-# LANGUAGE DataKinds #-}
10> {-# LANGUAGE TypeOperators #-}
17Consider the following informal specification of an API:
19 > The endpoint at `/users` expects a GET request with query string parameter
20 > `sortby` whose value can be one of `age` or `name` and returns a
21 > list/array of JSON objects describing users, with fields `age`, `name`,
22 > `email`, `registration_date`".
24You *should* be able to formalize that. And then use the formalized version to
25get you much of the way towards writing a web app. And all the way towards
26getting some client libraries, and documentation (and in the future, who knows
27- tests, HATEOAS, ...).
29How would we describe it with servant? As mentioned earlier, an endpoint
30description is a good old Haskell **type**:
32> type UserAPI = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User]
34> data 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
45| Name`, says that the endpoint has a query string parameter named `sortby`
46whose value will be extracted as a value of type `SortBy`.
47- `Get '[JSON] [User]` says that the endpoint will be accessible through HTTP
48GET requests, returning a list of users encoded as JSON. You will see
49later how you can make use of this to make your data available under different
50formats, the choice being made depending on the [Accept
51header](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html) specified in
53- the `:>` operator that separates the various "combinators" just lets you
54sequence static path fragments, URL captures and other combinators. The
55ordering only matters for static path fragments and URL captures. `"users" :>
56"list-all" :> Get '[JSON] [User]`, equivalent to `/users/list-all`, is
57obviously not the same as `"list-all" :> "users" :> Get '[JSON] [User]`, which
58is equivalent to `/list-all/users`. This means that sometimes `:>` is somehow
59equivalent to `/`, but sometimes it just lets you chain another combinator.
61We can also describe APIs with multiple endpoints by using the `:<|>`
62combinators. Here's an example:
64> type UserAPI2 = "users" :> "list-all" :> Get '[JSON] [User]
65> :<|> "list-all" :> "users" :> Get '[JSON] [User]
67*servant* provides a fair amount of combinators out-of-the-box, but you can
68always write your own when you need it. Here's a quick overview of all the
69combinators that servant comes with.
77As you've already seen, you can use type-level strings (enabled with the
78`DataKinds` language extension) for static path fragments. Chaining
79them amounts to `/`-separating them in a URL.
81> type UserAPI3 = "users" :> "list-all" :> "now" :> Get '[JSON] [User]
82> -- describes an endpoint reachable at:
83> -- /users/list-all/now
85`Delete`, `Get`, `Patch`, `Post` and `Put`
86------------------------------------------
88These 5 combinators are very similar except that they each describe a
89different HTTP method. This is how they're declared
92data Delete (contentTypes :: [*]) a
93data Get (contentTypes :: [*]) a
94data Patch (contentTypes :: [*]) a
95data Post (contentTypes :: [*]) a
96data Put (contentTypes :: [*]) a
99An endpoint ends with one of the 5 combinators above (unless you write your
102> type UserAPI4 = "users" :> Get '[JSON] [User]
103> :<|> "admins" :> Get '[JSON] [User]
108URL captures are parts of the URL that are variable and whose actual value is
109captured and passed to the request handlers. In many web frameworks, you'll see
110it written as in `/users/:userid`, with that leading `:` denoting that `userid`
111is just some kind of variable name or placeholder. For instance, if `userid` is
112supposed to range over all integers greater or equal to 1, our endpoint will
113match requests made to `/users/1`, `/users/143` and so on.
115The `Capture` combinator in servant takes a (type-level) string representing
116the "name of the variable" and a type, which indicates the type we want to
117decode the "captured value" to.
120data Capture (s :: Symbol) a
121-- s :: Symbol just says that 's' must be a type-level string.
124In some web frameworks, you use regexes for captures. We use a
125[`FromText`](https://hackage.haskell.org/package/servant/docs/Servant-Common-Text.html#t:FromText)
126class, which the captured value must be an instance of.
130> type UserAPI5 = "user" :> Capture "userid" Integer :> Get '[JSON] User
131> -- equivalent to 'GET /user/:userid'
132> -- except that we explicitly say that "userid"
133> -- must be an integer
135> :<|> "user" :> Capture "userid" Integer :> Delete '[] ()
136> -- equivalent to 'DELETE /user/:userid'
138`QueryParam`, `QueryParams`, `QueryFlag`, `MatrixParam`, `MatrixParams` and `MatrixFlag`
139----------------------------------------------------------------------------------------
141`QueryParam`, `QueryParams` and `QueryFlag` are about query string
142parameters, i.e., those parameters that come after the question mark
143(`?`) in URLs, like `sortby` in `/users?sortby=age`, whose value is
144set to `age`. `QueryParams` lets you specify that the query parameter
145is actually a list of values, which can be specified using
146`?param[]=value1¶m[]=value2`. This represents a list of values
147composed of `value1` and `value2`. `QueryFlag` lets you specify a
148boolean-like query parameter where a client isn't forced to specify a
149value. The absence or presence of the parameter's name in the query
150string determines whether the parameter is considered to have the
151value `True` or `False`. For instance, `/users?active` would list only
152active users whereas `/users` would list them all.
154Here are the corresponding data type declarations:
157data QueryParam (sym :: Symbol) a
158data QueryParams (sym :: Symbol) a
159data QueryFlag (sym :: Symbol)
162[Matrix parameters](http://www.w3.org/DesignIssues/MatrixURIs.html)
163are similar to query string parameters, but they can appear anywhere
164in the paths (click the link for more details). A URL with matrix
165parameters in it looks like `/users;sortby=age`, as opposed to
166`/users?sortby=age` with query string parameters. The big advantage is
167that they are not necessarily at the end of the URL. You could have
168`/users;active=true;registered_after=2005-01-01/locations` to get
169geolocation data about users whom are still active and registered
170after *January 1st, 2005*.
172Corresponding data type declarations below.
175data MatrixParam (sym :: Symbol) a
176data MatrixParams (sym :: Symbol) a
177data MatrixFlag (sym :: Symbol)
182> type UserAPI6 = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User]
183> -- equivalent to 'GET /users?sortby={age, name}'
185> :<|> "users" :> MatrixParam "sortby" SortBy :> Get '[JSON] [User]
186> -- equivalent to 'GET /users;sortby={age, name}'
188Again, your handlers don't have to deserialize these things (into, for example,
189a `SortBy`). *servant* takes care of it.
194Each HTTP request can carry some additional data that the server can use in its
195*body*, and this data can be encoded in any format -- as long as the server
196understands it. This can be used for example for an endpoint for creating new
197users: instead of passing each field of the user as a separate query string
198parameter or something dirty like that, we can group all the data into a JSON
199object. This has the advantage of supporting nested objects.
201*servant*'s `ReqBody` combinator takes a list of content types in which the
202data encoded in the request body can be represented and the type of that data.
203And, as you might have guessed, you don't have to check the content-type
204header, and do the deserialization yourself. We do it for you. And return `Bad
205Request` or `Unsupported Content Type` as appropriate.
207Here's the data type declaration for it:
210data ReqBody (contentTypes :: [*]) a
215> type UserAPI7 = "users" :> ReqBody '[JSON] User :> Post '[JSON] User
216> -- - equivalent to 'POST /users' with a JSON object
217> -- describing a User in the request body
218> -- - returns a User encoded in JSON
220> :<|> "users" :> Capture "userid" Integer
221> :> ReqBody '[JSON] User
223> -- - equivalent to 'PUT /users/:userid' with a JSON
224> -- object describing a User in the request body
225> -- - returns a User encoded in JSON
230Request headers are used for various purposes, from caching to carrying
231auth-related data. They consist of a header name and an associated value. An
232example would be `Accept: application/json`.
234The `Header` combinator in servant takes a type-level string for the header
235name and the type to which we want to decode the header's value (from some
236textual representation), as illustrated below:
239data Header (sym :: Symbol) a
242Here's an example where we declare that an endpoint makes use of the
243`User-Agent` header which specifies the name of the software/library used by
244the client to send the request.
246> type UserAPI8 = "users" :> Header "User-Agent" Text :> Get '[JSON] [User]
251So far, whenever we have used a combinator that carries a list of content
252types, we've always specified `'[JSON]`. However, *servant* lets you use several
253content types, and also lets you define your own content types.
255Four content-types are provided out-of-the-box by the core *servant* package:
256`JSON`, `PlainText`, `FormUrlEncoded` and `OctetStream`. If for some obscure
257reason you wanted one of your endpoints to make your user data available under
258those 4 formats, you would write the API type as below:
260> type UserAPI9 = "users" :> Get '[JSON, PlainText, FormUrlEncoded, OctetStream] [User]
262We also provide an HTML content-type, but since there's no single library
263that everyone uses, we decided to release 2 packages, *servant-lucid* and
264*servant-blaze*, to provide HTML encoding of your data.
266We will further explain how these content types and your data types can play
267together in the [section about serving an API](/tutorial/server.html).
272Just like an HTTP request, the response generated by a webserver can carry
273headers too. *servant* provides a `Headers` combinator that carries a list of
274`Header` and can be used by simply wrapping the "return type" of an endpoint
278data Headers (ls :: [*]) a
281If you want to describe an endpoint that returns a "User-Count" header in each
282response, you could write it as below:
284> type UserAPI10 = "users" :> Get '[JSON] (Headers '[Header "User-Count" Integer] [User])
286Interoperability with other WAI `Application`s: `Raw`
287-----------------------------------------------------
289Finally, we also include a combinator named `Raw` that can be used for two reasons:
291- You want to serve static files from a given directory. In that case you can just say:
293> type UserAPI11 = "users" :> Get '[JSON] [User]
294> -- a /users endpoint
297> -- requests to anything else than /users
298> -- go here, where the server will try to
299> -- find a file with the right name
300> -- at the right path
302- You more generally want to plug a [WAI `Application`](http://hackage.haskell.org/package/wai)
303into your webservice. Static file serving is a specific example of that. The API type would look the
304same as above though. (You can even combine *servant* with other web frameworks
307<div style="text-align: center;">
308 <a href="/tutorial/server.html">Next page: Serving an API</a>
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