Phoenix LiveView - Building Web Apps Without JavaScript

Alim Arslan Kaya
Geek Culture
Published in
5 min readJul 5, 2021


After all, you don’t need JS to build web applications.

Story image

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.


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 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.


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.


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 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.


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.


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.


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.

Simple counter app image
Our Simple Counter App

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.

Websocket log image
Our Websocket Log

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:



Alim Arslan Kaya
Geek Culture

Fullstack Go, Rust, Elixir, Python and TypeScript Enthusiast.