HTML templating w/ descriptive api endpoints

10 views
Skip to first unread message

Nate Vogel

unread,
Nov 23, 2022, 9:07:05 AM11/23/22
to haskell-servant
Hi,

Something that's great about Servant is having a very descriptive type for the APi, like

`Get '[JSON] [Toy]`.

And also being able to return different types of responses using instances, like returning JSON or HTML as long as I've got ToJSON and ToMarkup instances of `Something`:

`Get '[JSON,HTML] [Toy]`

If i'm returning HTML, its for humans to look at, so I want to wrap my site in something nice with a layout, a header, a footer, etc. What's a nice way to do that with Servant?

I can think of a few approaches, and i'm curious if there's a community-preferred practice.

1. Just create new alternative enpoints like `Get '[HTML] Html`.

This would let me write a handler to produce any html i want, but now the api type doesn't say anything about what this enpoint is, anymore.

2. Write my ToMarkup intances of types to include all the site layout/wrapper content, like

```
toMarkup a = wrapInSiteBase $ div
     show a
```

The problem I run into this way is if i've got multiple types. Type `Fruit` needs a page for each Fruit, so it needs an instance that wraps it in the site wrapper. But it also is part of the type `Basket [Fruit] [Vegetable]`, and `Basket` also needs a ToMarkup instance. It will be convenient in `Basket`'s `ToMarkup` instance to call `toMarkup fruit`. But now I've included the site wrapper multiple times.

3. Create a custom content type, as inhttps://hackage.haskell.org/package/servant-0.19.1/docs/Servant-API-ContentTypes.html#v:contentType, to wrap responses to the server.

I have only just started going down this road, and it seems like it'll work, but it'll be very rigid. There will only be one wrapper that I'll be able to apply, so it'll be useful for adding a site-wide theme/header/styles.

I hope this makes  sense, and i'm happy to add more detail, if it'd be helpful.

Thanks!

Julian Arni

unread,
Nov 23, 2022, 9:18:39 AM11/23/22
to Nate Vogel, haskell-servant
Hi Nate,

One way to avoid multiple wrappers is to have a wrapper *type*:

```
newtype Wrapper a = Wrapper { getWrapper :: a}

instance ToMarkup a => ToMarkup (Wrapper a) where
   toMarkup (Wrapper a) = wrapInSiteBase (toMarkup a)
```

and then have the endpoints return a `Wrapper [Toy]` instead of a toy.

But as you pointed out, there are many good options. The custom mimetype datatype route (route 3), is probably what I would do if the wrapper is always the same. You'd need to declare a new datatype, MyHTML, and have it be an instance of all classes HTML is, but when instantiating MimeRender, call wrapInSiteBase.

But if the wrapper changes, the newtype approach can work well, since you can easily have as many wrappers as you want.

Not sure if this is actually helpful - I'm mostly just nodding along with your original email!

Cheers,
  Julian

--
You received this message because you are subscribed to the Google Groups "haskell-servant" group.
To unsubscribe from this group and stop receiving emails from it, send an email to haskell-serva...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/haskell-servant/c2f18760-e717-4f5e-9af6-b6fbd784bdd2n%40googlegroups.com.

Nate Vogel

unread,
Nov 28, 2022, 3:55:42 PM11/28/22
to Julian Arni, haskell-servant
Hi Julian,

Thank you so much. I've gone with the custom content type for now, but it also looks like a Wrapper type would be a more flexible solution, if this site gets a little more complicated than it is for now.

Best,
Nate
Reply all
Reply to author
Forward
0 new messages