Socket
Socket
Sign inDemoInstall

@soundify/web-api

Package Overview
Dependencies
0
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

@soundify/web-api

🎧 Spotify Web API client for js/ts runtime environments


Version published
Maintainers
1
Weekly downloads
84
increased by281.82%

Weekly downloads

Readme

Source

npm Size of package (minified) Github stars jsr registry score

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
Artem Melnyk

🚧
danluki
danluki

💻
Andrii Zontov
Andrii Zontov

🐛

Keywords

FAQs

Last updated on 22 Mar 2024

Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc