Now that we’ve handled the boilerplate, we can dive into our Movie Catalog domain.
Consider a `Movie` constructed from a `Title` and a `Year` of publication.
``` haskell
data Movie = Movie
{ movieId :: MovieId
, title :: Title
, year :: Year
}
deriving stock Generic
deriving anyclass (FromJSON, ToJSON)
type MovieId = String
type Title = String
type Year = Int
```
Let’s forget about the deriving stuff for now and think about the API that we want to make.
```
"version" -> Get Version
/
api "list" -> Get [Movie] ?sortBy= Title | Year (sort by the Title or the Year)
\ /
"movies" Get Movie
\ /
Capture MovieId - Put Movie
\
Delete MovieId
```
In this example, we create a very simple endpoint for the Version,
and several complex endpoints that use nested records for the CRUD part of the movie.
So, the URLs would look like
- GET …/version
- GET …/movies/list?sortby=Title
- GET …/movies/<MovieId>/
- PUT …/movies/<MovieId>/
- DELETE …/movies/<MovieId>
### API Type
Now that we have a very clear idea of the API we want to make, we need to transform it into usable Haskell code:
``` haskell
data API mode = API
{ version :: mode :- "version" :> Get '[JSON] Version
, movies :: mode :- "movies" :> NamedRoutes MoviesAPI
} deriving stock Generic
type Version = String -- This will do for the sake of example.
```
Here, we see the first node of our tree. It contains the two branches “version” and “movies” respectively:
The “version” branch is very simple and self-explanatory.
The “movies” branch will contain another node, represented by another record (see above). That is why we need the `NameRoutes` helper.
Note:
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.
Let's jump into the "movies" subtree node:
``` haskell
data MoviesAPI mode = MoviesAPI
{ list :: mode :- "list" :> QueryParam "SortBy" SortBy :> Get '[JSON] [Movie]
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 ?
Finally, we can run the server and connect the API routes to the handlers as usual:
``` haskell
api :: Proxy MovieCatalogAPI
api = Proxy
main :: IO ()
main = run 8081 app
app :: Application
app = serve api server
```
Yay! That’s done and we’ve got our server!
## The Client
The client, so to speak, is very easy to implement:
If you are interested in further understanding the built-in Servant combinators, see [Structuring APIs](../structuring-apis/StructuringApis.html).
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).