on 78034cd help sign in
servant/doc/tutorial/ApiType.lhs
418 lines 15 kB history raw
1# A web API as a type
3The source for this tutorial section is a literate haskell file, so first we
4need to have some language extensions and imports:
6``` haskell
7{-# LANGUAGE DataKinds #-}
8{-# LANGUAGE TypeOperators #-}
10module ApiType where
12import Data.Text
13import Data.Time (UTCTime)
14import Servant.API
15```
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 more.
28How would we describe it with **servant**? An endpoint description is a good old
29Haskell **type**:
31``` haskell
32type UserAPI = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User]
34data SortBy = Age | Name
36data User = User {
37 name :: String,
38 age :: Int,
39 email :: String,
40 registration_date :: UTCTime
42```
44Let's break that down:
46- `"users"` says that our endpoint will be accessible under `/users`;
47- `QueryParam "sortby" SortBy`, where `SortBy` is defined by `data SortBy = Age | Name`,
48 says that the endpoint has a query string parameter named `sortby`
49 whose value will be extracted as a value of type `SortBy`.
50- `Get '[JSON] [User]` says that the endpoint will be accessible through HTTP
51 GET requests, returning a list of users encoded as JSON. You will see
52 later how you can make use of this to make your data available under different
53 formats, the choice being made depending on the [Accept
54 header](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html) specified in
55 the client's request.
56- The `:>` operator that separates the various "combinators" just lets you
57 sequence static path fragments, URL captures and other combinators. The
58 ordering only matters for static path fragments and URL captures. `"users" :>
59 "list-all" :> Get '[JSON] [User]`, equivalent to `/users/list-all`, is
60 obviously not the same as `"list-all" :> "users" :> Get '[JSON] [User]`, which
61 is equivalent to `/list-all/users`. This means that sometimes `:>` is somehow
62 equivalent to `/`, but sometimes it just lets you chain another combinator.
64Tip: If your endpoint responds to `/` (the root path), just omit any combinators
65that introduce path segments. E.g. the following api has only one endpoint on `/`:
67``` haskell
68type RootEndpoint =
69 Get '[JSON] User
70```
72We can also describe APIs with multiple endpoints by using the `:<|>`
73combinators. Here's an example:
75``` haskell
76type UserAPI2 = "users" :> "list-all" :> Get '[JSON] [User]
77 :<|> "list-all" :> "users" :> Get '[JSON] [User]
78```
80**servant** provides a fair amount of combinators out-of-the-box, but you can
81always write your own when you need it. Here's a quick overview of the most
82often needed combinators that **servant** comes with.
84## Combinators
86### Static strings
88As you've already seen, you can use type-level strings (enabled with the
89`DataKinds` language extension) for static path fragments. Chaining
90them amounts to `/`-separating them in a URL.
92``` haskell
93type UserAPI3 = "users" :> "list-all" :> "now" :> Get '[JSON] [User]
94 -- describes an endpoint reachable at:
95 -- /users/list-all/now
96```
98### `Delete`, `Get`, `Patch`, `Post` and `Put`
100The `Get` combinator is defined in terms of the more general `Verb`:
101``` haskell ignore
102data Verb method (statusCode :: Nat) (contentType :: [*]) a
103type Get = Verb 'GET 200
104```
106There are other predefined type synonyms for other common HTTP methods,
107such as e.g.:
108``` haskell ignore
109type Delete = Verb 'DELETE 200
110type Patch = Verb 'PATCH 200
111type Post = Verb 'POST 200
112type Put = Verb 'PUT 200
113```
115There are also variants that do not return a 200 status code, such
116as for example:
117``` haskell ignore
118type PostCreated = Verb 'POST 201
119type PostAccepted = Verb 'POST 202
120```
122An endpoint always ends with a variant of the `Verb` combinator
123(unless you write your own combinators). Examples:
125``` haskell
126type UserAPI4 = "users" :> Get '[JSON] [User]
127 :<|> "admins" :> Get '[JSON] [User]
128```
130### `StreamGet` and `StreamPost`
132*Note*: Streaming has changed considerably in `servant-0.15`.
134The `StreamGet` and `StreamPost` combinators are defined in terms of the more general `Stream`
136``` haskell ignore
137data Stream (method :: k1) (status :: Nat) (framing :: *) (contentType :: *) (a :: *)
139type StreamGet = Stream 'GET 200
140type StreamPost = Stream 'POST 200
141```
143These describe endpoints that return a stream of values rather than just a
144single value. They not only take a single content type as a parameter, but also
145a framing strategy -- this specifies how the individual results are delineated
146from one another in the stream. The three standard strategies given with
147Servant are `NewlineFraming`, `NetstringFraming` and `NoFraming`, but others
148can be written to match other protocols.
150### `Capture`
152URL captures are segments of the path of a URL that are variable and whose actual value is
153captured and passed to the request handlers. In many web frameworks, you'll see
154it written as in `/users/:userid`, with that leading `:` denoting that `userid`
155is just some kind of variable name or placeholder. For instance, if `userid` is
156supposed to range over all integers greater or equal to 1, our endpoint will
157match requests made to `/users/1`, `/users/143` and so on.
159The `Capture` combinator in **servant** takes a (type-level) string representing
160the "name of the variable" and a type, which indicates the type we want to
161decode the "captured value" to.
163``` haskell ignore
164data Capture (s :: Symbol) a
165-- s :: Symbol just says that 's' must be a type-level string.
166```
168In some web frameworks, you use regexes for captures. We use a
169[`FromHttpApiData`](https://hackage.haskell.org/package/http-api-data/docs/Web-HttpApiData.html#t:FromHttpApiData)
170class, which the captured value must be an instance of.
172Examples:
174``` haskell
175type UserAPI5 = "user" :> Capture "userid" Integer :> Get '[JSON] User
176 -- equivalent to 'GET /user/:userid'
177 -- except that we explicitly say that "userid"
178 -- must be an integer
180 :<|> "user" :> Capture "userid" Integer :> DeleteNoContent
181 -- equivalent to 'DELETE /user/:userid'
182```
184In the second case, `DeleteNoContent` specifies a 204 response code
185and that the response will always be empty.
187### `QueryParam`, `QueryParams`, `QueryFlag`
189`QueryParam`, `QueryParams` and `QueryFlag` are about parameters in the query string,
190i.e., those parameters that come after the question mark
191(`?`) in URLs, like `sortby` in `/users?sortby=age`, whose value is
192set to `age`. `QueryParams` lets you specify that the query parameter
193is actually a list of values, which can be specified using
194`?param=value1&param=value2`. This represents a list of values
195composed of `value1` and `value2`. `QueryFlag` lets you specify a
196boolean-like query parameter where a client isn't forced to specify a
197value. The absence or presence of the parameter's name in the query
198string determines whether the parameter is considered to have the
199value `True` or `False`. For instance, `/users?active` would list only
200active users whereas `/users` would list them all.
202Here are the corresponding data type declarations:
204``` haskell ignore
205data QueryParam (sym :: Symbol) a
206data QueryParams (sym :: Symbol) a
207data QueryFlag (sym :: Symbol)
208```
210Examples:
212``` haskell
213type UserAPI6 = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User]
214 -- equivalent to 'GET /users?sortby={age, name}'
216```
218Again, your handlers don't have to deserialize these things (into, for example,
219a `SortBy`). **servant** takes care of it.
221### `ReqBody`
223Each HTTP request can carry some additional data that the server can use in its
224*body*, and this data can be encoded in any format -- as long as the server
225understands it. This can be used for example for an endpoint for creating new
226users: instead of passing each field of the user as a separate query string
227parameter or something dirty like that, we can group all the data into a JSON
228object. This has the advantage of supporting nested objects.
230**servant**'s `ReqBody` combinator takes a list of content types in which the
231data encoded in the request body can be represented and the type of that data.
232And, as you might have guessed, you don't have to check the content type
233header, and do the deserialization yourself. We do it for you. And return `Bad
234Request` or `Unsupported Content Type` as appropriate.
236Here's the data type declaration for it:
238``` haskell ignore
239data ReqBody (contentTypes :: [*]) a
240```
242Examples:
244``` haskell
245type UserAPI7 = "users" :> ReqBody '[JSON] User :> Post '[JSON] User
246 -- - equivalent to 'POST /users' with a JSON object
247 -- describing a User in the request body
248 -- - returns a User encoded in JSON
250 :<|> "users" :> Capture "userid" Integer
251 :> ReqBody '[JSON] User
252 :> Put '[JSON] User
253 -- - equivalent to 'PUT /users/:userid' with a JSON
254 -- object describing a User in the request body
255 -- - returns a User encoded in JSON
256```
258### Request `Header`s
260Request headers are used for various purposes, from caching to carrying
261auth-related data. They consist of a header name and an associated value. An
262example would be `Accept: application/json`.
264The `Header` combinator in **servant** takes a type-level string for the header
265name and the type to which we want to decode the header's value (from some
266textual representation), as illustrated below:
268``` haskell ignore
269data Header (sym :: Symbol) a
270```
272Here's an example where we declare that an endpoint makes use of the
273`User-Agent` header which specifies the name of the software/library used by
274the client to send the request.
276``` haskell
277type UserAPI8 = "users" :> Header "User-Agent" Text :> Get '[JSON] [User]
278```
280### Content types
282So far, whenever we have used a combinator that carries a list of content
283types, we've always specified `'[JSON]`. However, **servant** lets you use several
284content types, and also lets you define your own content types.
286Four content types are provided out-of-the-box by the core **servant** package:
287`JSON`, `PlainText`, `FormUrlEncoded` and `OctetStream`. If for some obscure
288reason you wanted one of your endpoints to make your user data available under
289those 4 formats, you would write the API type as below:
291``` haskell
292type UserAPI9 = "users" :> Get '[JSON, PlainText, FormUrlEncoded, OctetStream] [User]
293```
295(There are other packages that provide other content types. For example
296**servant-lucid** and **servant-blaze** allow to generate html pages (using
297**lucid** and **blaze-html**) and both come with a content type for html.)
299We will further explain how these content types and your data types can play
300together in the [section about serving an API](Server.html).
302### Response `Headers`
304Just like an HTTP request, the response generated by a webserver can carry
305headers too. **servant** provides a `Headers` combinator that carries a list of
306`Header` types and can be used by simply wrapping the "return type" of an endpoint
307with it.
309``` haskell ignore
310data Headers (ls :: [*]) a
311```
313If you want to describe an endpoint that returns a "User-Count" header in each
314response, you could write it as below:
316``` haskell
317type UserAPI10 = "users" :> Get '[JSON] (Headers '[Header "User-Count" Integer] [User])
318```
320### Basic Authentication
322Once you've established the basic routes and semantics of your API, it's time
323to consider protecting parts of it. Authentication and authorization are broad
324and nuanced topics; as servant began to explore this space we started small
325with one of HTTP's earliest authentication schemes: [Basic
326Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication).
328When protecting endpoints with basic authentication, we need to specify two items:
3301. The **realm** of authentication as per the Basic Authentication spec.
3312. The datatype returned by the server after authentication is verified. This
332 is usually a `User` or `Customer` type datatype.
334With those two items in mind, *servant* provides the following combinator:
336``` haskell ignore
337data BasicAuth (realm :: Symbol) (userData :: *)
338```
340Which is used like so:
342``` haskell
343type ProtectedAPI11
344 = UserAPI -- this is public
345 :<|> BasicAuth "my-realm" User :> UserAPI2 -- this is protected by auth
346```
348### Empty APIs
350Sometimes it is useful to be able to generalise an API over the type of some
351part of it:
353``` haskell
354type UserAPI12 innerAPI
355 = UserAPI -- this is the fixed bit of the API
356 :<|> "inner" :> innerAPI -- this lets us put various other APIs under /inner
357```
359If there is a case where you do not have anything extra to serve, you can use
360the `EmptyAPI` combinator to indicate this:
362``` haskell
363type UserAPI12Alone = UserAPI12 EmptyAPI
364```
366This also works well as a placeholder for unfinished parts of an API while it
367is under development, for when you know that there should be _something_ there
368but you don't yet know what. Think of it as similar to the unit type `()`.
370### Interoperability with `wai`: `Raw`
372Finally, we also include a combinator named `Raw` that provides an escape hatch
373to the underlying low-level web library `wai`. It can be used when
374you want to plug a [wai `Application`](http://hackage.haskell.org/package/wai)
375into your webservice:
377``` haskell
378type UserAPI13 = "users" :> Get '[JSON] [User]
379 -- a /users endpoint
381 :<|> Raw
382 -- requests to anything else than /users
383 -- go here, where the server will try to
384 -- find a file with the right name
385 -- at the right path
386```
388One example for this is if you want to serve a directory of static files along
389with the rest of your API. But you can plug in everything that is an
390`Application`, e.g. a whole web application written in any of the web
391frameworks that support `wai`.
393Be mindful! The `servant-server`'s router works by pattern-matching the
394different routes that are composed using `:<|>`. `Raw`, as an escape hatch,
395matches any route that hasn't been matched by previous patterns. Therefore,
396any subsequent route will be silently ignored.
398``` haskell
399type UserAPI14 = Raw
400 :<|> "users" :> Get '[JSON] [User]
401 -- In this situation, the /users endpoint
402 -- will not be reachable because the Raw
403 -- endpoint matches requests before
404```
405A simple way to avoid this pitfall is to either use `Raw` as the last
406definition, or to always have it under a static path.
408``` haskell
409type UserAPI15 = "files" :> Raw
410 -- The raw endpoint is under the /files
411 -- static path, so it won't match /users.
412 :<|> "users" :> Get '[JSON] [User]
414type UserAPI16 = "users" :> Get '[JSON] [User]
415 :<|> Raw
416 -- The Raw endpoint is matched last, so
417 -- it won't overlap another endpoint.
418```