Phoenix LiveView - Building Web Apps Without JavaScript
After all, you don’t need JS to build web applications.

In this story, you will learn what Phoenix LiveView is and how it works. Then, we will build a small counter app using LiveView.
I’m assuming that you have Phoenix, Elixir/Erlang and Node.js installed. If not, then make sure to install them.
Let’s start!
What is Phoenix LiveView?
LiveView is one of the most exciting features in the Phoenix web framework.
It’s a new way to produce fluid, real-time, fast and scalable web interfaces that don’t require writing JavaScript.
No JavaScript
Yes, you heard me right. No JavaScript. You don’t need any JavaScript to build web applications with LiveView. Though, you still need it for stuff like animations.
The LiveView programming model is declarative: instead of saying “once event X happens, change Y on the page”, events in LiveView are regular messages which may cause changes to its state. Once the state changes, LiveView will re-render the relevant parts of its HTML template and push it to the browser, which updates itself in the most efficient manner.
- Phoenix Documentation
This is how Phoenix explains LiveView.
Basically, instead of using stuff like onClick()
to do something, we can just have messages that do something when their value/state changes.
Also, Phoenix says that “LiveView will re-render the relevant parts of its HTML template”. This means that instead of updating the whole page, LiveView will only update the relevant part (e.g. Follower Count) of the page.
This makes LiveView very efficient and fast.
How it works
LiveView is first rendered statically as part of regular HTTP requests, which provides quick times for “First Meaningful Paint”, in addition to helping search and indexing engines.
Then a persistent connection is established between client and server using WebSockets.
Thus, making the app faster as there is less work to be done and fewer data to be sent compared to stateless requests that have to authenticate, decode, load, and encode data on every request.
The flip side is that LiveView uses more memory on the server compared to stateless requests.
Confused?
If you are confused, don’t worry. The video below explains how LiveView works pretty well. Even if you understood everything, I recommend watching the video.
My Thoughts
I personally love LiveView. It’s easy and fun to code. I don’t think there are any alternatives to it in the JS ecosystem that provides the same performance and development speed.
Anyways, enough theory. Let’s write some code!
A Simple Counter App
Let’s build a simple counter app. We will start with scaffolding our project.
Creating a new Phoenix LiveView project
To create a new LiveView app, just run the command below.
mix phx.new counter --live --no-ecto
What this command does is basically creating a new Phoenix project, adding LiveView and removing Ecto.
Why did we remove Ecto? Well, we don’t really need DB interactions for a simple counter app. So we just add the --no-ecto
flag to not have to configure anything and decrease the size of our app.
Also, adding the --live
flag adds some configuration for us, but I won’t be explaining it here. If you want to read more about the LiveView configuration, you can check out the docs.
LiveView
You will see that there is a live
folder inside the lib/liveview_web
directory. And inside it, there are two files. A template and a page_live.ex
file. You can remove the template file since we usually render the templates inside the *_live.ex
file. You can of course render from template files whenever you want.
Router
If you look at the router file, you will see that there is a live route added for us.
live "/", PageLive, :index
This basically specifies that this route serves a LiveView.
Building our app
We reviewed the files created by running the mix phx.new counter --live --no-ecto
command. Now let’s actually start coding our app.
Go to the lib/liveview_web/live/page_live.ex
file, and change it to the code below.
Let’s review the code line by line.
mount/3
The mount/3
callback wires up socket assign necessary for rendering the view. We are creating our :count
with the value of 0. And returning the socket.
render/1
The render/1
callback receives the socket.assigns
and is responsible for returning rendered content.
See how we did @count
there? That’s how we get a value from the socket in LiveView.
Also, you can see that we have phx-click
bindings on our buttons. That’s how we send an event on click. The first button sends an increase
event, and the second one sends a decrease
event.
handle_event/3
We receive the event in the handle_event/3
callback. and update our socket assigns. Whenever a socket's assigns change, render/1
is automatically invoked, and the updates are sent to the client.
If the event we are getting is increase
, the first handle_event
will be executed and the count will be increased.
But, if the event we are getting is decrease
, the secondhandle_event
will be executed and the count will be decreased.
Running our app
mix phx.server
This command will start the server and you’ll be able to see your app at http://localhost:4000
if everything works fine.
If you now visit the http://localhost:4000
, you’ll see your counter app that either increases or decreases the count on button click.

That was easy, wasn’t it?
How does it work?
If you go to DevTools -> Network -> WS
, you’ll see that every time you click on a button, data is being sent through the WebSocket and updated instantly.

Pretty cool right? We did all this stuff with no JavaScript at all!
Final Thoughts
You just learned the basics of LiveView, and built a simple counter app with no JavaScript! I hope this story helped you in any way. See you in the next story!
And that’s it. Thanks for reading this story!
If you liked the story, make sure to clap to it! And feel free to ask me anything you want.
Follow me on Twitter:
Support me on Patreon: