When you’re learning about new concepts, it’s always helpful to have practical examples and projects to work on. The Spotify API is a great introduction to Web APIs in general. With it, you can easily build simple utilities that carry out various music-related tasks.
About the API
All APIs are limited by the functions and data their owner chooses to make available. But anything is better than nothing, and the Spotify API is perhaps surprisingly open and easy to use.
The API has two main components:
- A Web API that provides metadata about artists, tracks, and playlists.
- A Web Playback SDK that lets you embed a Spotify player within a web page.
In general, these are quite restricted; for example, Spotify states that you must not use the Playback SDK to develop commercial streaming apps. But there are still some interesting projects that you can create for your own use, and the Spotify API is a good example of web APIs in general, perfect for learning and practicing.
Setting Things Up
The most obvious thing you’ll need to build these sample apps is a Spotify account. Using it, you can log in to the Spotify for Developers Dashboard and start by creating an app.
You’ll also need a local web server, which you may already have set up. If not, it’s pretty easy to do so. Choose an appropriate location for your Spotify development, e.g., localhost/spotify or localhost:8080.
Meanwhile, back on the Spotify website, complete the “Create app” form. Make sure you select both the “Web API” and “Web Playback SDK” options and pick a redirect URI. For example, I’m hosting the “top” demo in a file named top.html, which is served at http://localhost:8080/top.html, so that’s one of my Redirect URIs. If you’re using a similar architecture for your demos, remember to add each new file as a Redirect URI.
Once you’ve created your app, take note of the Client ID and Client secret, both of which are shown on your app’s “Basic Information” page.
For production apps, you’ll need to take steps to keep your Client secret hidden. For this local demonstration, I’m just embedding the value in my JavaScript file, but you shouldn’t disclose your Client secret to anyone.
At this point, you may want to download the sample files, move them into your local document root, and start experimenting. I can only cover a fraction of the code in this article, so doing so will let you follow along with the process.
Authorizing With the API
The first step in creating your app is to authorise using OAuth. This is a popular standard for web app authorization, so it’s valuable to learn about it.
OAuth works by sending an access token over HTTP, and the process is established enough that helper libraries are available. Still, for our basic purposes, it’s easy to handle everything ourselves.
I’ve isolated this code in the access.js file. The first step is to redirect to Spotify’s authorization URL, which is https://accounts.spotify.com/authorize. You need to supply a few parameters to request the user’s permission to use your app:
- response_type: for the types of sample app we’re building, just use code.
- client_id: this is the Client ID that you noted during app setup.
- scope: a space-separated string of scopes to grant permission. For the two demos I’ll cover here, “streaming user-top-read” is good enough, but make sure to consult the full set of scopes if you’re trying out other endpoints.
- redirect_uri: a valid Redirect URI you have added to your app.
You should also use the state parameter for security reasons, but it’s fine to omit it during these demos.
In access.js, the authorize function looks like this:
function authorize() {
let base = "https://accounts.spotify.com/authorize",
params = {
response_type: "code",
client_id: CLIENT_ID,
scope: "streaming ...",
redirect_uri: REDIRECT_URI,
}; window.location.assign(base + "?" + new URLSearchParams(params));
}
This redirects the browser to a Spotify URL with the relevant parameters. When you view this URL, you’ll see something like the following:
A user will only need to grant permissions once, unless you change the scope. With permission granted, they’ll be sent to the redirect_uri, along with a code parameter. So they’ll end up at something like http://localhost:8080/top.html?code=… Your script should check for this parameter and proceed to the next step if it’s present:
let p = new URLSearchParams(window.location.search);if (p.has("code")) {
get_access_token(p.get("code"));
}
This second step involves sending the code you’ve just obtained back to Spotify in return for an access token. You’ll then use the access token in all further requests to the API.
The code in this article is simplified to keep the length down. Consult the full code in the GitHub repo for more details. In particular, you’ll also need to refresh the token when it expires.
async function get_access_token(code) {
let url = "https://accounts.spotify.com/api/token",
data = {
grant_type: "authorization_code",
code: code,
redirect_uri: REDIRECT_URI,
},
headers = {
"content-type": "application/x-www-form-urlencoded",
Authorization: "Basic " + btoa(CLIENT_ID + ":" + CLIENT_SECRET),
}; let opts = {
method: "post",
headers: headers,
body: get_post_data(data),
};
const response = await fetch(url, opts),
token = await response.json();
if (token.access_token) {
localStorage.setItem("access-token", token.access_token);
} else {
console.error("Some kind of error getting access token", token);
}
}
You can use the local storage API to keep a record of the access token and fetch it in the future:
let token = localStorage.getItem("access-token");
You should now send the access token in an Authorization header for all Spotify API requests:
async function spotify_api_get(url) {
let headers = {
Authorization: "Bearer " + at.access_token,
}; let opts = { headers: headers, method: "GET" };
const response = await fetch(url, opts);
return await response.json();
}
This function takes up the entire spotify.js file and handles GET requests only. You could extend this file and write similar functions for methods like PUT and POST when the endpoint you need to use requires them.
OAuth is quite a complex subject, which is beyond the scope of this article. You can use a simple version for experimentation; just remember that you’ll need to manually reset the token if it expires (see localStorage.removeItem).
With everything set up and the client authorized, you’re finally ready to start using the API. Fetching metadata is a bit more straightforward, so I’ll begin with the “top” sample (top.html), which shows you your top artists, along with a bit of information about them.
Start by consulting the Spotify API documentation, which explains how to use the /me/top/artists endpoint. You can keep this simple by avoiding additional parameters and focusing on the response object, which contains details of your top artists in its items property.
In top.js, the run() function contains the code to fetch and display this data. It’s very straightforward: first off, it calls the API endpoint and stores the object returned:
let top_artists = await spotify_api_get(
"https://api.spotify.com/v1/me/top/artists",
at.access_token,
);
Next, it iterates over the items in the response:
top_artists.items.forEach(function (artist) {
});
And, inside that loop, it adds a table row containing data for each item:
tr = tbody.appendChild(document.createElement("tr"));
td = tr.appendChild(document.createElement("td"));
td.appendChild(document.createTextNode(artist.name));
The result is a simple table with your top artists and their associated genres; mine looks like this:
Using the Spotify Player
Metadata is interesting, but a functioning Spotify player is perhaps more useful. Although there are limitations, the API does essentially give you full control over playback, so—with a bit of work—you can build your own client.
The biggest friction point is the API’s insistence that you run Spotify at the same time as your own client. This shouldn’t be too much of a problem for your development, but it may limit the potential apps you can develop. You’ll need to explicitly connect the Spotify app to your API client, as a device:
What’s more, you’ll need to do this every time you refresh your web app, which can be annoying. I’ve added an error message to the demo, which reminds you to do this; it eases the pain very slightly!
The player.html file presents a simple client with album artwork, playback controls, and a progress indicator that lets you seek. Most of the relevant functionality lives in player.js.
The first thing to do is to insert Spotify’s SDK script:
<script src="https://sdk.scdn.co/spotify-player.js"></script>
In the demo, I inject this dynamically. This ensures that the script can check for an access token first, obtaining one if necessary. Without a valid access token, the SDK script can generate errors.
Next, the script creates a new Player instance and attempts to connect to it:
window.onSpotifyWebPlaybackSDKReady = () => {
player = new Spotify.Player({
name: "bobbykjack spotify client",
getOAuthToken: (cb) => {
cb(at.access_token);
},
volume: 0.5,
}); setup_player_state_event();
player.connect().then((success) => {
if (success) {
check_device_connected();
} else {
console.error("Oops, failed to connect to spotify");
}
});
};
Note that the player needs to be fed a valid access token, along with a name that will appear in the Spotify App as a device to connect to. The check_device_connected and check_player_state methods poll for a device connection; without one, the script will display the “Please connect” error I mentioned earlier.
When a Player object is set up and connected, you can call additional methods on it to load tracks and control playback. These methods are detailed in the Web Playback SDK Reference docs. The sample player uses several of these: pause, seek, resume, togglePlay, and getCurrentState.
Some of the functionality just requires connecting a button on the page to a corresponding SDK method. For example, here’s the code for the play button:
play.addEventListener("click", function (ev) {
player.resume().then(() => {
play.setAttribute("disabled", "1");
pause.removeAttribute("disabled");
});
});
When clicked, the button calls the Player.resume method, then disables itself and enables the pause button.
Some functionality is a bit more complicated. When a track changes, the player_state_changed event calls a method of the same name, which updates the display, changing the album artwork, for example:
let src = get_image(arg.track_window.current_track.album.images, 300),
set_attr("#currently_playing div.track-meta img", "src", src);
To show current track progress, the code polls the Player.getCurrentState method. This returns an object containing various details about playback, including the current track position:
function check_player_state(state) {
update_time(".position", state.position);
}
Finally, track position can be changed by dragging the progress input:
document
.querySelector("#track-progress")
.addEventListener("input", handle_seek_input);function handle_seek_input(ev) {
if (seek_timer) {
window.clearTimeout(seek_timer);
}
seek_timer = window.setTimeout(function () {
seek_to_ms(ev.target.value);
}, 200);
}
function seek_to_ms(position) {
player.pause().then(function () {
player.seek(position).then(function () {
player.resume();
update_time(".position", position);
});
});
}
Note that a timer is used to inject an artificial—but small—delay before calling the Player.seek method. Without this delay, dragging the progress bar for a brief period might result in hundreds of calls to the API; this is best avoided.
Like many APIs, Spotify uses rate limits to ensure you don’t overwhelm the service with too many requests. It’s quite forgiving, though, and you shouldn’t run into any problems with these samples; check your console for 429 errors if anything doesn’t work as expected.
The final app presents a basic player that synchronizes with Spotify:
Hopefully, you can learn from these simple demos and begin to write your own code to work with the Spotify API. It might seem limited, but there are lots of tweaks you could make to a simple client to improve your Spotify experience.
I’ve always felt that playlists should be able to contain full albums, so that’s a feature I’ll definitely be working on soon. You could also investigate other APIs, like YouTube, to combine data with a Spotify player or metadata, and build an app that presents the best of both.