This simple applications helps people discover new music on Spotify based on their current mood.
Users are first asked to say what kind of mood they’re in, and then the app responds with a bunch of suggestions based on that.
This application was created with Express and written entirely in TypeScript with a vanilla JavaScript and HTML/CSS frontend. It utilizes the powerful Spotify Recommendation API to retrieve tracks that the user may be interested in listening to based on their mood. On any request to the server, Express middleware checks if the client has a valid Spotify authorization token stored as a cookie:
export const checkTokenCookie = async (req: Request, res: Response, next: NextFunction) => {
if (req.cookies.spotifyToken && new Date(Date.now()) > new Date(req.cookies.spotifyTokenExpiry)) {
next();
} else {
console.log(' --- token expired, refreshing...');
const response = await getToken();
const token = response.access_token;
const tokenExpiry = new Date(new Date().getTime() + response.expires_in);
res.cookie('spotifyToken', token).status(200);
res.cookie('spotifyTokenExpiry', tokenExpiry);
console.log(' --- token refresh complete, expires at ', tokenExpiry);
next();
}
};
// ...
export const getToken = async (): Promise<SpotifyAuthResponse> => {
let formData = new FormData();
formData.append('grant_type', 'client_credentials');
try {
const response = await axios({
method: 'POST',
url: '<https://accounts.spotify.com/api/token>',
data: { grant_type: 'client_credentials' },
headers: {
Authorization: 'Basic ' + Buffer.from(client_id + ':' + client_secret).toString('base64'),
'Content-Type': 'application/x-www-form-urlencoded',
},
});
return Promise.resolve(response.data);
} catch (err: any) {
console.log(err);
return Promise.reject(err.message);
}
};
Once the user has been validated and Express has confirmed that, users can input their mood and the server makes a call to the Spotify api like so:
export const getRecommendations = async (mood: Mood, token: string): Promise<Array<RecsForClient>> => {
// the requestData file stores artist, tracks and genres sent to Spotify to
// help their algorithm with recommendations, that is the big "bridge"
// between our application and Spotify
const baseUrl = '<https://api.spotify.com/v1/recommendations?limit=5&market=ES>';
const seedArtist: string[] = requestData.artist[mood];
const seedGenres: string = requestData.genres[mood];
const seedTracks: string[] = requestData.songs[mood];
// create the request URL
const reqUrl = `${baseUrl}&seed_artists=${seedArtist}&seed_genres=${seedGenres}&seed_tracks=${seedTracks}`;
console.log(' --- REQUEST URL: ', reqUrl);
// send the request and await the response
try {
const response = await axios.get(reqUrl, {
headers: {
Authorization: 'Bearer ' + token,
'Content-Type': 'application/json',
},
});
const tracks: Array<SongResponse> = response.data.tracks;
let resData: RecsForClient[] = [];
// loop through and save the data
for (let i = 0; i < tracks.length - 1; i++) {
resData.push({
track_name: tracks[i].name,
artist: tracks[i].artists[0].name,
artwork: tracks[i].album.images[0].url,
duration_ms: tracks[i].duration_ms,
spotify_link: tracks[i].external_urls.spotify,
explicit: tracks[i].explicit,
});
}
return Promise.resolve(resData);
} catch (err: any) {
console.log(err.data.error);
return Promise.reject();
}
};
Spotify sends back a LOT of data from a request like that, but we don’t need it all. The data is boiled down to the main things we need and sent back to the client as an array of the following JSON objects:
export declare type RecsForClient = {
track_name: string;
artist: string;
artwork: string;
duration_ms: number;
spotify_link: string;
explicit: boolean;
};
Once the client receives that data, it redirects to a page full of the recommendations, like so: