Built and signed on GitHub ActionsBuilt and signed on GitHub Actions
🎧 Spotify Web API client for js/ts runtime environments
Soundify is a lightweight and flexible library for interacting with the Spotify API, designed to work seamlessly with TypeScript and support all available runtimes.
Getting Started | Error handling | Token refreshing | Pagination
Installation
The package doesn't depend on runtime specific apis, so you should be able to use it without any problems everywhere.
npm install @soundify/web-api
// deno.json { "imports": { "@soundify/web-api": "https://deno.land/x/soundify/mod.ts" } }
Install from JSR registry
deno add @soundify/web-api
Getting Started
Soundify has a very simple structure. It consists of a SpotifyClient
capable
of making requests to the Spotify API, along with a set of functions (like
getCurrentUser
) that utilize the client to make requests to specific
endpoints.
import { getCurrentUser, search, SpotifyClient } from "@soundify/web-api"; const client = new SpotifyClient("YOUR_ACCESS_TOKEN"); const me = await getCurrentUser(client); console.log(me); const result = await search(client, "track", "Never Gonna Give You Up"); console.log(result.tracks.items.at(0));
Compared to the usual OOP way of creating API clients, this approach has several advantages. The main one is that it is tree-shakable. You only ship code you use. This may be not that important for server-side apps, but I'm sure frontend users will thank you for not including an extra 10kb of crappy js into your bundle.
import { getAlbumTracks, getArtist, getArtistAlbums, getRecommendations, SpotifyClient, } from "@soundify/web-api"; const client = new SpotifyClient("YOUR_ACCESS_TOKEN"); const radiohead = await getArtist(client, "4Z8W4fKeB5YxbusRsdQVPb"); console.log(`Radiohead popularity - ${radiohead.popularity}`); const pagingResult = await getArtistAlbums(client, radiohead.id, { limit: 1 }); const album = pagingResult.items.at(0)!; console.log(`Album - ${album.name}`); const tracks = await getAlbumTracks(client, album.id, { limit: 5 }); console.table( tracks.items.map((track) => ({ name: track.name, duration: track.duration_ms, })), ); const recomendations = await getRecommendations(client, { seed_artists: [radiohead.id], seed_tracks: tracks.items.map((track) => track.id).slice(0, 4), market: "US", limit: 5, }); console.table( recomendations.tracks.map((track) => ({ artist: track.artists.at(0)!.name, name: track.name, })), );
Error handling 📛
import { getCurrentUser, SpotifyClient, SpotifyError } from "@soundify/web-api"; const client = new SpotifyClient("INVALID_ACCESS_TOKEN"); try { const me = await getCurrentUser(client); console.log(me); } catch (error) { if (error instanceof SpotifyError) { error.status; // 401 const message = typeof error.body === "string" ? error.body : error.body?.error.message; console.error(message); // "Invalid access token" error.response.headers.get("Date"); // You can access the response here console.error(error); // SpotifyError: 401 Unauthorized (https://api.spotify.com/v1/me) : Invalid access token return; } // If it's not a SpotifyError, then it's some type of network error that fetch throws // Or can be DOMException if you abort the request console.error("We're totally f#%ked!"); }
Rate Limiting 🕒
If you're really annoying customer, Spotify may block you for some time. To know
what time you need to wait, you can use Retry-After
header, which will tell
you time in seconds.
More about rate limiting↗
To handle this automatically, you can use waitForRateLimit
option in
SpotifyClient
. (it's disabled by default, because it may block your code for
unknown time)
const client = new SpotifyClient("YOUR_ACCESS_TOKEN", { waitForRateLimit: true, // wait only if it's less than a minute waitForRateLimit: (retryAfter) => retryAfter < 60, });
Authorization
Soundify doesn't provide any tools for authorization, because that would require to write whole oauth library in here. We have many other battle-tested oauth solutions, like oauth4webapi or oidc-client-ts. I just don't see a point in reinventing the wheel 🫤.
Despite this, we have a huge directory of examples, including those for authorization. OAuth2 Examples↗
Token Refreshing
import { getCurrentUser, SpotifyClient } from "@soundify/web-api"; // if you don't have access token yet, you can pass null to first argument const client = new SpotifyClient(null, { // but you have to provide a function that will return a new access token refresher: () => { return Promise.resolve("YOUR_NEW_ACCESS_TOKEN"); }, }); const me = await getCurrentUser(client); // client will call your refresher to get the token // and only then make the request console.log(me); // let's wait some time to expire the token ... const me = await getCurrentUser(client); // client will receive 401 and call your refresher to get new token // you don't have to worry about it as long as your refresher is working console.log(me);
Pagination
To simplify the process of paginating through the results, we provide a
PageIterator
and CursorPageIterator
classes.
import { getPlaylistTracks, SpotifyClient } from "@soundify/web-api"; import { PageIterator } from "@soundify/web-api/pagination"; const client = new SpotifyClient("YOUR_ACCESS_TOKEN"); const playlistIter = new PageIterator( (offset) => getPlaylistTracks(client, "37i9dQZEVXbMDoHDwVN2tF", { // you can find the max limit for specific endpoint // in spotify docs or in the jsdoc comments of this property limit: 50, offset, }), ); // iterate over all tracks in the playlist for await (const track of playlistIter) { console.log(track); } // or collect all tracks into an array const allTracks = await playlistIter.collect(); console.log(allTracks.length); // Want to get the last 100 items? No problem const lastHundredTracks = new PageIterator( (offset) => getPlaylistTracks( client, "37i9dQZEVXbMDoHDwVN2tF", { limit: 50, offset }, ), { initialOffset: -100 }, // this will work just as `Array.slice(-100)` ).collect();
import { getFollowedArtists, SpotifyClient } from "@soundify/web-api"; import { CursorPageIterator } from "@soundify/web-api/pagination"; const client = new SpotifyClient("YOUR_ACCESS_TOKEN"); // loop over all followed artists for await ( const artist of new CursorPageIterator( (opts) => getFollowedArtists(client, { limit: 50, after: opts.after }), ) ) { console.log(artist.name); } // or collect all followed artists into an array const artists = await new CursorPageIterator( (opts) => getFollowedArtists(client, { limit: 50, after: opts.after }), ).collect(); // get all followed artists starting from Radiohead const artists = await new CursorPageIterator( (opts) => getFollowedArtists(client, { limit: 50, after: opts.after }), { initialAfter: "4Z8W4fKeB5YxbusRsdQVPb" }, // let's start from Radiohead ).collect();
Other customizations
import { SpotifyClient } from "@soundify/web-api"; const client = new SpotifyClient("YOUR_ACCESS_TOKEN", { // You can use any fetch implementation you want // For example, you can use `node-fetch` in node.js fetch: (input, init) => { return fetch(input, init); }, // You can change the base url of the client // by default it's "https://api.spotify.com/" beseUrl: "https://example.com/", middlewares: [(next) => (url, opts) => { // You can add your own middleware // For example, you can add some headers to every request return next(url, opts); }], });
Contributors ✨
All contributions are very welcome ❤️ (emoji key)
Artem Melnyk 🚧 |
danluki 💻 |
Andrii Zontov 🐛 |
Example 1
Example 1
import { SpotifyClient } from "@soundify/web-api"; // with access token const client = new SpotifyClient("YOUR_ACCESS_TOKEN"); // with automatic token refreshing const client = new SpotifyClient(null, { // your custom refresher here refresher: () => Promise.resolve("NEW_ACCESS_TOKEN"), }); const res = await client.fetch("/v1/me"); const user = await res.json();
Endpoints
Functions that utilize the SpotifyClient
to make requests to the Spotify Web API.
Example 2
Example 2
import { SpotifyClient, getCurrentUser } from "@soundify/web-api"; const client = new SpotifyClient("YOUR_ACCESS_TOKEN"); const user = await getCurrentUser(client); // How endpoint functions are built const getCurrentUser = async (client: SpotifyClient) => { const res = await client.fetch("/v1/me"); return await res.json(); }
Add Package
deno add jsr:@soundify/web-api
Import symbol
import * as web_api from "@soundify/web-api";
Import directly with a jsr specifier
import * as web_api from "jsr:@soundify/web-api";
Add Package
pnpm i jsr:@soundify/web-api
pnpm dlx jsr add @soundify/web-api
Import symbol
import * as web_api from "@soundify/web-api";
Add Package
yarn add jsr:@soundify/web-api
yarn dlx jsr add @soundify/web-api
Import symbol
import * as web_api from "@soundify/web-api";
Add Package
npx jsr add @soundify/web-api
Import symbol
import * as web_api from "@soundify/web-api";
Add Package
bunx jsr add @soundify/web-api
Import symbol
import * as web_api from "@soundify/web-api";