Having spent years in the Ruby and Rails ecosystem before transitioning to full-stack JS and PHP, I’ve always been fascinated by the versatility and productivity of these languages. JavaScript, with its expansive ecosystem and React, has been enjoyable and productive, albeit occasionally overwhelming with its abundance of options.
My interest in Rails was reignited by its recent updates, beautifully captured in this article by DHH: The One-Person Framework. The simplicity and power of Rails are truly compelling, yet the allure of Elixir’s concurrency model and optimized runtime have caught my eye, with the promise of sidesteping potential scaling challenges down the road.
While the JS/TS community offers similar promises, the culture and analysis paralysis can be daunting at times.
As I’m exploring Elixir, I can’t help but wonder about leveraging it beyond its typical use, akin to a super-charged “process manager.” Imagine using Elixir not just for backend functionalities like authentication and background jobs but also for overseeing Node.js processes that handle SSR for React views, supplemented by an Elixir-powered API for client-side hydration.
In theory, based on what I’ve read so far about BEAM/Elixir, this setup could offer incredible flexibility, allowing for selective delegation between Node.js and Elixir based on specific route requirements, potentially optimizing performance and scalability across the board.
Has anyone ventured down this path? Could Elixir effectively replace PM2 while boosting performance, scalability, and stability (and developer productivity)? I’m keen to hear your experiences or any insights you might have!
Usually people around here want less JavaScript, not more.
The problem is not PM2 it is just a symptom of nodejs/javacript not really being made for serverside work. You have to do all sorts of crazy and complicated things as a consequence of that.
You can certainly build what you are suggesting, but I don’t think it is where Elixir/BEAM shines.
I guess there are quite a few people around here using it in a similar way.
In our case, we use elixir / phoenix to provide a websocket based api for machines, as well as a basic web based UI for various native apps on a RaspberryPi. As long as you can control another process via a proper API, elixirs supervision trees are pretty awesome to manage and control all those dependencies.
I would love a Phoenix-powered React Server Components implementation, which would also be “live” as in LiveViews-live.
I experimented with something like that, by putting a Next.js server behind a Phoenix reverse-proxy. So basically a web request hits Phoenix, I use Ecto to load up the data, then use POST to send that data to Next.js handler, and stream the Next.js-generated response back to the client browser connected to Phoenix. This works and is pretty simple, in fact I was surprised how simple it is. But this is still running a Next.js server with all it’s bugs and issues it has, eventually I decided against actually pushing this forward in any way.
As far as I understand, it doesn’t have any server-side rendering of React components, but you can somewhat build React Server Components-like behavior but also somewhat better with it using LiveViews.
The comopnents you mount this way will receive updated props from LiveView when it updates, and can send back events to LiveView.
What would be super cool is that these components could be pre-rendered, possibly using Bun or Node or some other JavaScript implementation and then hydrated. I believe that’s doable with something like you are suggesting.
Also, note that the LIveView state management and change tracking differs significantly in in principles from what React is doing. There are reasons for that, obviously, as avoiding re-rendering everything is somewhat more imporatant when the code sits on the server.
But another thing to try, would be to build something LiveView-like that is, however, using more React-style state management, for conceptual consistency if nothing else. Even if the code would be written in Elixir instead of JavaScript, having similar concepts of Hooks (as in useState) rather than implicit change tracking would be helpful and reduce the learning curve a lot.
Anyway, a lot of ideas, very little time to try them out.
I’d rather let the OS manage the services’ lifecycle and use Unix domain sockets for inter-process communication. I’m always trying to not reinvent the wheel. Make your services behave like proper daemons instead of half-arsing solutions.
I would encourage you to check out LiveState if you’re interested in exploring these ideas. The idea of LiveState is to give you the same state management via light-weight processes in Elixir leveraging Phoenix Channels, but allow you to do simple client side rendering for situations where that is preferable. Many of the examples use custom elements, but its easy to use with React, or even with simple client side templates in html using live-templates. It’s fairly mature at this point, and is actually quite simple as it is a thin veneer over channels for the most part.
Nice! So supervision trees are what differentiates the BEAM runtime from other runtimes in terms of process management? Does it automatically respawn crash processes, and can it (or some other abstraction) deal with pooling and some kind of auto-scaling?
What I understood from your post is you are looking for goodness of both worlds. Though I have seen some React integrations, the best JS framework integration into Phoenix would be woutdp/live_svelte: Svelte inside Phoenix LiveView with seamless end-to-end reactivity (github.com) -You get server side rendering, mix and match of Live Components and Svelte Components, animations everything.
I just thought if someone will do a port of that to React. That might make the React Component World come alive in the Phoenix.
Ofcourse, many people here would instead dream of a LiveView Component Library that matches the React world!!
Oh, livestate does look interesting! Thanks for pointing it out. So, if I got it right, you’re suggesting still spawning nodejs processes for SSR rendering of React, but then using livestate to render updates that are controlled by Elixir itself?
Yeah, precisely - React for (SSR/CSR) UI and Elixir for everything else. I think I was too broad, and I could also have been more concise, but I wanted to add some background info as well! .
I know I could have something along those lines with a BFF (backend-for-frontend) approach, i.e., Remix on top of an Elixir backend, but that would be a bit more complex and arguably also require more maintenance/resources. It would also shift the cognitive focus over to JS more, I think. It just feels that an Elixir-first approach would be more enjoyable, too, and allow me to keep JS to a minimum while still leveraging it when needed.
Good points! I know I could just use a BFF approach (see my other reply about using Remix ontop of an Elixir backend) if I want to focus on React UIs that are SSRed, but that would mean managing another repo and potentially another deployment process as well. That can be fine, but the approach I’m trying to explore here is to effectively abstract that as an implementation detail (part of the view layer) of the Elixir/Phoenix app, which could simplify things, I guess (or not!). I’m sure I’m missing other aspects, like logging, etc, it might not be too feasible in practice, that’s why I also wanted to ask around
Yeah, I saw that, but it looks abandoned, and I couldn’t get much info on how well it scales/runs when compared to a regular cluster of nodes managed by PM2, for example.
I see your point. I’m sure Liveview is more than enough, but being able to tap into SSRed React in a more integrated way for Phoenix views (and reuse existing skills) without having to have a separate app would be interesting!
EDIT: Another advantage of this approach, is that you can freely mix and match regular Elixir views with the augmented SSRed React ones, use the Phoenix routes or delegate tot the React routes as needed. The orchestrator here would be the Phoenix app, and not the nodejs app, which I think in theory would be easier to scale and isolate JS to the minimum necessary.
I think you might have an easier time checking out the use-live-state react hook. I built as a POC for a prospective client, didn’t end up using it in anger, but it’s also only 30 lines of code or so.
Yeah, that’s more or less what I have in mind, but I was wondering if it would be feasible not to use express and just connect at the process level (stdout). Not sure if it would be easier or more performant, still a newbie when it comes to BEAM. Probably it’d be more complex and not worth it? Also not sure if it’d support suspense in a straightforward way.
I think your approach could also align with what I want to do as long as Elixir manages it. Did your Elixir app spawned the Next.JS process? Did you have a pool and did you have any logic to “auto-scale” if needed? Any caching?
If you have the code somewhere, I’d love to take a peek at it!
Firstly, yes Elixir is capable of what you wish. I’m not sure if it’s still true, but Heroku used (still does?) Erlang’s supervision feature to manage its dynos. All those features are available in Elixir too.
This brings to mind an interesting perspective on skill level. In order to build what you want, one would develop considerable Elixir skills, perhaps even to the point of losing interest in their React skills. That sort of “catch 22” could be why no one has done it yet