Broadcast messages from api to LiveView controller

Hi, I created a basic LiveView example to test the “broadcast_from” messaging, and it works great. This is my controller:

defmodule HelloWeb.ThermostatLive do                                                                                                                                                       
  use Phoenix.LiveView                                                                                                                                                                     
                                                                                                                                                                                           
  @topic "deployments"                                                                                                                                                                     
                                                                                                                                                                                           
  def render(assigns) do                                                                                                                                                                   
    ~L"""                                                                                                                                                                                  
    <h1>The light is <%= @light_bulb_status %>.</h1>                                                                                                                                       
    <button phx-click="on">On</button>                                                                                                                                                     
    <button phx-click="off">Off</button>                                                                                                                                                   
    """                                                                                                                                                                                    
  end                                                                                                                                                                                      
                                                                                                                                                                                           
  def mount(_params, _session, socket) do                                                                                                                                                  
    HelloWeb.Endpoint.subscribe(@topic)                                                                                                                                                    
    socket = assign(socket, :light_bulb_status, "off")                                                                                                                                     
    {:ok, socket}                                                                                                                                                                          
  end                                                                                                                                                                                      
                                                                                                                                                                                           
  def handle_event(light_bulb_status, _value, socket) do                                                                                                                                   
    HelloWeb.Endpoint.broadcast_from(self(), @topic, "status_change", light_bulb_status)                                                                                                   
    {:noreply, assign(socket, :light_bulb_status, light_bulb_status)}                                                                                                                      
  end                                                                                                                                                                                      
                                                                                                                                                                                           
  def handle_info(%{topic: @topic, payload: light_bulb_status}, socket) do                                                                                                                 
    IO.puts "HANDLE BROADCAST FOR"                                                                                                                                                         
    IO.puts light_bulb_status                                                                                                                                                              
    {:noreply, assign(socket, :light_bulb_status, light_bulb_status)}                                                                                                                      
  end                                                                                                                                                                                      
end                                                                                                                                                                                            

Now, I would like to change the values of light_bulb_status from an external app. To do that I created an /api endpoint named /api/notify and would like it to send messages to the ThermostatLive controller.

This is my NotifyController:

defmodule HelloWeb.NotifyController do                                                                                                                                                     
  use HelloWeb, :controller                                                                                                                                                                
  use Phoenix.LiveView                                                                                                                                                                     
  @topic "deployments"                                                                                                                                                                     
                                                                                                                                                                                           
  def get(conn, _params) do                                                                                                                                                                
    HelloWeb.Endpoint.broadcast_from(self(), @topic, "status_change", "on")                                                                                                                
    {:noreply, Phoenix.LiveView.assign(socket, :light_bulb_status, "on")}                                                                                                                  
    json(conn, %{result: "get"})                                                                                                                                                           
  end                                                                                                                                                                                      
end   

When I call it from cURL with:

curl -H "Content-Type: application/json" "http://127.0.0.1:4000/api/notify"

I get this:

= Compilation error in file lib/hello_web/controllers/notify_controller.ex ==
** (CompileError) lib/hello_web/controllers/notify_controller.ex:8: undefined function socket/0
    (elixir 1.12.3) src/elixir_locals.erl:114: anonymous fn/3 in :elixir_locals.ensure_no_undefined_local/3
    (stdlib 3.16.1) erl_eval.erl:685: :erl_eval.do_apply/6
    (elixir 1.12.3) lib/kernel/parallel_compiler.ex:319: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/7

How can I broadcast messages from my api to the other controller?

It seems you have 2 questions here:

  1. How to broadcast from a phoenix controller?
    You did that with:
HelloWeb.Endpoint.broadcast_from(self(), @topic, "status_change", "on")
  1. How to change a socket assign from a Phoenix controller?
    You can’t directly do it. The line:
{:noreply, Phoenix.LiveView.assign(socket, :light_bulb_status, "on")}

won’t work. There is no socket in a standard Phoenix controller, that’s why you are getting a compilation error. What you should do instead is broadcast a message, receive it in the LiveView in handle_info and there you update the assigns.

Wow!, didn’t know the `assign(socket, …)``` wasn’t required, here’s my api controller, now working as expected:

defmodule HelloWeb.NotifyController do                                                                                                                                                     
  use HelloWeb, :controller                                                                                                                                                                
  use Phoenix.LiveView                                                                                                                                                                     
  @topic "deployments"                                                                                                                                                                     
                                                                                                                                                                                           
  def get(conn, _params) do                                                                                                                                                                
    HelloWeb.Endpoint.broadcast_from(self(), @topic, "status_change", "on")                                                                                                                
    json(conn, %{result: "get"})                                                                                                                                                           
  end                                                                                                                                                                                      
end    

Now, is this the “best practice” for interacting with an Phoenix app from the outside?.

What do you mean by “this”?

To expose a REST endpoint so that other services or users can communicate with your app is a pretty standard way, yes.

Thanks, that answers my question.