How to serve JavaScript from a Phoenix View
Tags: elixir
Published: 2020-05-10 05:46:08

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!

## JavaScript!

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 

```elixir
defmodule Myapp.JSView do
  use MyappWeb, :view
end
```

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

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

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

```elixir
defmodule MyappWeb.JSController do
  use MyappWeb, :controller

  def index(conn, _params) do
    render(conn, "something.js", adjective: "awesome")
  end
end
```

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

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

  pipeline :js do
    plug :put_js_content_type
  end

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

  scope "/custom_js", MyappWeb do
    pipe_through :js
    get "/myjsfile.js", JSController, :index
  end
end
```

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

# DANGER

_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](https://owasp.org/www-community/attacks/xss/).

Phoenix has facilities to deal with (some) JavaScript data escaping. Refer to [javascript_escape](https://hexdocs.pm/phoenix_html/Phoenix.HTML.html#javascript_escape/1).

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.

# Conclusion 

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!