@@ -0,0 +1,373 @@
1+# NamedRoutes - Using records to define APIs
2+
3+*Available in Servant 0.19 or higher*
4+
5+Servant offers a very natural way of constructing APIs with nested records, called `NamedRoutes`.
6+
7+This cookbook explains how to implement such nested-record-APIs using `NamedRoutes` through the example of a Movie Catalog.
8+
9+First, we start by constructing the domain types of our Movie Catalog.
10+After, we show you how to implement the API type with the NamedRoutes records.
11+Lastly, we make a Server and a Client out of the API type.
12+
13+However, it should be understood that this cookbook does _not_ dwell on the built-in servant combinators as the [<Structuring APIs> cookbook ](<https://docs.servant.dev/en/stable/cookbook/structuring-apis/StructuringApis.html>) already covers that angle.
14+
15+## Why would I want to use `NamedRoutes` over the alternative `:<|>` operator?
16+
17+With `NamedRoutes`, we don’t need to care about the declaration order of the endpoints.
18+For example, with the `:<|>` operator there’s room for error when the order of the API type
19+
20+```haskell,ignore
21+type API1 = "version" :> Get '[JSON] Version
22+ :<|> "movies" :> Get '[JSON] [Movie]
23+```
24+
25+does not follow the `Handler` implementation order
26+
27+```haskell,ignore
28+apiHandler :: ServerT API1 Handler
29+apiHandler = getMovies
30+ :<|> getVersion
31+```
32+
33+GHC could scold you with a very tedious message such as :
34+
35+```console
36+ • Couldn't match type 'Handler NoContent'
37+ with 'Movie -> Handler NoContent'
38+ Expected type: ServerT MovieCatalogAPI Handler
39+ Actual type: Handler Version
40+ :<|> ((Maybe SortBy -> Handler [Movie])
41+ :<|> ((MovieId -> Handler (Maybe Movie))
42+ :<|> ((MovieId -> Movie -> Handler NoContent)
43+ :<|> (MovieId -> Handler NoContent))))
44+ • In the expression:
45+ versionHandler
46+ :<|>
47+ movieListHandler
48+ :<|>
49+ getMovieHandler :<|> updateMovieHandler :<|> deleteMovieHandler
50+ In an equation for 'server':
51+ server
52+ = versionHandler
53+ :<|>
54+ movieListHandler
55+ :<|>
56+ getMovieHandler :<|> updateMovieHandler :<|> deleteMovieHandler
57+ |
58+226 | server = versionHandler
59+```
60+
61+On the contrary, with the `NamedRoutes` technique, we refer to the routes by their name:
62+
63+```haskell,ignore
64+data API mode = API
65+ { list :: "list" :> ...
66+ , delete :: "delete" :> ...
67+ }
68+```
69+
70+and GHC follows the lead :
71+
72+```console
73+ • Couldn't match type 'NoContent' with 'Movie'
74+ Expected type: AsServerT Handler :- Delete '[JSON] Movie
75+ Actual type: Handler NoContent
76+ • In the 'delete' field of a record
77+ In the expression:
78+ MovieAPI
79+ {get = getMovieHandler movieId,
80+ update = updateMovieHandler movieId,
81+ delete = deleteMovieHandler movieId}
82+ In an equation for 'movieHandler':
83+ movieHandler movieId
84+ = MovieAPI
85+ {get = getMovieHandler movieId,
86+ update = updateMovieHandler movieId,
87+ delete = deleteMovieHandler movieId}
88+ |
89+252 | , delete = deleteMovieHandler movieId
90+```
91+
92+So, NamedRoutes is more readable for a human, and GHC gives you more accurate error messages.
93+
94+What are we waiting for?
95+
96+
97+## Boilerplate time!
98+
99+First, let’s get rid of the the extensions and imports boilerplate in order to focus on our new technique:
100+
101+
102+```haskell
103+{-# LANGUAGE DataKinds #-}
104+{-# LANGUAGE DeriveAnyClass #-}
105+{-# LANGUAGE DerivingStrategies #-}
106+{-# LANGUAGE DeriveGeneric #-}
107+{-# LANGUAGE OverloadedStrings #-}
108+{-# LANGUAGE TypeOperators #-}
109+
110+import GHC.Generics ( Generic )
111+import Data.Aeson ( FromJSON, ToJSON )
112+import Data.Proxy ( Proxy(..) )
113+import Network.Wai.Handler.Warp ( run )
114+
115+import Servant ( NamedRoutes
116+ , Handler, serve )
117+import Servant.API (Capture, Delete, Get, Put, QueryParam, ReqBody
118+ , JSON, NoContent (..)
119+ , FromHttpApiData (..),ToHttpApiData(..)
120+ , (:>) )
121+import Servant.API.Generic ( (:-) )
122+
123+import Servant.Client ( AsClientT, ClientM, client
124+ , (//), (/:) )
125+import Servant.Client.Generic ()
126+
127+import Servant.Server ( Application, ServerT )
128+import Servant.Server.Generic ( AsServerT )
129+
130+```
131+
132+## Domain context
133+
134+Now that we’ve handled the boilerplate, we can dive into our Movie Catalog domain.
135+
136+Consider a `Movie` constructed from a `Title` and a `Year` of publication.
137+
138+``` haskell
139+data Movie = Movie
140+ { movieId :: MovieId
141+ , title :: Title
142+ , year :: Year
143+ }
144+ deriving stock Generic
145+ deriving anyclass (FromJSON, ToJSON)
146+
147+type MovieId = String
148+type Title = String
149+type Year = Int
150+
151+```
152+
153+
154+Let’s forget about the deriving stuff for now and think about the API that we want to make.
155+
156+```
157+ "version" -> Get Version
158+ /
159+api "list" -> Get [Movie] ?sortBy= Title | Year (sort by the Title or the Year)
160+ \ /
161+ "movies" Get Movie
162+ \ /
163+ Capture MovieId - Put Movie
164+ \
165+ Delete MovieId
166+```
167+
168+In this example, we create a very simple endpoint for the Version,
169+and several complex endpoints that use nested records for the CRUD part of the movie.
170+
171+So, the URLs would look like
172+
173+- GET …/version
174+- GET …/movies/list?sortby=Title
175+- GET …/movies/<MovieId>/
176+- PUT …/movies/<MovieId>/
177+- DELETE …/movies/<MovieId>
178+
179+### API Type
180+
181+Now that we have a very clear idea of the API we want to make, we need to transform it into usable Haskell code:
182+
183+``` haskell
184+
185+data API mode = API
186+ { version :: mode :- "version" :> Get '[JSON] Version
187+ , movies :: mode :- "movies" :> NamedRoutes MoviesAPI
188+ } deriving stock Generic
189+
190+type Version = String -- This will do for the sake of example.
191+
192+```
193+Here, we see the first node of our tree. It contains the two branches “version” and “movies” respectively:
194+
195+The “version” branch is very simple and self-explanatory.
196+The “movies” branch will contain another node, represented by another record (see above). That is why we need the `NameRoutes` helper.
197+
198+Note:
199+
200+The `mode` type parameter indicates into which implementation the record’s `Generic` representation will be transformed—as a client or as a server. We will discuss that later.
201+
202+Let's jump into the "movies" subtree node:
203+
204+
205+``` haskell
206+
207+data MoviesAPI mode = MoviesAPI
208+ { list :: mode :- "list" :> QueryParam "SortBy" SortBy :> Get '[JSON] [Movie]
209+ , movie :: mode :- Capture "movieId" MovieId :> NamedRoutes MovieAPI
210+ } deriving stock Generic
211+
212+data SortBy = Year | Title
213+
214+instance ToHttpApiData SortBy where
215+ toQueryParam Year = "year"
216+ toQueryParam Title = "title"
217+
218+instance FromHttpApiData SortBy where
219+ parseQueryParam "year" = Right Year
220+ parseQueryParam "title" = Right Title
221+ parseQueryParam param = Left $ param <> " is not a valid value"
222+
223+```
224+So, remember, this type represents the `MoviesAPI` node that we’ve connected earlier to the main `API` tree.
225+
226+In this subtree, we illustrated both an endpoint with a **query param** and also, a **capture** with a subtree underneath it.
227+
228+So, let's go deeper into our API tree.
229+
230+``` haskell
231+data MovieAPI mode = MovieAPI
232+ { get :: mode :- Get '[JSON] (Maybe Movie)
233+ , update :: mode :- ReqBody '[JSON] Movie :> Put '[JSON] NoContent
234+ , delete :: mode :- Delete '[JSON] NoContent
235+ } deriving stock Generic
236+```
237+
238+As you can see, we end up implementing the deepest routes of our API.
239+
240+Small detail: as our main API tree is also a record, we need the `NamedRoutes` helper.
241+To improve readability, we suggest you create a type alias:
242+
243+``` haskell
244+type MovieCatalogAPI = NamedRoutes API
245+```
246+
247+That's it, we have our `MovieCatalogAPI` type!
248+
249+Let's make a server and a client out of it!
250+
251+### The Server
252+
253+As you know, we can’t talk about a server, without addressing the handlers.
254+
255+First, we take our handlers…
256+
257+```haskell
258+versionHandler :: Handler Version
259+versionHandler = pure "0.0.1"
260+
261+movieListHandler :: Maybe SortBy -> Handler [Movie]
262+movieListHandler _ = pure moviesDB
263+
264+moviesDB :: [Movie]
265+moviesDB =
266+ [ Movie "1" "Se7en" 1995
267+ , Movie "2" "Minority Report" 2002
268+ , Movie "3" "The Godfather" 1972
269+ ]
270+
271+getMovieHandler :: MovieId -> Handler (Maybe Movie)
272+getMovieHandler requestMovieId = go moviesDB
273+ where
274+ go [] = pure Nothing
275+ go (movie:ms) | movieId movie == requestMovieId = pure $ Just movie
276+ go (m:ms) = go ms
277+
278+updateMovieHandler :: MovieId -> Movie -> Handler NoContent
279+updateMovieHandler requestedMovieId newMovie =
280+ -- update the movie list in the database...
281+ pure NoContent
282+
283+deleteMovieHandler :: MovieId -> Handler NoContent
284+deleteMovieHandler _ =
285+ -- delete the movie from the database...
286+ pure NoContent
287+
288+```
289+
290+And assemble them together with the record structure, which is the glue here.
291+
292+```haskell
293+server :: ServerT MovieCatalogAPI Handler
294+server =
295+ API
296+ { version = versionHandler
297+ , movies = moviesHandler
298+ }
299+
300+moviesHandler :: MoviesAPI (AsServerT Handler)
301+moviesHandler =
302+ MoviesAPI
303+ { list = movieListHandler
304+ , movie = movieHandler
305+ }
306+
307+movieHandler :: MovieId -> MovieAPI (AsServerT Handler)
308+movieHandler movieId = MovieAPI
309+ { get = getMovieHandler movieId
310+ , update = updateMovieHandler movieId
311+ , delete = deleteMovieHandler movieId
312+ }
313+```
314+As you might have noticed, we build our handlers out of the same record types we used to define our API: `MoviesAPI` and `MovieAPI`. What kind of magic is this ?
315+
316+Remember the `mode` type parameter we saw earlier? Since we need to transform our API type into a _server_, we need to provide a server `mode`, which is `AsServerT Handler` here.
317+
318+Finally, we can run the server and connect the API routes to the handlers as usual:
319+
320+``` haskell
321+api :: Proxy MovieCatalogAPI
322+api = Proxy
323+
324+main :: IO ()
325+main = run 8081 app
326+
327+app :: Application
328+app = serve api server
329+
330+```
331+Yay! That’s done and we’ve got our server!
332+
333+## The Client
334+
335+The client, so to speak, is very easy to implement:
336+
337+``` haskell
338+movieCatalogClient :: API (AsClientT ClientM)
339+movieCatalogClient = client api -- remember: api: Proxy MovieCatalogAPI
340+```
341+
342+Have you noticed the `mode` `AsClient ClientM`?
343+
344+We’ve also introduced some operators that help navigate through the nested records.
345+
346+`(//)` is used to jump from one record to another.
347+`(/:)` is used to provide a parameter, whether it be a query param or a capture.
348+
349+Let’s use those nice helpers for our movie catalog:
350+
351+```haskell
352+listMovies :: Maybe SortBy -> ClientM [Movie]
353+listMovies sortBy = movieCatalogClient // movies // list /: sortBy
354+
355+getMovie :: MovieId -> ClientM (Maybe Movie)
356+getMovie movieId = movieCatalogClient // movies // movie /: movieId // get
357+
358+updateMovie :: MovieId -> Movie -> ClientM NoContent
359+updateMovie movieId newMovie = movieCatalogClient // movies // movie /: movieId // update /: newMovie
360+
361+deleteMovie :: MovieId -> ClientM NoContent
362+deleteMovie movieId = movieCatalogClient // movies // movie /: movieId // delete
363+```
364+
365+Done! We’ve got our client!
366+
367+## Conclusion
368+
369+We hope that you found this workbook helpful, and that you now feel more confident using the `NamedRoutes` technique.
370+
371+If you are interested in further understanding the built-in Servant combinators, see [Structuring APIs](https://docs.servant.dev/en/stable/cookbook/structuring-apis/StructuringApis.html).
372+
373+Since `NamedRoutes` is based on the Generic mechanism, you might want to have a look at [Sandy Maguire’s _Thinking with Types_ book](https://doku.pub/download/sandy-maguire-thinking-with-typesz-liborgpdf-4lo5ne7kdj0x).