Which UI framework you prefer to use along with Phoenix API back-end?

Grateful if you could share your experiences on this. Even if you prefer using Phoenix HTML without seperation between front-end and back-end.

6 Likes

I currently do a hybrid of both, mostly due to business constraints.

I share react code on mobile and my webapp interface, but I reuse the presentational react components in eex templates as well to have some interactivity on the pages that I want to be SSRā€™d and indexed.

If I was working with only Web, I would just stick to eex everywhere and sprinkling react widgets where I need them.

5 Likes

I sometimes have to use React, but my preference is:

  1. For most apps: ā€œSprinkles of JSā€ (UJS + Turbolinks)
  2. For very complex front-ends: Vue

When using Vue, I still often do routing from the backend, and make all of or most of the template files a Vue component (as is fairly common among Laravel devs).

For UI frameworks, Iā€™ve worked most with Bootstrap but am getting more interested in Tailwind. Vue Element is also interesting.

10 Likes

I like using Bulma when wanting many pre-built widgets but have also used Tailwind CSS in a recent project and really like it for more custom designs when wanting to be closer to CSS.

Now that LiveView is out, Iā€™ve been experimenting with a more hybrid approach with server-side ā€œcomponentsā€ (i.e. stateless ā€œpartialā€ ~E functions or stateful child ~L Live Views), routing only server-side (regular and live), and sprinkling in presentational react components as needed.

I do think this will eventually work great and speed up development of some apps by removing much of the complexity of API/GQL/client side state but still havenā€™t figured out how to pass LV hooks (especially pushEvent callback) into a react tree cleanly. Just waiting for more interop examples in the wild before testing this POC.

3 Likes

Seems a strange way of looking at things.

Phoenix may assemble the HTML/JS/CSS - but once itā€™s delivered to the browser itā€™s free to take on a life of its own. Not taking advantage of the separate browser resources is an implementation choice - not a lack of separation.

1 Like

Lately Iā€™ve really liked using https://materializecss.com.

3 Likes

I use a combination of Phoenix HTML and LiveView. I use Tailwind CSS.

3 Likes

I think this was an obvious point to make, but it seemed clear to me at least that the op was referring to using a more traditional client server full page refresh approach when they said Phoinex HTML. Of course the response could contain any number of JS frameworks

1 Like

I meant Phoenix with built-in eex HTML templating vs JSON backend with JS driven front-end.

I have tried to use just eex for web apps Iā€™ve built and always end up going back to Vue. You simply canā€™t match the experience (easily at least) of using a framework like Vuetify. I think Vue is pretty simple and easy to understand and is by far a better UI experience than eex from a development perspective. Combined with Apollo on the frontend and Absinthe on the backend the data transfer between server and client is straightforward. Live View is interesting but you still miss out on all the nice things you get from a javascript framework. I also think working in javascript for frontend work is superior to a server side templating engine. Maybe thatā€™s just because I enjoy javascript?

7 Likes

jQuery and Bootstrap 4.3, I gave up on front end rendering for now.

Iā€™ve invested a summer in VueJS 2.5 iirc and it going to change in 3. Ugh. After a few years out of front end and I decided that front end was stable enoughā€¦ noooope. I did Ember.JS and a little bit of Angular 1 before.

Highly interested in LiveView though, but Iā€™m a one man shop, Iā€™ll wait for it to be better.

4 Likes

