on 8b1bf02 help sign in
servant/doc/tutorial/api-type.lhs
333 lines 12 kB history raw
1---
2title: A web API as a type
3toc: true
4---
6The source for this tutorial section is a literate haskell file, so first we
7need to have some language extensions and imports:
9``` haskell
10{-# LANGUAGE DataKinds #-}
11{-# LANGUAGE TypeOperators #-}
13module ApiType where
15import Data.Text
16import Servant.API
17```
19Consider the following informal specification of an API:
21 > The endpoint at `/users` expects a GET request with query string parameter
22 > `sortby` whose value can be one of `age` or `name` and returns a
23 > list/array of JSON objects describing users, with fields `age`, `name`,
24 > `email`, `registration_date`".
26You *should* be able to formalize that. And then use the formalized version to
27get you much of the way towards writing a web app. And all the way towards
28getting some client libraries, and documentation (and in the future, who knows
29- tests, HATEOAS, ...).
31How would we describe it with servant? As mentioned earlier, an endpoint
32description is a good old Haskell **type**:
34``` haskell
35type UserAPI = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User]
37data SortBy = Age | Name
39data User = User {
40 name :: String,
41 age :: Int
43```
45Let's break that down:
47- `"users"` says that our endpoint will be accessible under `/users`;
48- `QueryParam "sortby" SortBy`, where `SortBy` is defined by `data SortBy = Age
49| Name`, says that the endpoint has a query string parameter named `sortby`
50whose value will be extracted as a value of type `SortBy`.
51- `Get '[JSON] [User]` says that the endpoint will be accessible through HTTP
52GET requests, returning a list of users encoded as JSON. You will see
53later how you can make use of this to make your data available under different
54formats, the choice being made depending on the [Accept
55header](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html) specified in
56the client's request.
57- the `:>` operator that separates the various "combinators" just lets you
58sequence static path fragments, URL captures and other combinators. The
59ordering only matters for static path fragments and URL captures. `"users" :>
60"list-all" :> Get '[JSON] [User]`, equivalent to `/users/list-all`, is
61obviously not the same as `"list-all" :> "users" :> Get '[JSON] [User]`, which
62is equivalent to `/list-all/users`. This means that sometimes `:>` is somehow
63equivalent to `/`, but sometimes it just lets you chain another combinator.
65We can also describe APIs with multiple endpoints by using the `:<|>`
66combinators. Here's an example:
68``` haskell
69type UserAPI2 = "users" :> "list-all" :> Get '[JSON] [User]
70 :<|> "list-all" :> "users" :> Get '[JSON] [User]
71```
73*servant* provides a fair amount of combinators out-of-the-box, but you can
74always write your own when you need it. Here's a quick overview of all the
75combinators that servant comes with.
77Combinators
78===========
80Static strings
81--------------
83As you've already seen, you can use type-level strings (enabled with the
84`DataKinds` language extension) for static path fragments. Chaining
85them amounts to `/`-separating them in a URL.
87``` haskell
88type UserAPI3 = "users" :> "list-all" :> "now" :> Get '[JSON] [User]
89 -- describes an endpoint reachable at:
90 -- /users/list-all/now
91```
93`Delete`, `Get`, `Patch`, `Post` and `Put`
94------------------------------------------
96These 5 combinators are very similar except that they each describe a
97different HTTP method. This is how they're declared
99``` haskell ignore
100data Delete (contentTypes :: [*]) a
101data Get (contentTypes :: [*]) a
102data Patch (contentTypes :: [*]) a
103data Post (contentTypes :: [*]) a
104data Put (contentTypes :: [*]) a
105```
107An endpoint ends with one of the 5 combinators above (unless you write your
108own). Examples:
110``` haskell
111type UserAPI4 = "users" :> Get '[JSON] [User]
112 :<|> "admins" :> Get '[JSON] [User]
113```
115`Capture`
116---------
118URL captures are parts of the URL that are variable and whose actual value is
119captured and passed to the request handlers. In many web frameworks, you'll see
120it written as in `/users/:userid`, with that leading `:` denoting that `userid`
121is just some kind of variable name or placeholder. For instance, if `userid` is
122supposed to range over all integers greater or equal to 1, our endpoint will
123match requests made to `/users/1`, `/users/143` and so on.
125The `Capture` combinator in servant takes a (type-level) string representing
126the "name of the variable" and a type, which indicates the type we want to
127decode the "captured value" to.
129``` haskell ignore
130data Capture (s :: Symbol) a
131-- s :: Symbol just says that 's' must be a type-level string.
132```
134In some web frameworks, you use regexes for captures. We use a
135[`FromText`](https://hackage.haskell.org/package/servant/docs/Servant-Common-Text.html#t:FromText)
136class, which the captured value must be an instance of.
138Examples:
140``` haskell
141type UserAPI5 = "user" :> Capture "userid" Integer :> Get '[JSON] User
142 -- equivalent to 'GET /user/:userid'
143 -- except that we explicitly say that "userid"
144 -- must be an integer
146 :<|> "user" :> Capture "userid" Integer :> Delete '[] ()
147 -- equivalent to 'DELETE /user/:userid'
148```
150`QueryParam`, `QueryParams`, `QueryFlag`, `MatrixParam`, `MatrixParams` and `MatrixFlag`
151----------------------------------------------------------------------------------------
153`QueryParam`, `QueryParams` and `QueryFlag` are about query string
154parameters, i.e., those parameters that come after the question mark
155(`?`) in URLs, like `sortby` in `/users?sortby=age`, whose value is
156set to `age`. `QueryParams` lets you specify that the query parameter
157is actually a list of values, which can be specified using
158`?param[]=value1&param[]=value2`. This represents a list of values
159composed of `value1` and `value2`. `QueryFlag` lets you specify a
160boolean-like query parameter where a client isn't forced to specify a
161value. The absence or presence of the parameter's name in the query
162string determines whether the parameter is considered to have the
163value `True` or `False`. For instance, `/users?active` would list only
164active users whereas `/users` would list them all.
166Here are the corresponding data type declarations:
168``` haskell ignore
169data QueryParam (sym :: Symbol) a
170data QueryParams (sym :: Symbol) a
171data QueryFlag (sym :: Symbol)
172```
174[Matrix parameters](http://www.w3.org/DesignIssues/MatrixURIs.html)
175are similar to query string parameters, but they can appear anywhere
176in the paths (click the link for more details). A URL with matrix
177parameters in it looks like `/users;sortby=age`, as opposed to
178`/users?sortby=age` with query string parameters. The big advantage is
179that they are not necessarily at the end of the URL. You could have
180`/users;active=true;registered_after=2005-01-01/locations` to get
181geolocation data about users whom are still active and registered
182after *January 1st, 2005*.
184Corresponding data type declarations below.
186``` haskell ignore
187data MatrixParam (sym :: Symbol) a
188data MatrixParams (sym :: Symbol) a
189data MatrixFlag (sym :: Symbol)
190```
192Examples:
194``` haskell
195type UserAPI6 = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User]
196 -- equivalent to 'GET /users?sortby={age, name}'
198 :<|> "users" :> MatrixParam "sortby" SortBy :> Get '[JSON] [User]
199 -- equivalent to 'GET /users;sortby={age, name}'
200```
202Again, your handlers don't have to deserialize these things (into, for example,
203a `SortBy`). *servant* takes care of it.
205`ReqBody`
206---------
208Each HTTP request can carry some additional data that the server can use in its
209*body*, and this data can be encoded in any format -- as long as the server
210understands it. This can be used for example for an endpoint for creating new
211users: instead of passing each field of the user as a separate query string
212parameter or something dirty like that, we can group all the data into a JSON
213object. This has the advantage of supporting nested objects.
215*servant*'s `ReqBody` combinator takes a list of content types in which the
216data encoded in the request body can be represented and the type of that data.
217And, as you might have guessed, you don't have to check the content-type
218header, and do the deserialization yourself. We do it for you. And return `Bad
219Request` or `Unsupported Content Type` as appropriate.
221Here's the data type declaration for it:
223``` haskell ignore
224data ReqBody (contentTypes :: [*]) a
225```
227Examples:
229``` haskell
230type UserAPI7 = "users" :> ReqBody '[JSON] User :> Post '[JSON] User
231 -- - equivalent to 'POST /users' with a JSON object
232 -- describing a User in the request body
233 -- - returns a User encoded in JSON
235 :<|> "users" :> Capture "userid" Integer
236 :> ReqBody '[JSON] User
237 :> Put '[JSON] User
238 -- - equivalent to 'PUT /users/:userid' with a JSON
239 -- object describing a User in the request body
240 -- - returns a User encoded in JSON
241```
243Request `Header`s
244-----------------
246Request headers are used for various purposes, from caching to carrying
247auth-related data. They consist of a header name and an associated value. An
248example would be `Accept: application/json`.
250The `Header` combinator in servant takes a type-level string for the header
251name and the type to which we want to decode the header's value (from some
252textual representation), as illustrated below:
254``` haskell ignore
255data Header (sym :: Symbol) a
256```
258Here's an example where we declare that an endpoint makes use of the
259`User-Agent` header which specifies the name of the software/library used by
260the client to send the request.
262``` haskell
263type UserAPI8 = "users" :> Header "User-Agent" Text :> Get '[JSON] [User]
264```
266Content types
267-------------
269So far, whenever we have used a combinator that carries a list of content
270types, we've always specified `'[JSON]`. However, *servant* lets you use several
271content types, and also lets you define your own content types.
273Four content-types are provided out-of-the-box by the core *servant* package:
274`JSON`, `PlainText`, `FormUrlEncoded` and `OctetStream`. If for some obscure
275reason you wanted one of your endpoints to make your user data available under
276those 4 formats, you would write the API type as below:
278``` haskell
279type UserAPI9 = "users" :> Get '[JSON, PlainText, FormUrlEncoded, OctetStream] [User]
280```
282We also provide an HTML content-type, but since there's no single library
283that everyone uses, we decided to release 2 packages, *servant-lucid* and
284*servant-blaze*, to provide HTML encoding of your data.
286We will further explain how these content types and your data types can play
287together in the [section about serving an API](/tutorial/server.html).
289Response `Headers`
290------------------
292Just like an HTTP request, the response generated by a webserver can carry
293headers too. *servant* provides a `Headers` combinator that carries a list of
294`Header` and can be used by simply wrapping the "return type" of an endpoint
295with it.
297``` haskell ignore
298data Headers (ls :: [*]) a
299```
301If you want to describe an endpoint that returns a "User-Count" header in each
302response, you could write it as below:
304``` haskell
305type UserAPI10 = "users" :> Get '[JSON] (Headers '[Header "User-Count" Integer] [User])
306```
308Interoperability with other WAI `Application`s: `Raw`
309-----------------------------------------------------
311Finally, we also include a combinator named `Raw` that can be used for two reasons:
313- You want to serve static files from a given directory. In that case you can just say:
315``` haskell
316type UserAPI11 = "users" :> Get '[JSON] [User]
317 -- a /users endpoint
319 :<|> Raw
320 -- requests to anything else than /users
321 -- go here, where the server will try to
322 -- find a file with the right name
323 -- at the right path
324```
326- You more generally want to plug a [WAI `Application`](http://hackage.haskell.org/package/wai)
327into your webservice. Static file serving is a specific example of that. The API type would look the
328same as above though. (You can even combine *servant* with other web frameworks
329this way!)
331<div style="text-align: center;">
332 <a href="/tutorial/server.html">Next page: Serving an API</a>
333</div>