Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/mellkam/soundify
π§ Lightweight integration with the Spotify Web API for modern Javascript runtimes
https://github.com/mellkam/soundify
api deno music sdk soundify spotify typescript web
Last synced: 19 days ago
JSON representation
π§ Lightweight integration with the Spotify Web API for modern Javascript runtimes
- Host: GitHub
- URL: https://github.com/mellkam/soundify
- Owner: MellKam
- License: mit
- Created: 2023-01-29T15:17:51.000Z (almost 2 years ago)
- Default Branch: main
- Last Pushed: 2024-03-22T10:07:40.000Z (8 months ago)
- Last Synced: 2024-04-14T06:52:26.962Z (7 months ago)
- Topics: api, deno, music, sdk, soundify, spotify, typescript, web
- Language: TypeScript
- Homepage: https://npmjs.com/@soundify/web-api
- Size: 820 KB
- Stars: 26
- Watchers: 3
- Forks: 2
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
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.```bash
npm install @soundify/web-api
``````jsonc
// deno.json
{
"imports": {
"@soundify/web-api": "https://deno.land/x/soundify/mod.ts"
}
}
```Install from [JSR registry](https://jsr.io/@soundify/web-api)
```bash
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.```ts
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.```ts
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 π
```ts
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; // 401const 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β](https://developer.spotify.com/documentation/web-api/concepts/rate-limits)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)```ts
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](https://github.com/panva/oauth4webapi) or
[oidc-client-ts](https://github.com/authts/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β](https://github.com/MellKam/soundify/tree/main/examples/oauth)### Token Refreshing
```ts
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.```ts
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();
``````ts
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
```ts
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](https://allcontributors.org/docs/en/emoji-key))
Artem Melnyk
π§
danluki
π»
Andrii Zontov
π
Brayden Babbitt
π