Happy Little Projects: Elixir, Phoenix, Twilio, and the Spotify API

As developers, it‘s easy to get stuck in a rut of using the same familiar languages, frameworks, and tools for every project. While there‘s nothing wrong with playing to your strengths, stepping outside your comfort zone to try something new is a great way to expand your skill set and discover better ways of doing things. Personally, I find that bite-sized side projects are the perfect vehicle for learning and experimentation. Recently, I challenged myself to recreate a simple JavaScript/Node.js app in Elixir and Phoenix. The results were impressive and left me eager to dive deeper into this powerful language and framework.

Why Elixir and Phoenix?

Elixir is a dynamic, functional language built on top of the battle-tested Erlang virtual machine (BEAM). With a Ruby-like syntax and excellent support for concurrency, fault-tolerance, and distributed systems, Elixir is a joy to work with. Phoenix is a modern web framework that leverages the power of Elixir and the BEAM to enable super fast, reliable and scalable applications. Features like real-time functionality via WebSockets and server-side rendering are available right out of the box.

While relatively new, Elixir and Phoenix have already proven themselves in production at companies like Bleacher Report, Inverse, and PagerDuty. The fantastic documentation, helpful community, and strong core team behind these tools make them a pleasure to learn and inspire confidence for building real-world applications.

The "Twilio Sings" App

The app we‘ll be building is a simple one – users can text the name of a song to a Twilio phone number, and the app will call them back and play a preview clip of the song fetched from the Spotify API. We‘ll be using Elixir and Phoenix on the backend to handle the interaction with Twilio and Spotify.

Here‘s a high-level overview of how it works:

  1. User texts a song name to our Twilio number
  2. Twilio sends a webhook to our app with the song name and user‘s phone number
  3. Our app searches the Spotify API for the song and extracts a preview MP3 url
  4. Our app tells Twilio to call the user and responds with TwiML to play the song preview
  5. Twilio calls the user and plays back the song clip

Simple, but lots of fun! Let‘s walk through how to build it step-by-step.

Setting Up the Project

First, make sure you have Elixir and Phoenix installed. Refer to the Elixir and Phoenix websites for guides on getting set up for your operating system.

In a terminal, navigate to the folder where you want to create the project and run:

$ mix phx.new twilio_sings

This will generate a new Phoenix project called twilio_sings. Follow the prompts to install dependencies and create the project folder.

Change into the project directory:

$ cd twilio_sings

We‘ll also need a way to expose our local development server to the internet so Twilio can send requests to our app. For this we‘ll use ngrok, which you can install from the website or via npm:

$ npm install -g ngrok

To start the ngrok tunnel, run:

$ ngrok http 4000

