DEV Community

Bruno Luis Panuto Silva
Bruno Luis Panuto Silva

Posted on • Updated on

How to serve JavaScript from a Phoenix View

When we first hear and use Phoenix Views, it's primary use case is to render HTML or JSON views.

But that's not all Phoenix Views can do!


HTLM and JSON have their uses in web apps and APIs, for sure. But what if we are an API or web application providing a service not to a user or system, but to a browser?

A web browser can deal with different assets. But if you want it to do something, you have to speak JavaScript. There are some notable exceptions (WebAssembly?) but this is the de-facto way of getting code to a user's browser.

So, are we able to create a Phoenix View that renders JavaScript? Let's give it a shot.

In order to pull this off, we would need:

1) A Phoenix.View that is going to render our JavaScript file
2) A template, probably with an extension like .js.eex
3) A controller that sets up the correct Content-Type and renders this .js.eex template.

Let's try!

The View

defmodule Myapp.JSView do
  use MyappWeb, :view
Enter fullscreen mode Exit fullscreen mode

We're stating that the template is available in the directory configured by the :root property. By default, that would be myapp_web/templates/#{myresource}/#{mycontroller.html.eex}.

But we are not HTML.

The Template

# myapp_web/templates/js/something.js.eex
(function() {
  console.log('oh my, from Elixir? This is <%= @adjective %>!');
Enter fullscreen mode Exit fullscreen mode

As you can see it's a normal template. But there's a key difference: we're actually a .js.eex extension. This is just to signal that we're building a .js template, it is not dealt with differently by EEx.

And we have access to any assigns, as you can see when we read the @adjective. No surprises here!

So, how do we render it?

The Controller

defmodule MyappWeb.JSController do
  use MyappWeb, :controller

  def index(conn, _params) do
    render(conn, "something.js", adjective: "awesome")
Enter fullscreen mode Exit fullscreen mode

something.js gets mapped to the corresponding something.js.eex template we defined above. Not unlike building a "normal" HTML Phoenix View. Super clean!

There's only one last thing we need to do in order to correctly serve this file as valid JavaScript: the Content-Type.

# lib/myapp_web/router.ex
defmodule MyappWeb.Router do
  # lots of stuff above...

  pipeline :js do
    plug :put_js_content_type

  defp put_js_content_type(conn, _params) do
    put_content_type(conn, "text/javascript")

  scope "/custom_js", MyappWeb do
    pipe_through :js
    get "/myjsfile.js", JSController, :index
Enter fullscreen mode Exit fullscreen mode

So there you have it. Phoenix Views are great and really flexible!


Be Careful with how you deal with user input when pasting it inside JS code. You could be opening yourself up to a XSS attack! You can read more about XSS here.

Phoenix has facilities to deal with (some) JavaScript data escaping. Refer to javascript_escape.

In short: make sure that data you put on your file is not able to run arbitrary code. This is not meant to be a security guide, so viewer discretion is advised.


Granted, this is not your common use case. Usually, JS files are static and served from a CDN. But sometimes we need to step out of the ordinary, and it's amazing that it's so simple to do in Phoenix!

Top comments (0)