React-Axios-Express Spotify music player webapp

February 01, 2022

THIS IS NOT HOSTED CURRENTLY!

I would only be able to allow a few users to access it without a production api key. I need to review the TOS further but I’m pretty sure I’d be violating if I parsed out my own registering/deregistering bot that held a registered user of some kind. 🙂

What is this exactly?

This isn't a follow along.

This is a brief 😓 overview of a spotify app I did based on an app web dev simplified did, with a few small twists. I’m not going over the entire app setup it’s meant to be a reference since alot of documentation out at time of writing was breaking. I’m not going over everything just the parts I think are relevant and interesting.

He used a bunch of packages for handling the post/get requests and I wanted to do those myself so I used axios. I also hate white backgrounds so I’ve styled mine to be a little easier on the eyes.

I also decided I didn’t want the lyrics bit in there so I’ve left it out. Hope it Helps! If someone is looking at this in the future I am fully aware that Node is planning to include fetch natively. I’m just comfortable with axios.😉

We want to play our spotify playlist with our own theme/look🎶

That means we will want to use the spotify api since they make it easy to setup and use their api to search and play songs 🎵 The endpoints used to search and play songs are available but need an access token in order to use them.

🌱CodeFlow Synopsis🌼

This project revolves around the codeflow for authorizing our app to act on behalf of the user. As well as allowing our app to refresh the access token while the user is on logged in. We will also want to refresh the token on page reload and on a timer before the token expires.

The Flow should look something like

Redirect user to oAuth2 site👉The user accepts our permissions.👇

oAuth2 site redirects back to us with the authorization code in the url paramaters🎈

We will extract the auth_code and send it to their authorization endpoint where they will return a json object stuffed with an access_token, refresh_token, and an expiration time.💣

We will finally use the access token to search and play music.🔈

This is definitely leaving out all the overhead and niceties that we will be implementing.

Technical Scemantics Start Here 👇

If you want to run it you’ll need to download it and install it locally using npm. It’s a really good place to start since buffer has been deprecated due to security issues in node earlier this year. The axios requests are formatted to match the update and should provide a reference in case you are looking to build your own spotify web app.

The reason it’s saved into two packages is in order to allow me to customize the ui a little bit to my liking in the near future. I haven’t finished the polish on the project as I’m trying to get my porfolio site in order before I hone the details in.

If you are looking to run the web app you will need to register on spotify for a developer key doesn’t take very long and is automated but you need a client secret and id in order to use this example live. I’m not currently hosting it although if i get it setup like i want i might.

Alright If you read all that you are just as dry as I am congrats 👏

💥I'm not going over setting up the app on spotify💥

Find the source here at GitHub

Page looks a little bland?

Sorry it serves the purpose of sending us to an oAuth2.0 endpoint thats gonna send us back to the uri redirect you give spotify on your dashboard. You'll only see it as the landing

The endpoint is https://accounts.spotify.com/authorize now according to spotify documentation we need to request

client_id=yourspotifyclient_id a response_type=code the redirect_uri setup on your spotify dashboard followed finally by the scope of permissions you are requesting from the user loging in.

Formatted href link would be better served as a function in case we decide to go to a production key. The request will be passed in then as AUTH_URL

AUTH_URL="https://accounts.spotify.com/authorize?client_id="+client_id+"&response_type=code&redirect_uri=http://localhost:3000&scope=streaming%20user-read-email%20user-read-private%20user-library-read%20user-library-modify%20user-read-playback-state%20user-modify-playback-state%20app-remote-control"

🔥Programming===Formatting_Data_Structures🔥

const Login = ({AUTH_URL}) => { return <Container className="d-flex justify-content-center align-items-center bg-dark" style={{minHeight: "100vh", minWidth: "100vw", backgroundColor: "--bs-gray-dark"}} > <a className="btn btn-success btn-lg" href={AUTH_URL}>Login with Spotify</a> </Container> }

Now What????

Well we are going to get into the meat of handling oAuth2.0 requests. The redirect will shove us back to our site and spotify will toss an access code as a url response.

💫That's right you guessed it more formatting💫

