Spotify Web API TypeScript SDK

repository·main·Indexed 19 days ago

https://github.com/spotify/spotify-web-api-ts-sdk

A TypeScript/JavaScript SDK for the Spotify Web API providing type-safe access to endpoints. It supports multiple authentication flows, including PKCE for browsers and Client Credentials for servers. The SDK is compatible with Node.js (v18.0.0+) and modern browsers, supporting both ESM and CommonJS builds. It features a configurable architecture allowing developers to implement custom authentication strategies, caching, error handling, and response validation.

Tokens
10.2K
Snippets
33
Records
43
Agent score
67%

What's inside @spotify/web-api-ts-sdk

  1. Implement Mixed Server and Client Side Authentication

    main

    Use this pattern if you want your Node.js server to interact with Spotify 'as a specific user' by performing a client-side Authorization Code Flow with PKCE and passing the resulting token to your server.

    1. Client Side: Trigger Authorization

    Use performUserAuthorization to trigger the redirect and handle the token exchange. You can provide a callback for custom post-back logic.

    // Redirect to a specific backend endpoint
    SpotifyApi.performUserAuthorization("client-id", "https://localhost:3000", ["scope1"], "https://your-backend-server.com/accept-user-token");
    
    // OR use a custom callback
    SpotifyApi.performUserAuthorization("client-id", "https://localhost:3000", ["scope1"], (accessToken) => {
        /* perform custom postback here */
    });

    2. Server Side: Accept Token

    Create an endpoint on your server to receive the token and initialize the SDK instance.

    const { SpotifyApi } = require("@spotify/web-api-ts-sdk");
    const express = require('express');
    const bodyParser = require('body-parser'); 
    const app = express();
    
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));
    
    let sdk;
    
    app.post('/accept-user-token', (req, res) => {
        let data = req.body;
        // Initialize SDK as the specific user
        sdk = SpotifyApi.withAccessToken("client-id", data);
    }); 
    
    app.listen(3000);
    // Client Side
    SpotifyApi.performUserAuthorization("client-id", "https://localhost:3000", ["scope1"], "https://your-backend-server.com/accept-user-token");
    
    // Server Side
    app.post('/accept-user-token', (req, res) => {
        let data = req.body;
        sdk = SpotifyApi.withAccessToken("client-id", data);
    });
  2. Run the embedded example app

    main

    To run the embedded example app, you must create a .env file inside the ./example folder.

    Set the following environment variables in that file:

    • VITE_SPOTIFY_CLIENT_ID: Use the same value as INTEGRATION_TESTS_SPOTIFY_CLIENT_ID.
    • VITE_REDIRECT_TARGET: Set to http://localhost:3000.
    # Inside ./example/.env
    VITE_SPOTIFY_CLIENT_ID=your_client_id
    VITE_REDIRECT_TARGET=http://localhost:3000
  3. Run integration tests

    main

    To run integration tests, you must have a Spotify account and a registered app in the Spotify Developer portal with a redirect URI set to http://localhost:3000.

    Integration tests require a real user account to test endpoints requiring authorization (e.g., followPlaylist). You can obtain a refresh token by running the example app and authenticating.

    Set the following environment variables in a .env file at the root of the repository:

    • INTEGRATION_TESTS_REFRESH_TOKEN
    • INTEGRATION_TESTS_SPOTIFY_CLIENT_ID
    • INTEGRATION_TESTS_SPOTIFY_CLIENT_SECRET
    npm run test
  4. Extend the SDK via SdkConfiguration

    main

    All constructors and static initialization methods accept an optional SdkConfiguration object to override default behaviors.

    Default Configuration Schema

    const defaultConfig: SdkConfiguration = {
        fetch: (req: RequestInfo | URL, init: RequestInit | undefined) => fetch(req, init),
        beforeRequest: (_: string, __: RequestInit) => { },
        afterRequest: (_: string, __: RequestInit, ___: Response) => { },
        deserializer: new DefaultResponseDeserializer(),
        responseValidator: new DefaultResponseValidator(),
        errorHandler: new NoOpErrorHandler(),
        redirectionStrategy: new DocumentLocationRedirectionStrategy(),
        cachingStrategy: isBrowser
            ? new LocalStorageCachingStrategy()
            : new InMemoryCachingStrategy()
    };

    Customizing the SDK

    Pass your custom options object as the last argument to any factory method:

    const opts = {
        fetch: (req, init) => {
            console.log("Called via my custom fetch!");
            return fetch(req, init);
        }
    };
    
    const sdk = SpotifyApi.withUserAuthorization("client-id", "https://callback", ["scope1"], opts);
  5. Implement a custom Redirection Strategy

    main

    The IRedirectionStrategy interface is used to manage how the SDK handles redirects, particularly during authentication flows. You must implement two methods:

    • redirect(targetUrl: string | URL): Triggered when a redirect is required. This typically involves navigating the browser to the new URL.
    • onReturnFromRedirect(): Triggered when the application returns to the original context after a redirect flow has completed.
    export interface IRedirectionStrategy {
        redirect(targetUrl: string | URL): Promise<void>;
        onReturnFromRedirect(): Promise<void>;
    }
  6. Implement a custom Caching Strategy

    main

    The ICachingStrategy interface allows you to define how the SDK stores and retrieves cached API responses. This is useful for reducing network calls and improving performance.

    To implement it, you must provide:

    • getOrCreate<T>: A method to retrieve an item from cache or create it using a provided function if it doesn't exist.
    • get<T>: Retrieve an existing item by its cacheKey.
    • setCacheItem<T>: Manually add an item to the cache.
    • remove: Remove an item from the cache.

    Items stored in the cache should implement ICachable, which allows specifying an expires timestamp or an expiresOnAccess flag.

    export interface ICachingStrategy {
        getOrCreate<T>(
            cacheKey: string,
            createFunction: () => Promise<T & ICachable & object>,
            updateFunction?: (item: T) => Promise<T & ICachable & object>
        ): Promise<T & ICachable>;
    
        get<T>(cacheKey: string): Promise<T & ICachable | null>;
        setCacheItem<T>(cacheKey: string, item: T & ICachable): void;
        remove(cacheKey: string): void;
    }
    
    export interface ICachable {
        expires?: number;
        expiresOnAccess?: boolean;
    }
  7. Make requests to the Spotify Web API

    main

    Once authenticated, use the methods exposed on the SpotifyApi instance. The SDK includes built-in TypeScript types for full intellisense and type checking.

    const items = await sdk.search("The Beatles", ["artist"]);
    
    console.table(items.artists.items.map((item) => ({
        name: item.name,
        followers: item.followers.total,
        popularity: item.popularity,
    })));
  8. Implement a custom deserializer

    main

    Override the default deserialization logic by providing a class that implements the IResponseDeserializer interface. This is useful for custom logging or handling serialization failures.

    async deserialize<TReturnType>(response: Response): Promise<TReturnType> {
        // Implement your custom deserialization logic here
    }
  9. Implement a custom redirectionStrategy

    main

    Override how the SDK handles redirects (e.g., during OAuth flows) by implementing the IRedirectionStrategy interface. This is useful for Single Page Applications (React/Vue) where you want to manage state before a redirect occurs.

    export default class DocumentLocationRedirectionStrategy implements IRedirectionStrategy {
        public async redirect(targetUrl: string | URL): Promise<void> {
            document.location = targetUrl.toString();
        }
    
        public async onReturnFromRedirect(): Promise<void> {
            // Logic to run when user returns to the app
        }
    }
  10. Implement a custom errorHandler

    main

    Override the default error handling by implementing the IHandleErrors interface.

    Behavior Note:

    • If handleErrors returns true: The SDK treats the error as handled and returns null for the request that triggered it.
    • If handleErrors returns false: The SDK re-throws the original error after your handler has run.
    export default class MyErrorHandler implements IHandleErrors {
        public async handleErrors(error: any): Promise<boolean> {
            // Return true to swallow error, false to re-throw
            return false;
        }
    }