I use github.com/obsidiansystems/obelisk/ at work (I work at the place that makes it). It does it's own thing for user-visible routes (for frontend and backend because automatic prerendering). It does a different thing for requests and live queries all of which are over websockets for simplicity.
Our routes stuff should be pulled out as a generic dual parsing-prettyprinting library (it and https://hackage.haskell.org/package/tomland are the same basic ideal, just need to remove the concrete HTTP / TOML specifics to get to the commonality).