So we need to grab that out of the url split off the code and send the code to their authorization endpoint to get an access token. export default function useAuth(code){ const [accessToken, setAccessToken] = useState() const [refreshToken, setRefreshToken] = useState() const [expiresIn, setExpiresIn] = useState() useEffect(()=>{ axios.post('http://localhost:3001/login',{ code, }).then(res => { setAccessToken(res.data.accessToken) setRefreshToken(res.data.refreshToken) setExpiresIn(res.data.expiresIn) console.log('login complete token: ', res.data.accessToken) }).catch(() => { window.location= '/' }) }, [code]) useEffect(() => { if(!refreshToken || !expiresIn) return console.log('refreshing token standby for transmission expires in', expiresIn) axios.post('http://localhost:3001/refresh',{ refreshToken, }).then((res) => { console.log('refresh done new token: ', res.data.accessToken) setAccessToken(res.data.accessToken) setExpiresIn(res.data.expiresIn) }).catch(()=> { window.location = '/' }) }, [refreshToken, expiresIn]) return accessToken };

Really like the point of using a custom hook for this, almost like react was made for it.💣

Our hook will take our code in and toss it into a proper post request based on which endpoint we are going to take our code and send it off to the login endpoint on our express server. The express server will then use the authorization code given to requisition the access token that will allow us to use the endpoints we are after. Then if we have an access token we can refresh it. However I’m not going over the refreshing portion at the moment.

app.post('/login', (req, res) => { const code = req.body.code //query params for logging in const queryS = new url.URLSearchParams() queryS.append('grant_type', 'authorization_code') queryS.append('code', code) queryS.append('redirect_uri', redirect_uri) //###post request using axios and manually setting headers axios.post( tokenAuthEndpoint, queryS.toString(), headerHandle ).then((response) => { console.log(response.data) res.json({ accessToken: response.data.access_token, token_type: response.data.token_type, expiresIn: response.data.expires_in, refreshToken: response.data.refresh_token }) } ).catch((err) => { console.log("error: ", err); }) })

FrontEnd Stuff

The fun part is that now we have what we need to access the data we need we just have to format and stuff a bunch of markup into a page. const Dashboard = ({code}) => { let accessT = useAuth(code) const [accessToken, setAccessToken] = React.useState(accessT) const [search, setSearch] = React.useState('') const [searchResults, setSearchResults] = React.useState([]) const [playingTrack, setPlayingTrack] = React.useState() console.log(searchResults) function chooseTrack(track){ setPlayingTrack(track) setSearch('') } React.useEffect(() => { if(!accessToken){ setAccessToken(accessT) } }, [accessToken, accessT]) React.useEffect(() => { if(!search) return setSearchResults([]) let cancel = false var authField = ('Bearer ' + accessToken) console.log(authField) axios.get(('https://api.spotify.com/v1/search?type=track,artist,album&q='+search), { headers: { 'Authorization': authField, 'content-type': 'application/x-www-form-urlencoded', } } ).then((response) => { if(cancel) return setSearchResults(response.data.tracks.items.map((track) => { const smallestAlbumImage = track.album.images.reduce((smallest,image) => { if(image.height < smallest.height) return image return smallest }, track.album.images[0]) return{ artist: track.artists[0].name, title: track.name, uri: track.uri, albumUrl: smallestAlbumImage.url } })) }).catch(error => console.log(error)) return () => cancel = true }, [search, accessToken]) return <Container className="d-flex justify-content-center flex-column bg-dark py-2" style={{minHeight: "100vh", minWidth: "100vw", backgroundColor: "--bs-gray-dark", color: "white"}}> <Form.Control type="search" placeholder="Search songe/Artists" value={search} onChange={e => setSearch(e.target.value)} /> <div className="flex-grow-1 my-2" style={{overflowY: 'auto'}}> {console.log(searchResults)} {searchResults.map(track => (<SearchResultContainer track={track} key={track.uri} chooseTrack={chooseTrack}/>))} {searchResults.length === 0 && ( <div className="text-center" style={{whiteSpace: 'pre'}} > {/*Lyrics/Alternate Styling */} </div> )} </div> <div> {console.log(playingTrack)} <Player accessToken={accessToken} trackUri={playingTrack?.uri}/> </div> </Container> };

That will give us the Dashboard we need to search and play music

more importantly it cancels the search on a new entry dynamicaly thanks to our use of useEffect();

Explanation on the rest coming soon!