A Playlist Generator Progressive Web App leveraging the Spotify API.
This project serves as a personal playground where I can try out new things as I continue to explore and learn new technologies and skills. It has evolved through numerous iterations since its original development in February, 2022 and holds a special place in my heart as the Codecademy project from which it grew was my first exposure to React, OAuth2.0, and fetch requests.
The main branch is up to date with branch v3.2_graphql.
- Express 4.18.2
- React.js 18.2.8
- GraphQL 16.8.1
- Apollo Server 4.9.5
- Apollo Client 3.8.7
- TypeScript for client-side code
- @types/spotify-api
- Styled Components
- Cookie-sessions
- Passport
- Passport-spotify
Spotify provides a robust search and recommendations feature which can be experienced on their own website, or on their desktop or mobile apps. When a user executes a search on Spotify, they are provided with matches in seven categories: All, Artists, Playlists, Albums, Songs, Podcasts & Shows, and Profiles. Each of these categories is itself a deep rabbit-hole, some containing even more sub-categories. An absolute wealth of information. But what if a user prefers not to sort through all of this? What if they'd like to just be served up a freshly curated playlist of tracks by many different artists, all based on a search term (artist, album, or song) of their choosing? Enter Assemble the Jams.
A demo video of the app can be viewed by clicking on the below thumbnail.
On first render, a useEffect
hook in AuthContext.Provider
(at the top of the component tree, wrapping the App
component) sends a request to the backend which authenticates the Client with Spotify.
At the same time, a useEffect
in the App
component looks for an authenticated user. If no user is found, audioPlayer()
(exposed by a custom useAudio
hook) is invoked, triggering a useEffect
in useAudio
to create a new Audio()
object on the window and store its reference in a useRef
hook.
Once the Client is authenticated, an un-authenticated user can execute keyword searches and preview select tracks using in-track controls.
Two lists are populated in the UI on execution of a keyword search:
- 'Search Results' - the direct result of the keyword search (which may optionally include a filter to match the search term to artist, track or album)
- 'Playlist' - a custom curated suggested playlist generated by leveraging the results of the keyword search against the Spotify 'Recommendations' algorithm.
Some, but not all, track objects returned from Spotify have a property preview
, whose value is a url that can be passed into the audioRef
. Otherwise, no preview is available to the un-authenticated user for that track.
At any point, the user may use the 'LOGIN' button to authenticate with their Spotify Premium account. Once the user is authenticated, the useEffect
in App
invokes spotifyPlayer()
, triggering useAudio
to load a new SpotifyPlayer()
on the window using the Spotify sdk and storing its reference in spotifyRef
. All audio functionality will now be passed to spotifyRef
, and the user is able to hear a preview of any track regardless of whether it has a preview
url.
The authenticated user is also able to modify their Premium account in two ways:
- Save a playlist to their account (after optionally naming it and adding/deleting tracks)
- Add/delete a track to/from their list of 'Liked Songs'
The app uses three protocols for engaging with Spotify:
- Spotify Web Playback SDK for playing track previews with an authenticated user
- Express Auth Router
- Auth requests are sent to the Express server at
/auth
, where Client credentials are obtained via fetch request to Spotify and user credentials are obtained via Passport Spotify strategy.
- Auth requests are sent to the Express server at
- GraphQL API
- Search, Save, and Like requests are sent via Apollo Client queries and mutations to the Express server at
/graphql
where the Apollo Server middleware invokes the appropriate resolver. - The api layer is made up of a graphql resolvers layer and a models layer. In the previous version (v3.1), the same models layer is used, but is invoked by a controller layer corresponding to REST API endpoints. This reuse of the models layer to engage with the Spotify API demonstrates the power and flexibility of this versionable approach.
- Search, Save, and Like requests are sent via Apollo Client queries and mutations to the Express server at
On the Client side, graphql queries and mutations are implemented in usePlaylist
and useLike
custom hooks which leverage useLazyQuery
and useMutation
hooks imported from Apollo Client.
In order to retrieve data from the Spotify API, this and all apps must be registered on the Spotify Developer portal. At that time, the app receives its own unique Client credentials and is registered in Development Mode.
In Development Mode, up to 25 Spotify users can install and use the app, but they must be explicitly added on the Developer portal before they can authenticate and actually use the app.
Assemble the Jams has been approved by Spotify as a full production app is no longer in Development Mode.
To attain this approval, this app underwent a rigorous review process by Spotify to ensure that it complied with all requirements in the Spotify Developer Policy and Spotify Design Guidelines. As a full production app, Assemble the Jams has been granted access to an increased API request quota and an unlimited number of Spotify users can install and use it without being explicitly added to a list of users in Developer portal.
The node package @types/spotify-api is a package containing type definitions for the Spotify Web API, including complex types for data objects, requests, and responses relating to the Spotify API.
I've had the opportunity to contribute three small pieces of code to this open-source package:
- RecommendationsObject - I was able to submit an update to this type after discovering that it contained an incorrectly-typed Track object (TrackObjectSimplified, which does not contain and album property). But while TrackObjectFull (previously the only available alternative) does contain an album property, that album Object yields album_type in all lower-case. This exposes a small bug in the Spotify API, as the all-caps album_type in the recommendations response Track is inconsistent with all other endpoint responses.
- RecommendationTrackObject - This is a new Track Object type added to the package, specific to the recommendations response returned from a recommendations with seeds request. This track object is identical to the TrackObjectFull except that it contains a newly-created RecommendationAlbumObject.
- RecommendationAlbumObject - Similarly, this extends the AlbumObjectSimplified, but yields album_type in all caps instead of lowercase.