For the past two years, Iā€™ve been using Phoenix headless, with GraphQL as a go-between with Nuxt.js (Vue). We use it for Edgewise (https://edgewiserealty.com). Iā€™ve used it on other projects as well. The Phoenix backend and Nuxt presentation layer are deployed separately via Kubernetes (different resource requirements), with assets (JS, CSS, etc.) hosted in a CDN. IMO, this is the ā€œbestā€ stack Iā€™ve come across (all things considered).

I am very optimistic about LiveView and it has a ton of potential, but I think the dev experience of LiveView still has a long way to go before it can go up against something as mature as Vue.js. For instance, I was initially skeptical about Vueā€™s Single File Components, but I donā€™t think I can go back now. Side note, Iā€™ve actually spent some time getting LiveView to populate Nuxt pages, which keeps the SFC capability with route splitting JS and CSS. I even called it ā€œPhoenuxtā€ :slight_smile:

Itā€™s still early for LiveView, but I think these more advanced front-end concepts are going to be necessary for LiveView to go beyond the ā€œlook what I did without JSā€ demos. If I get some spare time, Iā€™d like to actually create a ā€œsingle file componentā€ for LiveView, where the ā€œtemplateā€ portion is EEX. So thereā€™d actually be 4 sections (EEX template, Elixir code, Vue JS script, style). But I digressā€¦

4 Likes

I agree with the sentiment here and have to say that I think vue is a great technology even for smaller things is usually my default chioce now.

Im curious about your use of graphql here, do you find it more overhead to write than just exposing rest services? Also internally are you still writing rest services and then a further gql layer or does your gql resolvers/mutators directly expose/mutate directly?

Finally, are you using vuex to interact with gql and if so how are you finding it?

2 Likes

I donā€™t find GQL to be too much overhead. Plus, I think itā€™s more than worth it. Iā€™ve lost count on how many ā€œRESTā€ APIs Iā€™ve seen that couldnā€™t even follow basic collection/resource noun conventions and odd ways of handling authentication (basic, query params, x-something, Bearer, etc). Also, itā€™s essentially self-documenting via introspection, and you never have to worry about sending too little or bloating the response, or dealing with Swagger / OpenAPI / Postman collections. I usually pair it with Guardian, with a set of plugs that handle the initial header / JWT / resource / blacklist logic. I think the biggest concern is that you could accidentally allow someone to backdoor their way to sensitive information through a maze of querying. Something to watch out for, I suppose. Apollo has been doing some really interesting things with caching and tracing as well.

I initially offered both REST and GraphQL simultaneously, with the REST API sitting on top of the GraphQL layer. So a REST call for /users/1 would internally call a GQL query, which would then tap into Ecto. GraphQL acts as the ā€œviewā€ for your REST API; which allows your REST logic to be quite thin. Itā€™s not really too much trouble to offer both if your REST API is built on top of your GraphQL API.

Yes, I use vuex. Iā€™m always looking for better ways to do it, but I almost always interact with any API (whether itā€™s Apollo, Axios, etc.) via the Vuex store actions. That piece is relatively constant. The piece that I go back and forth on, is whether or not to store the state in Vuex or within the components and let the data cascade.

In the former, the stores are loosely tied to a URL struture. So I might have a store called product which has a get action. When someone navigates to the /products/1 page, the get action is called and populates the state with that productā€™s data. The components within the page tend to make use of mapState and computed properties quite a bit. When the user navigates away, the page component is responsible for ā€œresettingā€ the storeā€™s state (usually on beforeDestroy).

In the latter, I treat the store like an Elixir module, and essentially ignore the state functionality. The stores are not tied to URLs, and since there is no state, you donā€™t have to worry about resetting it. So I might have a product store, with get, all, update, actions. They donā€™t commit, they simply return the data. Whatever component made the API call is responsible for passing the data down to the child components; which I presume makes fans of dependency injection warm and fuzzy. In that model, you could make the argument that you donā€™t even need Vuex anymore, but Iā€™d disagree because I still use the root store for session data. I also find that I have way more props on components than Iā€™d like, and sometimes theyā€™re there just as a pass-through to a child component! The elder components end up passing the kitchen sink down just incase a great-great-great grandchild component might need it.

In my opinion, they both have their merits, and I honestly go back and forth between which I prefer. I think Evan would say the latter was for smaller apps, and the former was for bigger apps. Thereā€™s a lot of gray area in there thoughā€¦

Hope that helps.

6 Likes

Thanks for the interesting response I have many similar feelings to you on all this.

I quite like the idea of having a store per ā€œpageā€ with state that lasts as long as the components is mounted but I also understand the uneasiness of putting this in the vuex state in the first place.

Are you making several smaller gql queries for different parts of data in your ā€œpageā€? Do you still have done kind of loading/loaded/failed indicators in your pages and how do you identify these from a gql queries?

I assume that you also have some date that you keep more long lived that ā€˜pageā€™ components lifecycle like auth tokens, notifications counter and reference data etc? Are these always pulled from separate queries or somehow parts of your responses are designated to end up in these longer lived state buckets?

Regarding your prop data propogation, I am always unsure about this too. I feel like if the data is present in the store then am I just passing props down to dumb containers for the sake of it.

I completely agree with this in theory so to try and make components as decoupled as possible, but that chain of prop passing never gives me a good feeling if Iā€™m going further than one level down.

Ok thatā€™s interesting because I was more thinking along the lines of a gql resolvers querying a rest endpoint that would retrieve the data and not the reverse.

In some ways does this not remove the benefit of gql as you have a flexible query that is now hidden behind a ridgid resource?

1 Like

Phoenix and Vue are the ā€œmagic stackā€ for me. Ever since I started using them together two years ago, I havenā€™t looked back.

For reference, I also dabbled in React for a while, but didnā€™t find it nearly as ā€œnaturalā€ as Vue. IMO it abstracts away too much, and its source code was kind of a mess at the time I read it. I also donā€™t like that JSX is its go-to default and everything else is kind of a second thought.

3 Likes

Yes. The main page data is fetched server-side for SEO. On mount in the browser, additional below-the-fold content is fetched; which is sometimes handled by the parent, and sometimes delegated to the child components. You need to find the balance on page load performance and SEO. Additionally, you can use IntersectionObserver to fetch content only as the user is about to scroll that section into view.

Yes. For cascading parent/child/grandchild components, itā€™s easy enough to have a loading: true data prop by default in the parent, and then set loading: false when the data is returned from the store action. It can get a little more tricky if a sibling component wants to know the loading status (eg. a dashboard where multiple panels are displaying the same data differently). Still doable, and I can share how I do it, if you like.

Yes, the session data (user info), and extracted cookie / localStorage data is kept in the root store, as well as actions for logging in, etcā€¦ I almost always wrap cookie and localStorage get/set in actions / mutations.

Itā€™s case by case ā€“ but as a general rule ā€“ things like application settings and user profile data are all stored in the root store. I usually have a root getter called isGranted that I can use for client-side permissions.

Youā€™d run into trouble pretty quick. GQL is ā€œdeclarativeā€, which means the user might query a field that your REST API wouldnā€™t have returned. So you can wrap your GraphQL in REST, but you really canā€™t wrap your REST in GraphQL.

Yes, and thatā€™s one of the many reasons why GraphQL is superior to REST ā€“ in my opinion. REST is rigid by comparison. Sophisticated REST APIs like Stripe use expand query params to tell the endpoint to include nested data, but that can only get you so far.

So to offer REST on top of GraphQL, youā€™re doing it not because it offers more functionality, but rather maintaining legacy API endpoints as you move from REST to GraphQL, or simply providing developer options. Not everyone is comfortable yet with GraphQL.

3 Likes

Just because I didnā€™t see it mentioned yet, I have to say I love Elm on the front-end, with phoenix on the back-end. I love the type safety and other guarantees Elm gives that a JS or even Typescript framework canā€™t, and once youā€™re familiar enough with it, it is a joy to work with.
It seems less mentioned around here than it used to be, but Elm still has a great committed community around it.

1 Like

I have seen this statement posted a few times on this forum, which makes me ponder due to Elixir lack of type checking. Do you find the lack of type checking is less of an issue with Elixir compared to JS (maybe due to its functional nature or immutability ) ?

1 Like

Vue.js+Vue Router+Vuex with Buefy (a pretty cool Bulma-based UI component library).

I am curious about Svelte, is anyone here using it for some relatively complex front-end?

1 Like