Grateful if you could share your experiences on this. Even if you prefer using Phoenix HTML without seperation between front-end and back-end.
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.
I sometimes have to use React, but my preference is:
- For most apps: āSprinkles of JSā (UJS + Turbolinks)
- 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.
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.
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.
Lately Iāve really liked using https://materializecss.com.
I use a combination of Phoenix HTML and LiveView. I use Tailwind CSS.
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
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?
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.
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ā
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ā¦
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?
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.
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?
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.
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.
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.
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 ) ?