Hey @andrewb, we’ve been doing this for the past 6 months or so. We’ve built an entire suite of UI applications that get data exclusively from our GraphQL API and it’s been a really excellent experience. I’ve just gotten back from a long bit of traveling so I’m going to just post some jumbled thoughts on the matter now, and then hopefully follow up later with more discussion.
To address @hauleth’s point first, we at least did not do these as the same application. We have a rather large pre-existing Logistics IoT platform from which we had been driving React UIs via GraphQL. For various reasons we wanted to look at using LiveView instead of React for a new suite of user experiences. We considered building these into the original application, but several things made that a non optimal plan:
- The core platform is very large. Splitting up the apps makes the review process simpler because you can be absolutely certain that UI changes are only touching UI code. We can deploy that without concern for anything that’s happening in the underlying platform.
- The core platform is very critical. We didn’t want to risk stuff like a badly written live view eating up tons of memory and cause OOM crashing the platform containers. We run replicas of course but separating the deployed artifacts in general allowed us to have distinct operational flows for each.
With that said, here are some assorted things we learned:
Pro: querying GraphQL is really easy to grok. We had our React devs building LiveView UIs in no time flat because they could just look at the API docs as to what was available, query it, throw it in the UI. Even if you’re doing everything through “Phoenix Contexts” with nice clear boundaries and no out of band ecto querying, those context functions all have ad hoc Elixir specific ways of specifying what additional data you want to load. Discoverability is low, and any docs written are pure text, they aren’t generated from what’s actually possible.
Pro: GraphQL subscriptions works very well with LiveView. Query stuff on mount, open a socket, subscribe to the same stuff, and when you get a message replace the state. Boom, everything is immediately live, it’s like magic.
Pro/Con: If your API doesn’t support something yet, you need to add an API for it. This is sort of a Con, but in my view it works out to be a pro. Any other clients you have (mobile, customers, etc) benefit from the additions, and you get a self documented API out of the deal.
Con: Query results are decoded JSON strings. If you want nice atom keyed stuff you have to do some extra work. We started by just having a bunch of ecto embedded schemas in the app that we cast the GraphQL results into so we’d get nice atom keys and scalar type casting. This obviously sucks though so I’ve been working on some Absinthe.Client functionality that introspects a schema and can then cast results from that schema into nice Elixir “native” feeling data structures.
Con: You need to do a little bit of your own work to create an Absinthe client. I will try to open source the wrapper I wrote about the Phoenix Channel client here shortly cause that’s the most complicated bit. Pure HTTP request based queries are simple enough, pick your favorite HTTP client library and go for it. Arguably though if you’re doing subscriptions you want everything over the socket so I need to make sure there are nice APIs for that sort of thing.
All in all, I’d do it again, it’s been a wonderful experience. It helped that we already had a decent sized GraphQL api already so in particular the first like 70% of what we did UI wise didn’t require much or any changes to the API itself. Even as we’ve continued to develop this though, that API boundary has provided the perfect demarcation line for work across teams.
For small personal projects I’m not sure if it’d be worth it. I’m in the midst of setting up a nerves based backyard garden watering system and for that I will probably just wire live view directly to the underlying code. For anything at a company, I’d definitely recommend considering it.