Phoenix Framework 1.7 — What’s New & Why It Matters
In case you haven't used it before, the Phoenix Framework is an amazingly powerful web development framework for the Elixir programming language. It packs a ton of powerful features like channels for real-time websocket based communication, and LiveView, a server-rendered technology which enables the development of real-time interactive apps in the browser, without writing a line of JavaScript. And because it's built on the Erlang/OTP, Elixir and Phoenix bring kick-ass support for scalability, fault tolerance and concurrency right out of the box.
Version 1.7 of Phoenix is now live, and it's packed with great new features, especially for LiveView. In this post, we'll take a look at the new features in Phoenix version 1.7, and why they matter.
Though LiveView already provided a great developer experience, this release feels like "LiveView 2.0" in terms of the well thought out API changes and improvements.
Verified Routes
Verified routes are a new & greatly improved way to construct links within your Phoenix application.
When creating internal links in previous versions of Phoenix, we would typically use a route helper to generate a URL fragment for us. For example, if we had a PageController
defined in our router, and wanted to generate a link to it from somewhere else in the app, we might have code like the following:
# router.ex
get "/pages/:page", PageController, :show
# elsewhere in our app:
MyAppWeb.Router.Helpers.page_path(conn_or_endpoint, :show, "landing", options)
That works well, but can sometimes be a pain: it’s verbose, and requires knowledge of exactly how routes and actions are named. In practice, using helpers to generate URLs often involves consulting the router.ex
file or using mix phx.routes
to make sure you get the right module and action names.
It would be far simpler to just refer to the path directly when creating a link:
<a href="/app/pages/landing" />...</a>
However, writing the path like this comes with a major downside: you have no way of knowing at build-time if you get the path wrong or if it changes later on. There are no errors or warnings to tell you you’re using a broken path. (In fact, as an example, the path used above is incorrect, but we'd have no way of knowing!)
This is where verified routes come in: using Elixir's sigil syntax, we can reference a route with ~p
in front of a string and take advantage of compile-time route validation. It also allows us to add query params the way we would with the route helpers:
iex> query = [posts: 1, search: "elixir"]
iex> ~p"/pages/landing?#{query}"
"/pages/landing?posts=1&search=elixir"
LiveView Streams
A neat new feature to LiveView in 1.7 is the addition of streams. LiveView Streams allow you to manage updates, inserts and deletes to large datasets without needing to store and reload the entire collection on the server as one would with a regular LiveView assignment. They also helps to reduce the amount of re-rendering required to modify items on the frontend.
To create a stream, call stream/3
, providing the socket, the name of the stream, and the initial dataset:
def mount(_params, _session, socket) do
{:ok, stream(socket, :posts, Blog.list_posts())}
end
Then, in then template, instead of accessing a @posts
assign, you simply use @stream.posts
instead. If the contents of the collection change, you can use stream_insert/4
or stream_delete/3
. The stream_insert/4
function handles both new records and updates, and also supports re-ordering of existing records.
Unified Rendering for LiveView & Static HTML
Phoenix 1.7 introduces a new Phoenix.Template
rendering system which unifies how rendering works between static HTML and LiveView content.
Phoenix offers two distinct ways to render content: traditional static HTML, and LiveView for building rich, dynamic web applications. In practice, many applications use both approaches, depending on the type of content being rendered. For example, you might use a controller-based static approach for a login page, but an interactive LiveView once the user is logged in.
In the past, the two different approaches to rendering had different APIs and conventions making it difficult to reuse templates and other functionality between the two. With Phoenix 1.7, these differences are resolved with unified APIs using Phoenix.Template
instead of Phoenix.View
.
One of the most important changes is the introduction of unified function components that work in both LiveViews and in static-rendered controllers. Previously, sub-templates would be rendered differently in a controller vs. within a LiveView. Now, both kinds of content can use the new component approach:
# Rendering a table from a controller-based template (pre 1.7)
<%= render("table", user: user) %>
# Rendering a table from LiveView or controller (1.7)
<.table user={@user}>
This change greatly simplifies the developer experience for building content and enhances template reusability.
For backwards compatibility, you can still use Phoenix.View
in your application, but may wish to migrate to Phoenix.Template
to take advantage of the unified APIs. Note that both Phoenix.Template
and Phoenix.View
are separate dependencies, so be sure to install the one(s) you need.
Collocated Views & Updated Directory Structure
A related change which helps to unify LiveView and controller-based rendering is in the directory structure: prior to 1.7, controller-based pages kept views and templates in a separate lib/my_app_web/views
hierarchy, while LiveView typically placed template files right alongside the LiveView
or LiveComponent
module.
In 1.7, the "colocated" view approach is brought over to controller-based templates to unify the approach between the two. Note that this applies not just to HTML templates, but also to other formats such as JSON views. Keeping the template content closer to the controller code helps with project organization and consistency. Like many of the other changes here, however, this change is optional and fully backwards compatible.
Component-Based Generators with Tailwind and More
Building on the new unified function component support, Phoenix 1.7 introduces a new set of standard Core Components for key HTML functionality such as forms, tables, etc:
<.simple_form :let={f} for={@changeset} action={~p"/posts"}>
<.error :if={@changeset.action}>
Oops, something went wrong! Please check the errors below.
</.error>
<input field={{f, :title}} type="text" label="Title" />
<input field={{f, :views}} type="number" label="Views" />
<:actions>
<.button>Save Post</.button>
</:actions>
</.simple_form>
These default components will be created for you in a core_components.ex
file when creating a project with phx.new
. The components, by default, use Tailwind for styling, but can be fully customized or replaced with other styling libraries such as Bootstrap, Bulma, or your own plain CSS if you'd like.
How to Upgrade
To update, you’ll want to follow the upgrade guide. In addition to simply updating dependency versions, you’ll need to make some changes throughout your existing project to make use of new modules and patterns.
I found it was useful to create an empty new 1.7 project from scratch and compare the generated files — there were a few files that were missing or changed after following the upgrade instructions. For example, the core_components.ex
file mentioned above was missing since it's generated with phx.new
, and the root-level template HTML files were both updated (and moved).
Recap — the Best Web Framework Keeps Getting Getter
Phoenix 1.7 adds a number of great new features and refines the developer experience especially when working with LiveView. Honestly, though LiveView already provided a great developer experience, this release feels a bit like "LiveView 2.0" just in terms of the well thought out API changes and improvements.
With this release, Phoenix remains one of the most exciting web frameworks out there today. It earns that distinction owing to the marriage of innovative and powerful high-level features like channels and LiveView, built on the rock solid, highly performance foundation of Erlang/OTP.
If you haven’t used the Phoenix Framework before, the 1.7 release is a great time to get started! Check out the following for further reading on what makes the Phoenix framework so powerful.