expo-router-spotify Demo

repository·main·Indexed 41 days ago

https://github.com/evanbacon/expo-router-spotify

A demo project showcasing the use of React Server Components (RSC) in Expo Router to securely authenticate with the Spotify API and fetch data at the edge. It features a secure token exchange flow using server functions, automatic token refreshing, and a specialized API client implementation including `createSpotifyAPI` and `SpotifyClientAuthProvider`.

Tokens
6K
Snippets
21
Records
24
Agent score
51%

What's inside expo-router-spotify

  1. UI implementation details

    main

    The user interface is built using several specialized libraries and custom implementations:

    • Base Components: Uses the expo-router-forms-components template.
    • Native UI: Includes custom header animations for improved personality on mobile.
    • Web UI: Features a custom header optimized for desktop browsing and a customized version of vaul for the authentication modal to better suit desktop screens.
  2. How data fetching and memoization work

    main

    This project utilizes React Server Components (RSC) and Server Actions to manage data flow:

    • Server-side Fetching: Data is fetched on the server and passed to client components for rendering. This allows for streaming data back to iOS, Android, and web clients.
    • Composability: Server actions make data-fetching sections composable. For example, search functionality is implemented as an inline element on native platforms but as a standalone route on the web.
    • Memoization: The React compiler is enabled to memoize React Server Function calls. This ensures that server calls only trigger when the arguments of a server function change, reducing unnecessary server load.
  3. How authentication works in this demo

    main

    The authentication flow combines client-side session management with secure server-side token exchange:

    1. Client-side Setup: Uses expo-auth-session with a global provider. Results are stored using a localStorage polyfill.
    2. Secure Exchange: The code exchange is performed within a React Server Function using the SPOTIFY_CLIENT_SECRET to obtain a long-lived access token.
    3. Automatic Token Refresh: Server functions are wrapped in a helper layer that automatically refreshes the access token if it becomes invalid. This abstraction allows the authentication system to scale across all requests without manual handling in individual components.
  4. Configure environment variables for Spotify API

    main

    To use this project, you must provide Spotify API credentials in a .env file. These credentials can be obtained from the Spotify developer portal.

    Note that SPOTIFY_CLIENT_SECRET is used exclusively on the server and is never included in the client bundle, ensuring your API remains secure.

    EXPO_PUBLIC_SPOTIFY_CLIENT_ID=xxx
    SPOTIFY_CLIENT_SECRET=xxx
  5. Set up the SpotifyClientAuthProvider

    main

    To manage Spotify authentication state across your application, wrap your component tree with SpotifyClientAuthProvider. This provider handles token persistence in localStorage, automatic token refreshing, and provides access to authentication state via a context hook.

    Props

    • config: An AuthRequestConfig object (from expo-auth-session) containing required authentication details like redirectUri.
    • children: The React nodes to be wrapped.
    • cacheKey (optional): A string used as the key for storing the access token in localStorage. Defaults to "spotify-access-token".
    import { SpotifyClientAuthProvider } from "@/lib/spotify-auth/spotify-client-provider";
    
    function App() {
      const authConfig = {
        clientId: 'YOUR_CLIENT_ID',
        redirectUri: 'your-app://redirect',
        // ... other AuthRequestConfig properties
      };
    
      return (
        <SpotifyClientAuthProvider config={authConfig}>
          <YourAppContent />
        </SpotifyClientAuthProvider>
      );
    }
  6. Render playlist details with renderPlaylistAsync

    main

    Use renderPlaylistAsync to fetch and display detailed information about a specific Spotify playlist, including the owner's data. It returns a React component (<Playlist />) containing the playlist and user information.

    Parameters:

    • playlistId (string): The unique Spotify ID of the playlist.
    import { renderPlaylistAsync } from "./components/spotify/spotify-server-actions";
    
    // Example usage
    const playlistView = await renderPlaylistAsync({
      playlistId: "37i9dQZF1DXcBWIGoYBM3M"
    });
  7. Fetch and parse Spotify data with fetchSpotifyDataAsync

    main

    Use fetchSpotifyDataAsync to perform a fetch request and automatically parse the JSON response. If the Spotify API returns an error object, this function will throw an error containing the error message and the statusCode from the Spotify response.

    interface SpotifyTrack {
      id: string;
      name: string;
    }
    
    const tracks = await fetchSpotifyDataAsync<SpotifyTrack[]>("/v1/me/tracks");
  8. Fetch user playlists with getUserPlaylists

    main

    Use getUserPlaylists to retrieve a paginated list of the authenticated user's playlists. It returns a React component (<UserPlaylistsServer />) populated with the playlist data. If no playlists are found, it returns a fallback <Text /> component indicating "No playlists found".

    Parameters:

    • limit (number, optional): Number of playlists to fetch. Defaults to 20.
    • offset (number, optional): The index at which to start fetching. Defaults to 0.
    import { getUserPlaylists } from "./components/spotify/spotify-server-actions";
    
    // Example usage
    const playlistList = await getUserPlaylists({
      limit: 10,
      offset: 0
    });
  9. Initiate Spotify authentication with useSpotifyAuthRequest

    main

    The useSpotifyAuth hook exposes useSpotifyAuthRequest, which is a specialized version of the useSpotifyAuthSession hook. It is pre-configured to automatically exchange the authorization code for an access token and cache the result using the provider's internal logic.

    Usage

    Pass an optional Partial<AuthRequestConfig> to override or extend the base configuration provided to the SpotifyClientAuthProvider.

    import { useSpotifyAuth } from "@/lib/spotify-auth/spotify-client-provider";
    
    function LoginButton() {
      const { useSpotifyAuthRequest } = useSpotifyAuth();
      
      // This hook returns the standard expo-auth-session return type
      const { authorize, loadAsync, invalidate } = useSpotifyAuthRequest({
        // additional config overrides here
      });
    
      return (
        <Button title="Login with Spotify" onPress={() => authorize()} />
      );
    }
  10. Validate Spotify code exchange responses with SpotifyCodeExchangeResponseSchema

    main

    Use SpotifyCodeExchangeResponseSchema (a Zod schema) to validate the response received after exchanging a Spotify authorization code for an access token. This ensures the response contains the required fields like access_token, token_type, expires_in, and scope. The refresh_token is optional.

    import { SpotifyCodeExchangeResponseSchema } from './path-to-validation';
    
    const validatedResponse = SpotifyCodeExchangeResponseSchema.parse(apiResponse);
    // validatedResponse is typed as SpotifyCodeExchangeResponse
  11. Fetch Spotify data with fetchWithAuth

    main

    The fetchWithAuth function is a wrapper around the standard fetch API that automatically injects the Spotify Authorization header using the current server-side authentication state.

    It supports relative URLs (starting with /), which it will automatically resolve to https://api.spotify.com. Note that this function relies on getServerAuth() being populated, typically via the withAccessToken wrapper.

    // Using a relative URL
    const response = await fetchWithAuth("/v1/me/player/currently-playing");
    
    // Using an absolute URL
    const response = await fetchWithAuth("https://api.spotify.com/v1/me");
  12. Configure the SpotifyProvider

    main

    The SpotifyProvider component (returned by createSpotifyAPI) requires a useAuth prop. This prop must be a function that returns an AuthContext object. This context is used to inject the current access token into server actions and to update the token if it is refreshed during an API call.

    An AuthContext must satisfy the following shape:

    • auth: AuthResults | null (the current authentication state)
    • getFreshAccessToken: () => Promise<AuthResults>
    • setAccessToken: (accessToken: SpotifyCodeExchangeResponse) => void
    type AuthContext = {
      auth: AuthResults | null;
      getFreshAccessToken: () => Promise<AuthResults>;
      setAccessToken: (accessToken: SpotifyCodeExchangeResponse) => void;
    };
    
    // Usage in Provider:
    <Provider useAuth={() => authContext} />