Published on Nov 29 2023

Building a Chatbot with Elixir + Phoenix LiveView

Bottomright is an AI chatbot that trains itself by crawling your website. In this post, I’d like to share the thought process and some of the design decisions I made when building Bottomright.

Deciding the Tech Stack

When I started building Bottomright, I had two options in mind for the tech stack:

1. ReactJS Frontend + RESTful backend

The is probably a no-brainer, almost everybody is using it. But you know, with React, you end up building two applications actually:

  • The frontend in ReactJS (UI)
  • The backend API

To better explain, let’s take a step back and see what happens when you open a web page built with React:

  • Server returns a simple html with a link to JS (React app) and an empty container div.
  • Your browser runs the JS and that renders the UI in that container.

But there’s also a third step, the content, this is all the data that is displayed in the UI. Where do you get that from? Well, your server, and this is where the API comes in picture and also those loading spinners.

The whole point of using JS is to make the content dynamic, i.e the content of the page can change in response to user interaction.

2. Elixir + Phoenix LiveView

Phoenix LiveView takes a different approach. In this case, the server returns the HTML with the content baked in (aka Server Side Rendering)

But what about dynamic content? Hehe, JS is not going anywhere! LiveView also uses JS, but a tiny amout of it. Here’s what happens:

  • Server returns the HTML + Content and also a link to a tiny JS
  • This JS creates a websocket connection with backend which serves the dynamic content

Here, dynamic content are small HTML fragments (deltas) that the server sends and which get added to the HTML, for example show a compose text area when you click a reply button.

So what are the benifits of this approach?

  1. Save Time: You build only one app that handles frontend and backend.
  2. Speed: Since the HTML has all the content baked in, the first time render is faster + no spinners.
  3. Business Logic: All your business logic resides on the server.

In addition, since there’s a websocket alive between your frontend and backend you get all the real-time functionality for free! Perfect to build a chatbot!

Embedding Chatbot in a Website

Now that I was leaning towards using LiveViews, there was one more thing I needed to know — how do I add my chatbot to someone else’s website?

To add any external content to a website, <iframe> is the way to go, think of it as putting a website inside another website. However, we certainly don’t want to ask someone to put a huge chunk of our code to their website. Instead, we ask them to put a small script — a launcher in the <head> section:

<script bottomright-id="xxx" src=""></script>

When the page loads, this script is executed and it creates and iframe and adds it to the page:

const iframe = document.createElement('iframe');
iframe.src = '';

With this in place anyone can add our chatbot to any website with a single line of code!

Render LiveView inside iframe

Next step was to render our chatbot UI with LiveView inside the iframe. For this we need to serve two paths from our Phoenix project:

  1. The launcher.js script: This can simply go in our static_paths
  2. The /chatbot/xxx for our iframe’s src: This will serve our LiveView, so in our router.ex:
    live_session :chatbot do
    scope "/chatbot", MyAppWeb do
     pipe_through [:browser, :csrf, :liveview]
     live "/:id", ChatbotLiveView

With this in place I just added the launch script tag to a different page my Phoenix app and I could see the dummy chatbot rendered in the iframe:

<!DOCTYPE html>
<html lang="en">
    <script bottomright-id="xxx" src="http://localhost:3000/launcher.js"></script>
    <p class="uppercase"><%= title() %></p>
    <%= @inner_content %>


Here’s a link to source code on GitHub