This will expose port 4000, which is the default port Phoenix uses in development. Make note of the forwarding URL provided (it will look something like http://abc123.ngrok.io). We‘ll need this later to configure the webhook URL in our Twilio account.

Handling the Webhook

When Twilio receives an SMS to our phone number, it will send an HTTP POST request to our app with information about the message. To handle this, we need to set up a route and controller action.

Open up lib/twilio_sings_web/router.ex and add a new entry for the /sms path:

scope "/", TwilioSingsWeb do
  pipe_through :api

  post "/sms", SMSController, :create
end

This tells Phoenix to route POST requests to /sms to the create action of the SMSController.

Now let‘s create that controller. Make a new file lib/twilio_sings_web/controllers/sms_controller.ex:

defmodule TwilioSingsWeb.SMSController do
  use TwilioSingsWeb, :controller

  def create(conn, params) do
    song_name = params["Body"] 
    from_number = params["From"]

    # search Spotify for song and get preview URL  
    {:ok, track} = Spotify.search_track(song_name)
    preview_url = track.preview_url

    # respond with TwiML to tell Twilio to call user and play song  
    twiml = """
      <?xml version="1.0" encoding="UTF-8"?>
      <Response>  
        <Say>Here is a preview of #{song_name}</Say>
        <Play>#{preview_url}</Play>
      </Response>
    """

    conn
    |> put_resp_content_type("application/xml")
    |> text(twiml)
  end
end

Walking through this:

  • We extract the song name and user‘s number from the params sent by Twilio
  • We search the Spotify API for the song (we‘ll implement this next) and get the URL for a 30 second preview MP3
  • We generate a TwiML (Twilio Markup Language) response that tells Twilio to call the user and play the song preview
  • We set the response content type to application/xml and return the TwiML

Searching Spotify

To search Spotify and get the track information, we‘ll use the handy spotify_ex library. Add it to your dependencies in mix.exs:

defp deps do
  [
    {:spotify_ex, "~> 2.0.9"}
  ]
end

And run mix deps.get to install it.

Next, create a file lib/twilio_sings/spotify.ex and add the following code:

defmodule TwilioSings.Spotify do
  def search_track(name) do
    token = get_token()

    case Spotify.Search.query(token, "track", name, limit: 1) do
      {:ok, %{"tracks" => %{"items" => [%{"preview_url" => preview_url} = track]}}} ->
        {:ok, track |> Map.put(:preview_url, preview_url)}

      _ ->
        {:error, "No matching track found"} 
    end
  end

  defp get_token do
    client_id = System.get_env("SPOTIFY_CLIENT_ID")
    client_secret = System.get_env("SPOTIFY_CLIENT_SECRET")   

    case Spotify.Authentication.Client.get_token(client_id, client_secret) do
      {:ok, %{"access_token" => access_token}} -> access_token
      _ -> raise "Failed to authenticate with Spotify" 
    end
  end
end

Some key points:

  • The search_track function takes a song name, looks it up using the Spotify API, and returns a simplified version of the track data containing just the preview URL
  • We use Spotify.Search.query to perform the search, limiting to 1 result since we only care about the top hit
  • The search requires an access token, so we define a get_token helper to authenticate with our Spotify client ID and secret
  • We use System.get_env to read the client ID/secret from environment variables, so be sure to set SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET before running the app. You can get these by creating an app in the Spotify developer dashboard.

Responding with TwiML

The final piece is the TwiML response we send back to Twilio to direct it to call the user and play the song preview. We built this in the controller using Elixir‘s multi-line string syntax, but we can clean it up a bit by extracting it to a template.

Create a file lib/twilio_sings_web/views/sms_view.ex:

defmodule TwilioSingsWeb.SMSView do
  use TwilioSingsWeb, :view
end

And a corresponding template file lib/twilio_sings_web/templates/sms/twiml.xml.eex:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say>Here is a preview of <%= @song_name %></Say> 
  <Play><%= @preview_url %></Play>
</Response>

Now update the controller to render the template instead of inlining the TwiML:

# lib/twilio_sings_web/controllers/sms_controller.ex

def create(conn, params) do  
  song_name = params["Body"]
  from_number = params["From"]

  {:ok, track} = Spotify.search_track(song_name)
  preview_url = track.preview_url

  conn
  |> put_resp_content_type("application/xml") 
  |> render("twiml.xml", song_name: song_name, preview_url: preview_url)  
end

Much nicer!

Try it out

At this point, we have a working version of our song preview app. To test it out:

  1. In your Twilio account, configure your phone number‘s SMS webhook to point to your ngrok URL (http://your-ngrok-url.ngrok.io/sms)

  2. Make sure you have set your SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET environment variables

  3. Start the Phoenix server with mix phx.server

  4. Send a text to your Twilio number with the name of a song

  5. Wait a few seconds, and you should receive a call playing the song preview!

How cool is that? With less than 100 lines of Elixir, we have a fully functioning app that combines two powerful APIs to create a novel experience.

Next Steps

There are tons of ways you could extend this basic application:

  • Add some validation and error handling to deal with missing preview URLs or invalid search terms
  • Allow users to specify an artist in addition to a song name for more accurate searches
  • Implement a database to keep track of previous searches and let users request replays
  • Enhance the TwiML response with further information about the track

I encourage you to experiment and see what you can come up with!

Why Bother?

You might be thinking, "This is a neat proof of concept, but why go to the trouble of learning a whole new language and framework just to build something I could throw together in a few minutes with Node.js/Express?"

It‘s a fair question, and the truth is, for a tiny app like this it probably doesn‘t matter a whole lot which tech stack you choose. However, as your applications grow in size and complexity, the virtues of a language like Elixir and a framework like Phoenix start to shine through:

  • Elixir‘s functional style and pattern matching make your code cleaner, more expressive, and easier to reason about
  • Immutable data structures and message passing avoid the headaches of accidental mutation and complex shared state
  • The BEAM virtual machine provides unparalleled support for concurrency, letting you build highly scalable distributed systems without breaking a sweat
  • Phoenix‘s realtime features, easy testing, and solid conventions allow you to be insanely productive

Beyond the technical benefits, learning new languages and paradigms also helps make you a more well rounded developer. You absorb fresh ideas and approaches that you can bring back to your usual toolset.

I was pleasantly surprised at how quickly I took to Elixir and Phoenix and how natural they felt after an initial adjustment period. The excellent documentation and helpful community smoothed out the learning curve, and I came away impressed by the thought and craftsmanship that have gone into the language and framework. I‘m excited to continue using them for side projects, and could even see myself reaching for them over my old standbys for certain production applications.

So if you find yourself with some spare time and want to branch out, I highly recommend giving Elixir and Phoenix a look. Build yourself a happy little project, have fun, and expand your horizons a bit. It‘s amazing how good it feels to add a new tool to your belt!

Resources

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *