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:
- User texts a song name to our Twilio number
- Twilio sends a webhook to our app with the song name and user‘s phone number
- Our app searches the Spotify API for the song and extracts a preview MP3 url
- Our app tells Twilio to call the user and responds with TwiML to play the song preview
- 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 setSPOTIFY_CLIENT_ID
andSPOTIFY_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:
-
In your Twilio account, configure your phone number‘s SMS webhook to point to your ngrok URL (
http://your-ngrok-url.ngrok.io/sms
) -
Make sure you have set your
SPOTIFY_CLIENT_ID
andSPOTIFY_CLIENT_SECRET
environment variables -
Start the Phoenix server with
mix phx.server
-
Send a text to your Twilio number with the name of a song
-
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
- Official Elixir website: https://elixir-lang.org/
- Official Phoenix website: https://www.phoenixframework.org/
- "Programming Elixir" book: https://pragprog.com/titles/elixir16/programming-elixir-1-6/
- "Programming Phoenix" book: https://pragprog.com/titles/phoenix14/programming-phoenix-1-4/
- "Joy of Elixir" site: https://joyofelixir.com/
- Elixir School: https://elixirschool.com/
- Awesome Elixir (collection of Elixir libraries and resources): https://github.com/h4cc/awesome-elixir