- v1.1.5Latest
- v1.1.4
- v1.1.3
- v1.1.2
- v1.1.1
- v1.1.0
- v1.0.0
- v1.0.0-rc11
- v1.0.0-rc10
- v1.0.0-rc9
- v1.0.0-rc8
- v1.0.0-rc7
- v1.0.0-rc6
- v1.0.0-rc5
- v1.0.0-rc4
- v1.0.0-rc3
- v1.0.0-rc2
- v0.1.4-beta.0
- v0.1.3-beta.1
- v0.1.3-beta.0
- v0.1.2-beta.0
- v0.1.1-beta.0
- v0.1.0-alpha.3
- v0.1.0-alpha.2
- v0.1.0-alpha.1
- v0.1.0-alpha.0
- v0.0.37
- v0.0.36
- v0.0.35
- v0.0.34
- v0.0.33
- v0.0.32
- v0.0.31
- v0.0.30
- v0.0.29
- v0.0.28
- v0.0.27
- v0.0.26
- v0.0.25
- v0.0.24
- v0.0.23
- v0.0.22
- v0.0.21
- v0.0.20
- v0.0.15
- v0.0.14
- v0.0.13
- v0.0.12
- v0.0.11
- v0.0.10
- v0.0.9
- list
- v0.0.8
- v0.0.7
- v0.0.3
- 0.0.2
- v0.0.6
- v0.0.5
- v0.0.4
Soundify is a lightweight and flexible library for seamless communication with Spotify API, designed to work smoothly with TypeScript, Deno, Node.js, and client-side JavaScript. It’s open source and provides an easy-to-use interface for accessing Spotify’s data.
Features ✨
- 💻 Multiruntime: Works seamlessly with Node.js, Deno, and the Browser environment.
- 🚀 Modern: Leverages native web APIs like
fetch
,crypto
,URLSearchParams
, etc. - 🔑 Comprehensive auth support: It can handle all Spotify Auth flows and automatically refreshes access token.
- 📦 Lightweight and treeshakable: It is designed with care for your bundle size.
- 🆎 Strictly typed: All entities returned by the api have exact and up to date types.
- 📖 Great docs: The library comes with extensive documentation and lots of examples.
Installation
NPM
Packages:
- @soundify/api - Provides client, endpoints and entity types. Can be used both in the browser and in nodejs
- @soundify/web-auth - Spotify authorization for browser
- @soundify/node-auth - Spotify authorization for nodejs
The only difference in
web-auth
andnode-auth
packages is that under the hood they use different platform api to perform the same tasks. The use of the library api remains the same.
This is minified bundle size of each package without treeshaking
Deno
Deno is straightforward, you can just import the package from deno.land and use all the functionality.
import { ... } from "https://deno.land/x/soundify/mod.ts"
Gettings started
To make your first request with Soundify you need to create a SpotifyClient
. As the first parameter it takes access token or AuthProvider.
import { SpotifyClient } from "@soundify/api";
const client = new SpotifyClient("ACCESS_TOKEN");
If you’ve used other api libraries, you can expect something like a bunch of methods on a single class, but in our case the default recommendation is to use endpoint functions that take the client as the first argument. In practice, it looks like this:
import { SpotifyClient, getCurrentUser } from "@soundify/api";
const client = new SpotifyClient("ACCESS_TOKEN");
const user = await getCurrentUser(client);
console.log(user);
If your Access Token is valid it will output something like this
{
"id": "31xofk5q7l22rvsbff7yiechyx6i",
"display_name": "Soundify",
"type": "user",
"uri": "spotify:user:31xofk5q7l22rvsbff7yiechyx6i",
// etc...
}
This may be inconvenient for some users, but it was done primarily to allow tree sharding so that clients don’t send a lot of unused code.
But, if you are writing a backend or don’t care about the size of the library you can use the createSpotifyAPI()
function which will bind all the endpoint functions to the client. That way you can use this object throughout your application and not have to worry about imports.
import { SpotifyClient, createSpotifyAPI } from "@soundify/api";
const api = createSpotifyAPI("ACCESS_TOKEN");
const user = await api.getCurrentUser();
console.log(user);
Authorization
If you have no experience with Spotify Authorization you can read more about it in the Spotify Authorization Guide.
There are four authorization flows that can be used in Spotify, and all of them are supported in this library 🔥. The criteria for choosing the right flow for you are described in the Spotify docs linked above.
Authorization Code flow
With this flow user grants permission only once, after which you can use refresh token to create a new access token. The flow is used on the server because it requires SPOTIFY_CLIENT_SECRET, which is not desirable to show to others.
Pseudo http-server code just for example
import { AuthCode } from "@soundify/node-auth";
const authFlow = new AuthCode({
client_id: "YOUR_CLIENT_ID",
client_secret: "YOUR_CLIENT_SECRET",
});
const loginHandler = async (req, res) => {
const authURL = authFlow.getAuthURL({
redirect_uri: "YOUR_REDIRECT_URI",
scopes: ["user-read-email"],
});
res.redirect(302, authURL.toString());
};
const codeHandler = async (req, res) => {
try {
const code = new URL(req.url).searchParams.get("code");
if (!code) throw new Error("Unable to find 'code'");
const { access_token, refresh_token } = await authFlow.getGrantData(
"YOUR_REDIRECT_URI",
code
);
res.cookie("refresh_token", refresh_token);
res.status(200).json({ access_token });
} catch (err) {
res.status(500).json({ error: err.message });
}
};
const refreshHandler = async (req, res) => {
try {
const { refresh_token } = req.cookies;
const { access_token } = await authFlow.refresh(refresh_token);
res.status(200).json({ access_token });
} catch (err) {
res.status(500).json({ error: err.message });
}
};
It is also recommended to use
state
, which provides protection against attacks such as cross-site request forgery, but in the examples below we will not use it for simplicity.
Real code examples with AuthCode flow:
Authorization Code flow with PKCE
This thread is similar to AuthCode, but it is handled on the client and therefore does not require SPOTIFY_CLIENT_SECRET.
import { PKCEAuthCode } from "@soundify/web-auth";
const authFlow = new PKCEAuthCode("YOUR_CLIENT_ID");
const authorize = async () => {
const { code_challenge, code_verifier } = await PKCEAuthCode.generateCodes();
localStorage.setItem("code_verifier", code_verifier);
location.replace(
authFlow.getAuthURL({
code_challenge,
scopes: ["user-read-email"],
redirect_uri: "YOUR_REDIRECT_URI",
})
);
};
const codeHandler = async () => {
const data = PKCEAuthCode.parseCallbackData(
new URLSearchParams(location.search)
);
if ("error" in data) {
throw new Error(data.error);
}
const code_verifier = localStorage.getItem("code_verifier");
if (!code_verifier) {
throw new Error("Cannot find code_verifier");
}
const { refresh_token, access_token } = authFlow.getGrantData({
code: data.code,
code_verifier,
redirect_uri: "YOUR_REDIRECT_URI",
});
localStorage.removeItem("code_verifier");
localStorage.setItem("refresh_token", refresh_token);
localStorage.setItem("access_token", access_token);
};
const refreshHandler = () => {
const refreshToken = localStorage.getItem("refresh_token");
const { access_token, refresh_token } = authFlow.refresh(refreshToken);
localStorage.setItem("refresh_token", refresh_token);
localStorage.setItem("access_token", access_token);
};
Real code examples with PKCEAuthCode flow:
Client Credentials flow
This flow is used in server-to-server authentication. Since this flow does not include authorization, only endpoints that do not access user information can be accessed.
import { ClientCredentials } from "@soundify/node-auth";
const authFlow = new ClientCredentials({
client_id: "YOUR_CLIENT_ID",
client_secret: "YOUR_CLIENT_SECRET",
});
const { access_token } = await authFlow.getAccessToken();
Real code examples with ClientCredentials flow:
Implicit Grant flow
The implicit grant flow is carried out on the client side and it does not involve secret keys. Access tokens issued are short-lived with no refresh token to extend them when they expire.
As from Spotify docs: “The implicit grant flow has some important security flaws, thus we don’t recommend using this flow. If you need to implement authorization where storing your client secret is not possible, use Authorization code with PKCE instead.”
import { ImplicitGrant } from "@soundify/web-auth";
const authFlow = new ImplicitGrant("YOUR_CLIENT_ID");
const authorize = () => {
const state = crypto.randomUUID();
localStorage.setItem("state", state);
location.replace(
authFlow.getAuthURL({
scopes: ["user-read-email"],
state,
redirect_uri: "YOUR_REDIRECT_URI",
}),
);
}
const handleCallback = () => {
const data = ImplicitGrant.parseCallbackData(location.hash);
if ("error" in data) {
throw new Error(data.error);
}
const storedState = localStorage.getItem("state");
if (!storedState || !params.state || storedState !== params.state) {
throw new Error("Invalid state");
}
localStorage.removeItem("state");
localStorage.setItem("access_token", data.access_token);
}
Real code examples with ImplicitGrant flow:
Auth provider and automatic tokens refreshing
As you saw earlier, you can simply pass the Access Token to SpotifyClient. But after some time (1 hour to be exact), it will expire and you’ll need to deal with it yourself. Somehow get a new Access Token and set it on the client.
import { SpotifyClient } from "@soundify/api";
import { AuthCode } from "@soundify/node-auth";
const authFlow = new AuthCode({ ... });
const client = new SpotifyClient("ACCESS_TOKEN");
// ...
// Oops, token expires :(
const { access_token } = await authFlow.refresh("REFRESH_TOKEN");
// set new token to your client
client.setAuthProvider(access_token);
But if you don’t want to deal with all that, you can just create an
AuthProvider
and pass it instead of the Access Token. It will automatically refresh your token.
import { SpotifyClient } from "@soundify/api";
import { AuthCode } from "@soundify/node-auth";
const authFlow = new AuthCode({ ... });
const authProvider = authFlow.createAuthProvider("YOUR_REFRESH_TOKEN");
const client = new SpotifyClient(authProvider);
You can create an AuthProvider
from AuthCode
, PKCEAuthCode
,
ClientCredentials
flows. Implicit grant does not allow to implement such
because you have to refresh the page to get a new token.
Also you can create your own AuthProvider from AuthProvider
class.
import { AuthProvider } from "@soundify/api";
const authProvider = new AuthProvider({
refresher: async () => {
// somehow refresh and get new `access_token`
return { access_token };
}
});
const client = new SpotifyClient(authProvider);
Refresh Events
AuthProvider provides an additional option for callback events that may be usefull in some cases.
import { AuthProvider } from "@soundify/api";
const authProvider = new AuthProvider({
refresher: async () => {
// ...
},
onRefreshSuccess: ({ access_token }) => {
// do something with new token
// for example, store it in localStorage
localStorage.setItem("access_token", access_token);
},
onRefreshFailure: (error) => {
// do something with error
// for example, ask user to login again
location.replace(PKCEAuthCode.getAuthURL({ ... }));
},
});
Auth Scopes
Scopes are usually used when creating authorization url. Pay attention to them, because many fields and endpoints may not be available if the correct scopes are not specified. Read the Spotify guide to learn more.
In Soundify scopes can be used as strings or with const object SCOPES
.
import { SCOPES, AuthCode } from "@soundify/node-auth";
AuthCode.getAuthURL({
scopes: ["user-read-email"],
// or like this
scopes: [SCOPES.USER_READ_EMAIL]
// or use all scopes
scopes: Object.values(SCOPES),
})
All contributions are very welcome ❤️