Créer une application utilisant Spotux avec Nuxt.js
À propos de l'auteur
Cher est un ingénieur autodidacte qui travaille actuellement en tant que responsable technique pour Starbucks. Avec 20 ans d'expérience dans le développement web, Cher attribue ses compétences…
Pour en savoir plus sur Cher …
Nous avons tous entendu parler de Spotify. Lancée en 2008, l'application propose des millions de pistes de divers artistes légendaires et à venir. Il vous permet de créer une liste de lecture, de suivre d'autres personnes ou de choisir une liste de lecture en fonction de votre humeur.
Mais prenons l'application aujourd'hui sous un autre angle. Construisons une application Web rendue côté serveur sur deux pages avec un composant «Lecture en cours sur Spotify». Je vous expliquerai toutes les étapes de la création d'une application côté client, de la création et de la connexion à une API de serveur, ainsi que de la connexion à des services d'API externes.
Notre projet sera construit à l'aide de Node.js et de npm. écosystèmes, Github pour stocker notre code, Heroku en tant qu’hôte, Heroku's Redis pour notre stockage et l’API Web de Spotify. L’application et l’API interne seront entièrement construits à l’aide du système Nuxt. Nuxt est un framework de rendu côté serveur qui s'exécute sur Vuejs, Expressjs, Webpack et Babeljs.
Ce tutoriel est moyennement complexe, mais est divisé en sections très consommables. Vous trouverez une démonstration de travail sur cherislistening.heroku.com .

Conditions requises
Ce tutoriel nécessite des connaissances en HTML, CSS, Javascript (ES6) et l'utilisation de la ligne de commande ou du terminal. Nous allons travailler avec Node.js et Vuejs ; une compréhension de base des deux sera utile avant de commencer ce tutoriel. Vous devez également installer les outils Xcode si vous utilisez MacOS.
Si vous préférez procéder à une ingénierie inverse, vous pouvez bifurquer le référentiel .
Table des matières
- Planning Our Application
Nous exposerons les fonctionnalités attendues et une représentation visuelle de ce que nous prévoyons voir une fois terminé. - Configuration et création de notre projet
Nous allons expliquer comment installer une application hébergée. sur le serveur de Heroku, configurez le déploiement automatique à partir de Github, configurez Nuxt à l'aide des outils de ligne de commande, puis lancez notre serveur local. - Construction de notre couche API
Nous allons apprendre à ajouter une couche API à notre application Nuxt. , comment se connecter à Redis et à l’API Web de Spotify. - Stockage côté client et gestion de l’état
Nous allons voir comment nous pouvons exploiter le magasin intégré Vuex pour actualiser les performances. Nous allons configurer nos connexions de données initiales à notre API. - Construction des pages et des composants
Nous allons jeter un bref aperçu de la façon dont les pages et les composants diffèrent dans Nuxt et créer deux pages et quelques composants. Nous utiliserons nos données pour créer notre application Now Playing et certaines animations. - Publication de notre application
Nous allons installer notre application sur GitHub et la construire sur le serveur Heroku, authentifier et partager avec tout le monde la musique que nous écoutons.
Planifier notre demande
La première étape avant de lancer un nouveau projet consiste à planifier nos objectifs. Cela nous aidera à établir un ensemble d'exigences pour atteindre nos objectifs.
- Combien de pages y a-t-il?
- Que voulons-nous sur nos pages?
- Souhaitons-nous que notre composant Spotify “Now Playing” soit présent sur les deux
- Voulons-nous une barre de progression pour montrer aux auditeurs où nous en sommes dans la chanson?
- Comment voulons-nous que nos pages soient aménagées?
Voilà le type de questions qui nous aideront à rédiger notre
Construisons deux pages pour notre application. Premièrement, nous voulons une page de destination avec notre composant «Lecture en cours». Notre deuxième page sera notre zone d’authentification où nous connectons nos données à Spotify. Notre conception sera très minimaliste, pour que les choses restent simples.
Pour notre composant «Now Playing», prévoyons de montrer les progrès de la piste sous forme de barre, le nom de la piste, le nom de l'artiste et le album d'art. Nous voudrons aussi montrer un autre état montrant la dernière piste jouée, au cas où nous n'écoutions rien actuellement.
Puisque nous avons affaire à l'API de Spotify, nous aurons des jetons spéciaux pour accéder aux données de notre site. Pour des raisons de sécurité, nous ne souhaitons pas exposer ces jetons sur le navigateur. Nous ne voulons que nos données, nous voulons donc nous assurer que nous sommes le seul utilisateur à pouvoir nous connecter à Spotify.
Le premier problème que nous rencontrons dans la planification est que nous devons nous connecter à Spotify. C’est là que notre stockage en cache Redis entre en jeu. L’API de Spotify permettra de connecter en permanence votre compte Spotify à une application avec un autre jeton spécial. Redis est un serveur de structure de données en mémoire très performant. Comme nous avons affaire à un jeton, une simple clé: le système de stockage de valeurs fonctionne bien. Nous voulons qu'il soit rapide afin de pouvoir le récupérer pendant le chargement de notre application.
Heroku a son propre service de cache Redis intégré. Ainsi, en utilisant Heroku pour notre serveur, notre hôte et notre stockage, nous pouvons tout gérer en un seul. endroit. Avec l'avantage supplémentaire du déploiement automatique, nous pouvons tout faire depuis notre console avec des commandes dans le terminal. Heroku détectera notre langage d'application à partir de notre push, et le construira et le déployera sans trop de configuration.
Configuration et création de notre projet
Installez Nodejs
Procurez-vous le bon package pour votre système d'exploitation ici: https : //nodejs.org/fr/download/
$ node --version
v10.0.1
Installez git
Suivez les instructions relatives à votre système d'exploitation ici: https://git-scm.com/book/fr/v2/Getting-Started-Installing-Git
$ git --version
Git version 2.14.3 (Apple Git-98)
Inscrivez-vous à GitHub
Suivez les instructions ici: https://github.com/join et https://help.github.com/articles/set-up- git / .
Créer un référentiel: https://help.github.com/articles/create-a-repo/
Clonez le référentiel: https: / /help.github.com/articles/cloning-a-repository/
J'ai nommé mine «cherislistening». Voici à quoi ressemble mon clone:
clone $ git https://github.com/cherscarlett/cherislistening.git
Cloner en `cherislistening` ...
remote: Comptage d'objets: 4, fait.
remote: Compression d'objets: 100% (4/4), terminé.
remove: Total 4 (delta 0), réutilisé 0 (delta 0)
Déballer des objets: 100% (4/4), c'est fait.
$ cd cherislistening /
Installer et installer Heroku
Inscrivez-vous à Heroku ici: https://signup.heroku.com/
Téléchargez et installez l'interface de ligne de commande (CLI): https : //devcenter.heroku.com/articles/heroku-cli#download-and-install
Nous devrons nous connecter et créer notre application, ainsi que configurer certaines variables de configuration. J'ai nommé mon application «cherislistening». Vous pouvez également désactiver la commande -a
et Heroku vous donnera un nom généré aléatoirement. Vous pouvez toujours le changer plus tard. L'URL de votre application sera http: //
.
Nuxt nécessite une configuration spécifique pour être construit et exécuté correctement, nous allons donc l'ajouter maintenant pour le faire sortir. du chemin.
$ heroku –version
heroku / 7.19.4 darwin-x64 node-v11.3.0
$ heroku login
heroku: Appuyez sur une touche pour ouvrir le navigateur pour vous connecter ou sur q pour quitter:
Se connecter… fait
Connecté en tant que cher.scarlett@gmail.com
$ heroku crée -a cherislistening
$ heroku config: set CLIENT_URL = http://cherislistening.herokuapp.com API_URL = / HOST = 0.0.0.0 NODE_ENV = production NPM_CONFIG_PRODUCTION = false
Définition de CLIENT_URL, API_URL, HOST, NODE_ENV, NPM_CONFIG_PRODUCTION et redémarrage de cherislistening… done, v1
API_URL: /
CLIENT_URL: http://cherislistening.herokuapp.com
HÔTE: 0.0.0.0
NODE_ENV: production
NPM_CONFIG_PRODUCTION: false
Accédez au tableau de bord Heroku et cliquez sur votre application nouvellement créée. Dans l'onglet "Déployer", connectez-vous à votre compte Github, sélectionnez le référentiel cloné et activez le déploiement automatique à partir de la branche principale.



Créer une application Nuxt
Nous allons utiliser npx pour créer notre application Nuxt. Npm est un excellent écosystème pour la gestion des paquets par Node.js, mais pour exécuter un paquet, il faut l’installer et l’ajouter à notre fichier package.json . Ce n’est pas très utile si nous voulons exécuter un seul paquet une fois, et installer quelque chose n’est pas vraiment nécessaire. Cela rend npx approprié pour exécuter des paquetages composant des arbres de fichiers, pour ajouter des passerelles et installer les paquetages dont vous avez besoin pendant l'exécution.
$ npx --version
6.4.1
npx est livré par défaut dans npm 5.2.0+, il est donc fortement recommandé de mettre à niveau npm au lieu de l’installer globalement. Si vous venez d'installer une nouvelle version de node.js, vous devez disposer des npm et npx actuels.
L'équipe Nuxt.js a créé un outil d'échafaudage qui donnera à votre application la structure de base nécessaire à son exécution. Assurez-vous d’être dans le dossier de votre nouveau projet avant d’exécuter la commande.
$ npx create-nuxt-app
npx: installé 407 en 5.865s
> Génération du projet Nuxt.js dans / Users / cstewart / Projets / personnel / tutorials / cherislistening
? Nom du projet cherislistening
? Description du projet Une application Spotify Now Playing
? Utiliser un framework de serveur personnalisé
? Choisissez les fonctionnalités pour installer Prettier, Axios
? Utiliser un cadre d'interface utilisateur personnalisé
? Utiliser un framework de test personnalisé
? Choisissez le mode de rendu Universel
? Nom de l'auteur Cher Scarlett
? Choisissez un gestionnaire de paquets npm
npm notice a créé un fichier de verrouillage nommé package-lock.json . Vous devez valider ce fichier.
Pour commencer:
npm run dev
Construire et démarrer pour la production:
npm run build
npm start
npm notice a créé un fichier de verrouillage sous le nom package-lock.json . Vous devez valider ce fichier.
Pour commencer:
npm run dev
Pour commencer à produire:
npm run build
npm start
Chaque dossier de l'échafaudage est accompagné d'un fichier README . Ce fichier vous donnera les bases du fonctionnement du dossier et vous indiquera s'il est nécessaire ou non. Nous allons parler des dossiers que nous utiliserons au fur et à mesure que nous y arriverons dans le tutoriel.
.nuxt /
les atouts/
| ___ README.md
Composants/
| ___ Logo.vue
| ___ README.md
mises en page /
| ___ default.vue
| ___ README.md
middleware /
| ___ README.md
node_modules /
pages /
| ___ index.vue
| ___ README.md
plugins /
| ___ README.md
statique/
| ___ favicon.co
| ___ README.md
le magasin/
| ___ README.md
.gitignore
.prettierrc
LICENCE
nuxt.config.js
package-lock.json
package.json
LISEZMOI.md
Nous devons modifier package.json pour que, lors de notre déploiement à Heroku, notre processus de construction s’exécute. Dans "scripts", nous ajouterons "heroku-postbuild": "npm run build"
. N'oubliez pas d'ajouter une virgule après la ligne précédente dans l'objet.
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt gener",
"heroku-postbuild": "npm run build"
},
Si vous exécutez npm run dev et accédez à http: // localhost: 3000
dans votre navigateur, vous devriez voir l'application échafaudée s'exécuter:

Installer Redis
Ouvrir un nouveau terminal ou ligne de commande et changez les répertoires (cd) dans le dossier parent de votre projet. Téléchargez redis et lancez make. Si vous utilisez Windows, vous devez vous procurer https://github.com/MicrosoftArchive/redis/releases .
$ cd ../
$ wget http://download.redis.io/releases/redis-5.0.3.tar.gz
$ tar xzf redis-5.0.3.tar.gz
$ cd redis-5.0.3
$ sudo make install
cd src && / Bibliothèque / Développeur / CommandLineTools / usr / bin / make install
Indice : C’est une bonne idée de lancer «make test». ?
INSTALLER installer
INSTALLER installer
INSTALLER installer
INSTALLER installer
INSTALLER installer
$ redis-server --version
Serveur Redis v = 5.0.3 sha = 00000000: 0 malloc = libc bits = 64 build = bfca7c83d5814ae0
$ redis-server --daemonize oui
Cela démarrera notre serveur Redis en tant que processus d'arrière-plan et nous pourrons fermer cet onglet. Le serveur Redis local sera exécuté à l'adresse http://127.0.0.1:6379/
.
Dans notre onglet avec notre projet en cours d'exécution, tapez Ctrl + C pour tuer le serveur. Nous devrons installer un paquet redis pour noeud et approvisionner notre instance Heroku Redis.
$ npm install async-redis --save
npm WARN eslint-config-prettier@3.6.0 requiert un homologue d'eslint @> = 3.14.1 mais aucun n'est installé. Vous devez installer vous-même les dépendances entre homologues.
+ async-redis@1.1.5
ajouté 5 paquets de 5 contributeurs et audité 14978 paquets en 7.954s
trouvé 0 vulnérabilités
$ heroku addons: créer des heroku-redis
Créer des heroku-redis sur ⬢ cherislistening ... gratuitement
Votre complément devrait être disponible dans quelques minutes.
! AVERTISSEMENT: les données stockées dans les plans de loisirs sur Heroku Redis ne sont pas conservées.
redis-metric-84005 est en cours de création en arrière-plan. L'application va redémarrer une fois terminé ...
Utilisez les addons heroku: info redis-metric-84005 pour vérifier la progression de la création.
Utilisez les addons heroku: docs heroku-redis pour afficher la documentation.
Comme nous utilisons un compte passe-temps, nous n’avons pas de sauvegarde de nos données. Si notre instance a besoin d'être redémarrée, nous devrons nous authentifier à nouveau pour obtenir une nouvelle clé. Notre application dormira également sur le compte gratuit, de sorte que certaines visites initiales seront un peu lentes, alors que l'application est «en train de se réveiller».
Notre nouvelle application sera disponible à l'adresse https: //cherislistening.herokuapp. com / où "cherislistening" correspond à ce que vous avez nommé votre application Heroku.

Inscrivez-vous pour un développeur Spotify Compte
Ceci nécessite un compte Spotify. Notez que chaque utilisation de l'API de Spotify doit être conforme aux directives de la marque .
Créez un ID client sur https://developer.spotify.com/dashboard/applications . . 19659005] Prenez le ID client et le Client Secret que vous trouverez si vous cliquez sur la carte verte dans les détails de votre nouvelle application, puis exportez-les vers Heroku en tant que variables de configuration. Si vous pensez que le secret de votre client a été exposé, vous pouvez en obtenir un nouveau, mais vous devrez également mettre à jour la configuration de votre application.

$ heroku config: set CLIENT_ID = CLIENT_SECRET =
Définition de CLIENT_ID, CLIENT_SECRET et redémarrage de ⬢ cherislistening ... done, v3
CLIENT_ID:
CLIENT_SECRET:
En haut à droite du tableau de bord de l'application, il y a un bouton Paramètres. Cliquez dessus et ajoutez deux URL de rappel pour la liste blanche. Vous aurez besoin d’une URL de rappel locale et d’une adresse pour votre serveur de production (l’adresse Heroku que nous avons obtenue lors de l’installation).

Spotify dispose d'une fantastique documentation de développeur y compris d'une superbe interface de référence permettant de tester les points de terminaison. Nous devons obtenir notre ID utilisateur pour enregistrer nos variables de configuration. Faisons-le avec Obtenir le profil de l'utilisateur actuel . Obtenez un jeton d'authentification depuis leur console, en sélectionnant la portée utilisateur-lecture-privé. Cliquez sur «Essayez-le» et dans la colonne de droite, recherchez votre identifiant. Nous allons utiliser cet identifiant pour nous assurer que personne d'autre ne pourra se connecter à notre application.
$ heroku config: set SPOTIFY_USER_ID =
Définition de SPOTIFY_USER_ID et redémarrage de ⬢ cherislistening ... done, v4
SPOTIFY_USER_ID:
Comme nous en avons discuté, nous aurons des données que nous ne voudrions pas exposer au public. Deux d'entre eux sont clientId
et clientSecret
fournis par Spotify, et un autre que Heroku a exporté pour que nous puissions accéder à notre cache Redis sur le serveur. Nous devrons également les prendre pour notre développement local.
$ heroku config
=== cherislistening Config Vars
API_URL: /
CLIENT_URL: http://cherislistening.herokuapp.com
HÔTE: 0.0.0.0
NODE_ENV: production
NPM_CONFIG_PRODUCTION: false
REDIS_URL:
SPOTIFY_CLIENT_ID:
SPOTIFY_CLIENT_SECRET:
SPOTIFY_USER_ID:
$ touch .env
Nous allons transférer les informations d'identification que Heroku a renvoyées dans notre terminal vers notre nouveau fichier, .env et nous ferons de l'URL de notre client notre serveur local, http: // localhost: 3000 /
. Nous devons également faire pointer notre URL Redis vers notre instance locale, qui est par défaut redis: //127.0.0.1: 6379
. Ce fichier sera ignoré par git.
CLIENT_URL = http: // localhost: 3000 /
REDIS_URL = redis: //127.0.0.1: 6379
SPOTIFY_CLIENT_ID =
SPOTIFY_CLIENT_SECRET =
SPOTIFY_USER_ID =
Pour accéder à la configuration sur notre serveur local, nous devons mettre à jour la configuration nuxt. Nous ajouterons un autre élément à notre tableau de modules: @ nuxtjs / dotenv
. Nous devrons également importer deux des variables dont nous aurons besoin, disponibles du côté client de notre application. Nous allons ajouter un objet env après les modules.
/ *
** Modules Nuxt.js
* /
modules: [
// Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
'@nuxtjs/dotenv'
],
env: {
spotifyId: process.env.SPOTIFY_CLIENT_ID,
clientUrl: process.env.CLIENT_URL
}
Construction de notre couche API
Middleware
Nuxt dispose de deux méthodes distinctes pour exécuter le code côté serveur.
Dans un composant à fichier unique (SFC), vous avez accès à la propriété de middleware qui correspond au dossier middleware de votre échafaudage. L'inconvénient de ce middleware pour notre cas d'utilisation est qu'il s'exécutera côté serveur lors du chargement ou de l'actualisation de votre page, mais côté client une fois que votre application est montée et lorsque vous naviguez avec les itinéraires de nuxt.
L'autre option est ce que nous recherchons. Nous allons créer notre propre répertoire et l’ajouter en tant que serverMiddleware à notre configuration. Nuxt crée sa propre instance express, nous pouvons donc écrire un middleware enregistré sur sa pile qui ne fonctionnera que sur le serveur. De cette façon, nous pouvons protéger nos données privées de l'exploitation. Ajoutons un dossier api
et index.js pour gérer nos points de terminaison API.
$ mkdir api
$ touch api / index.js
Nous devrons ensuite ajouter notre répertoire à notre configuration pour qu’il s’enregistre au démarrage de notre serveur. Ouvrons le fichier nuxt.config.js à la racine de notre application. Ce fichier nous donne notre HTML
ainsi que tout ce qui se connecte à notre client au moment de la construction. Vous pouvez en savoir plus sur la configuration dans la docs .Nous ajouterons notre répertoire api à notre fichier de configuration,
},
serverMiddleware: ['~/api']
}
Pendant notre développement, nos modifications nécessiteront des reconstructions et des redémarrages du serveur. Comme nous ne voulons pas avoir à le faire manuellement, nuxt installe nodemon pour nous, qui est un outil de «rechargement à chaud». Cela signifie simplement qu'il va redémarrer le serveur et reconstruire notre application lorsque nous sauvegardons nos modifications.
Puisque nous avons ajouté notre API en tant que serverMiddleware
à Nuxt, nous devons ajouter notre répertoire à la configuration. . Nous ajouterons watch
à notre objet de construction et ajouterons le chemin relatif à partir de la racine.
* / **
*** Configuration de construction *
** / *
construire: {
regarder: ['api'],
* / **
*** Vous pouvez étendre la configuration de webpack ici *
** / *
extend (config, ctx) {},
serverMiddleware: ['~/api']
}
Nous devons également modifier notre script de développement dans package.json pour redémarrer le serveur. nodemon --watch api --exec "nuxt "
:
"scripts": {
"dev": "nodemon --watch api --exec " nuxt "",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt gener",
"heroku-postbuild": "npm run build"
},
Désormais, nous n’avons plus à nous soucier de redémarrer et de redémarrer notre serveur manuellement chaque fois que nous apportons une modification. ?
Commençons par notre serveur de développement local.
$ npm run dev
Flux de données, stockage et sécurité
Avant de commencer à écrire notre couche API, nous souhaitons planifier la façon dont nous transférons les données de sources externes vers notre client. Nous avons configuré un serveur de cache Redis, souscrit à l'API Spotify et mis en place une structure comportant une couche client et une couche serveur. Le client a des pages et un magasin où nous pouvons stocker et restituer nos données. Comment ces deux éléments fonctionnent-ils ensemble pour protéger nos données d'authentification et piloter notre composant Now Playing?

Toutes les informations que nous souhaitons conserver à long terme, ou pour les nouvelles connexions entrantes, seront stockées sur le serveur. Nous ne pouvons pas nous connecter à Spotify lorsque d'autres utilisateurs visitent notre application. Nous devons donc nous assurer que les nouvelles connexions client peuvent contourner l'authentification en accédant à notre jeton de service spécial. Nous voudrons garder trace de nos propres identifiants Spotify afin que seule notre propre connexion soit approuvée par l'API, et nous voudrons qu'une piste soit prête à être affichée au cas où nous ne pourrions pas nous connecter à l'API de Spotify pour une raison quelconque. ] Il nous faut donc prévoir de stocker notre Spotify refresh_token
notre Spotify userId
et notre lastPlayedTrack
dans notre cache Redis.
Tout le reste peuvent être stockés en toute sécurité dans le magasin vuex de notre client Le magasin et les pages (y compris leurs composants) échangeront des données en utilisant l'architecture de nuxt, et nous parlerons au cache Redis et à l'API de Spotify via l'API de notre propre serveur.
Rédaction de l'API
Nuxt est fournie avec le express cadre déjà déjà installé, afin que nous puissions l'importer et y installer notre application serveur. Nous voudrons exporter notre gestionnaire et notre chemin afin que nuxt puisse gérer notre middleware.
import express de 'express'
const app = express ()
module.exports = {
chemin: '/ api /',
gestionnaire: app
}
Nous aurons besoin de quelques terminaux et fonctions pour gérer les services dont nous avons besoin:
-
POST
sur notre cache Redis - La dernière piste jouée
- ] Nom
- Artistes
- Adresse URL de la couverture de l’album
- Spotify
refresh_token
- Spotify
access_token
- Etat de la connexion Spotify
-
GET
dans notre cache Redis - Identique à
POST
- Rappel de Spotify
- Rafraîchissez notre Spotify
access_token
-
GET
pistes récemment jouées de Spotify -
GET
en cours de lecture de la piste de Spotify
Cela peut sembler beaucoup d'appels, mais nous allons combiner et ajouter de petits éléments de logique là où cela a un sens, au fur et à mesure que nous écrivons.
Notions de base de l'écriture d'un point de terminaison dans Expressjs
Nous allons utiliser la méthode get ()
de express pour définir la plupart de nos Nts. Si nous devons envoyer des données complexes à notre API, nous pouvons utiliser la méthode post ()
.
Mais si on pouvait faire les deux? Nous pouvons accepter plusieurs méthodes avec all ()
.
Ajoutons le premier itinéraire dont nous aurons besoin, qui est notre connexion à notre cache Redis. Nous le nommerons spotify / data
. La raison pour laquelle nous l'appelons d'après Spotify
plutôt que redis
est parce que nous traitons les informations de Spotify, et Redis est simplement un service que nous utilisons pour gérer les données. spotify
est plus descriptif ici, donc nous savons ce que nous obtenons, même si notre service de stockage change à un moment donné.
Pour l'instant, nous n'ajouterons qu'un res.send ( )
:
import express à partir de 'express'
const app = express ()
app.all ('/ spotify / data /: key', (req, res) => {
res.send ('Success! n')
})
module.exports = {
chemin: '/ api /',
gestionnaire: app
}
Faisons un test pour nous assurer que tout fonctionne correctement. Ouvrez un nouvel onglet dans votre terminal ou votre ligne de commande pour vous assurer que votre serveur nuxt continue de fonctionner et exécutez la commande cURL suivante:
$ curl http: // localhost: 3000 / api / spotify / data / key
Succès! ?
Comme vous pouvez le constater, res.send ()
a renvoyé le message que nous avons inclus en réponse à notre demande GET
. C’est ainsi que nous renverrons également les données extraites de Spotify et Redis au client.
Chacun de nos points de terminaison aura la même structure de base que notre premier.

Elle aura un chemin d'accès, ] / spotify / data /
il peut avoir un paramètre, comme : clé
et sur demande, express
renvoie un objet de requête, req
et un objet de réponse, res
. req
aura les données que nous envoyons au serveur, res
attend de gérer ce que nous voulons faire après avoir terminé les procédures de notre fonction.
Connecting to the Redis Cache
Nous avons déjà vu que nous pouvons renvoyer des données à notre client avec res.send ()
mais nous pouvons également souhaiter envoyer un res.status ()
. . En cas de problème pour atteindre Spotify (ou notre cache Redis), nous souhaitons le savoir afin de pouvoir gérer correctement l’erreur, au lieu de mettre notre serveur en panne ou de mettre le client en panne. Nous souhaitons également le consigner afin que nous puissions être informés des échecs des applications que nous construisons et que nous desservons.
Avant de pouvoir continuer avec ce point de terminaison, nous devons avoir accès à notre cache Redis. Lors de l'installation, nous avons installé async-redis
ce qui nous aidera à accéder facilement à notre cache depuis Heroku. Nous devrons également ajouter notre configuration dotenv
pour accéder à notre URL redis.
import redis à partir de 'async-redis'.
require ('dotenv'). config ()
// Redis
fonction connectToRedis () {
const redisClient = redis.createClient (process.env.REDIS_URL)
redisClient.on ('connect', () => {
console.log (' n? Le client Redis est connecté n')
})
redisClient.on ('error', err => {
console.error (` n? le client Redis n'a pas pu se connecter: $ {err} ? n`)
})
retournez redisClient
}
Par défaut, redis.createClient ()
utilisera l'hôte 127.0.0.1
et le port 6379
mais c'est notre raison d'être. l’instance redis de production est sur un hôte différent, nous allons prendre celle que nous avons mise dans notre configuration.
Nous devrions ajouter quelques commandes de console sur les écouteurs de connexion et d’erreur que nous fournit le redisClient. Il est toujours bon d'ajouter la journalisation, en particulier pendant le développement, donc si nous sommes bloqués et que quelque chose ne fonctionne pas, nous avons beaucoup d'informations pour nous dire ce qui ne va pas.
Nous devons gérer les cas suivants dans notre API. couche:
-
POST
à notre cache Redis - Spotify
lastPlayedTrack
- Titre
- Artiste
- Album Cover Asset URL
- Spotify
vrefresh_token
- Spotify
access_token
-
GET
de notre mémoire cache Redis - Identique à
POST
fonction asynchrone callStorage (... argument, ) {
const redisClient = connectToRedis ()
réponse const = wait redisClient [method] (... args)
redisClient.quit ()
retour réponse
}
Puisque nous demandons des données à une ressource externe, nous voudrons utiliser async / wait
pour indiquer à notre programme que ce point final contient une fonction qui . ] toujours retourne une promesse et qu'il faut attendre qu'elle soit renvoyée avant de continuer.
Dans nos arguments, nous extrayons notre méthode d'argument connue et requise et nous affectons le reste (. ..
) des paramètres des arguments constants
Nous appelons notre client Redis en utilisant la notation entre crochets ce qui nous permet de transmettre une variable comme méthode. Nous utilisons encore l'opérateur de propagation, ...
to expand our args Array into a list of arguments with the remaining items. A call to http://localhost:3000/api/spotify/data/test?value=1
would result in a call to the redis client of redisClient['set']('test', 1)
. Calling redisClient['set']()
is exactly the same as calling redisClient.set()
.
Make a note that we must quit()
to close our redis connection each time we open it.
function storageArgs(key, ... { expires, body, ...props }) {
const value = Boolean(body) ? JSON.stringify(body) : props.value
return [
Boolean(value) ? 'set' : 'get',
key,
value,
Boolean(expires) ? 'EX' : null,
expires
].filter(arg => Boolean(arg))
}
We know that we can get two types of inputs: either a JSON body or a string value. All we really need to do is check if body
exists, and we’ll assume it’s JSON and stringify it. Otherwise, we’ll use props.value
. If it’s empty, it will be null. We’ll assign whichever we get back from the ternary statement to the const value. Make note that we are not destructuring value from the rest (...
) of props because we need to assign body to value if it exists.
The first index of the array we are returning, position 0
will be the method we call on the redis client. We’re making a Boolean check in case something other than null is passed, like undefined. If there is a value, this will return true and our method will be set. If false, get
.
Index 1 and index 2 are our key and value, respectively.
The 3rd and 4th positions are used to set an expiration date on the key. This comes in handy for our access_token
which will expire every few minutes to protect the integrity of our application.
As you may have suspected, we don’t want a null or undefined value in our array, so if there is no value, we’ll want to remove it. There are several ways to handle this, but the most readable is to use Array’s method filter()
. This creates a new Array, removing any items that don’t match our condition. Using a Boolean()
type coercion, we can check for a true or false. A null or undefined argument in our array will be removed, leaving us with an array of arguments we can trust to return back to the caller.
const app = express()
app.use(express.json())
// Express app
app.all('/spotify/data/:key', async ({ params: { key } }, res) => {
try {
if (key === ('refresh_token' || 'access_token'))
throw { error: '? Cannot get protected stores. ?' }
const reply = await callStorage(...storageArgs(key))
res.send({ [key]: reply })
} catch (err) {
console.error(`n? There was an error at /api/spotify/data: ${err} ?n`)
res.send(err)
}
})
Make note of app.use(express.json())
. This gives us access to body on the request object. We’ll also be wrapping our endpoint procedures in try/catch blocks so we don’t wind up with uncaught errors. There are other ways to handle errors, but this is the simplest for our application.
Note: Check out this awesome demo of different errors by Wes Bos on Error Handling in Nodejs with async/await
.
We want to make sure that this endpoint doesn’t return any of the data we’re trying to hide, so after we grab our key by destructuring the request object, we’ll throw an error letting the client know they can’t get those stores. Make note that when we know the structure of an incoming Object’s structure in JavaScript ES6, we can use curly braces to pull out variable names using the Object’s keys.
const reply = await callStorage(...storageArgs(key))
We’re calling the function named callStorage
. Because we may have 3 or 4 arguments, we’re passing in rest parameters using a spread of our args Array. In the call, above, we use ...
to expand an Array into our list of arguments of an unknown size, which are built from the function StorageArgs()
.
res.send({ [key]: reply })
} catch (err) {
console.error(`n? There was an error at /api/spotify/data: ${err} ?n`)
res.send(err)
}
})
Now that we have our reply from the redis client, we can send it to the client via the response Object’s method send()
. If we POSTed to our cache, we’ll get a 1
back from the server if it’s a new key and 0
if we replaced an existing key. (We’ll want to make a mental note of that for later.) If there’s an error, we’ll catch it, log it, and send it to the client.
We’re ready to call the redis client and begin setting and getting our data.

Now let’s send a few test cURLs to our API endpoint in our command line or Terminal:
$ curl --request POST http://localhost:3000/api/spotify/data/test?value=Hello
{"test": 1}
$ curl http://localhost:3000/api/spotify/data/test
{"test": "Hello"}
$ curl --request POST
http://localhost:3000/api/spotify/data/bestSong
--header 'Content-Type: application/json'
--data '{
"name": "Break up with ur gf, I''’m bored",
"artist": "Ariana Grande"
}'
{"bestSong": 1}
$ curl http://localhost:3000/api/spotify/data/bestSong
{"bestSong":"{"name":"Break up with ur gf, I’m bored","artist":"Ariana Grande"}"}
Connecting With Spotify
Our remaining to-do list has shrunk considerably:
- Callback from Spotify
- Refresh our Spotify
access_token
GET
recently played track from SpotifyGET
currently playing track from Spotify
A callback is a function that must be executed following the completion of a prior function. When we make calls to Spotify’s API, they will “call us back”, and if something isn’t quite right, Spotify’s server will deny us access to the data we requested.
import axios from 'axios'
Our callback will need to do a couple of things. First, it will capture a response from Spotify that will contain a code we need temporarily. Then, we’ll need to make another call to Spotify to get our refresh_token
which you may recognize from our redis storage planning. This token will give us a permanent connection to Spotify’s API as long as we are on the same application logged in as the same user. We’ll also need to check for our userId
for a match before we do anything else, to prevent other users from changing our data to their own. Once we confirm we are the logged in user, we can save our refresh_token
and access_token
to our redis cache. Because we’re making API calls in our callback function, we’ll need to import axios to make requests, which nuxt installed when we scaffolded the app.
Note that JavaScript has a native fetch()
method, but it’s very common to see axios used instead, because the syntax is more user-friendly and legible.
const getSpotifyToken = (props = {}) =>
axios({
method: 'post',
url: 'https://accounts.spotify.com/api/token',
params: {
client_id: process.env.SPOTIFY_CLIENT_ID,
client_secret: process.env.SPOTIFY_CLIENT_SECRET,
redirect_uri: `${process.env.CLIENT_URL}/api/spotify/callback`,
...props
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
One of the benefits to using a function expression instead of an arrow function expression is that you have access to an inherit object called arguments which is mapped by index, you also get access to a contextual this object. While we don’t need access to a lexical this, since we’re only returning the response of our redisClient
call, we can omit closures here and implicitly return the response of the call.
We’ll want to write a single function for getting Spotify tokens. The majority of the code for getting our refresh_token
and access_token
is basically the same, so we can write an axios POST
boilerplate, and spread (...
) a props Object. Spreading an Object expands its properties into the context parent Object at the root depth, so if we spread { grant_type: 'refresh_token' }
our params Object will be extended to contain the properties of {client_id, client_secret, redirect_url, grant_type }
. Again, we forego a return with an arrow function and opt for an implicit return since this function only returns a single response.
Note that we set props in the arguments as an empty Object ({}
) by default just in case this function gets called without an argument. This way, nothing should break.
const spotifyBaseUrl = 'https://api.spotify.com/v1/'
const getUserData = access_token =>
axios.get(`${spotifyBaseUrl}me`, {
headers: {
withCredentials: true,
Authorization: `Bearer ${access_token}`
}
})
In order to check to see we are the user who logged in via Spotify, we’ll write another implicitly returned arrow function expression and call Spotify’s Get Current User’s Profile method (the one we tested earlier to get our SPOTIFY_USER_ID
). We set a const here with the base API URL because we will use it again in our other calls to the library. Should this ever change in the future (like for Version 2), we’ll only have to update it in once.
We now have all of the functions we need to write our callback endpoint. Make note of the fact that this will be a client-facing endpoint.
app.get('/spotify/callback', async ({ query: { code } }, res) => {
try {
const { data } = await getSpotifyToken({
code,
grant_type: 'authorization_code'
})
const { access_token, refresh_token, expires_in } = data
const {
data: { id }
} = await getUserData(access_token)
if (id !== process.env.SPOTIFY_USER_ID)
throw { error: "? You aren’t the droid we’re looking for. ?" }
callStorage(...storageArgs({ key: 'is_connected', value: true }))
callStorage(...storageArgs({ key: 'refresh_token', value: refresh_token }))
callStorage(
...storageArgs({
key: 'access_token',
value: access_token,
expires: expires_in
})
)
const success = { success: '? Welcome Back ?' }
res.redirect(`/auth?message=${success}`)
} catch (err) {
console.error(
`n? There was an error at /api/spotify/callback: ${err} ?n`
)
res.redirect(`/auth?message=${err}`)
}
Our callback endpoint must match the URL we added to our settings in the Spotify dashboard exactly. We used /api/spotify/callback
so we’ll get at /spotify/callback
here. This is another async function, and we need to destructure code from the request object.
We call the function we wrote earlier, getSpotifyToken()
to get our first access_token
our refresh_token
and our first expires_in
. We’ll want to save all three of these to our redis cache, using redis’ set method’s built-in key timeout command to expire our access_token
in expires_in
seconds. This will help us set up a system of refreshing our access_token
when we need it. Redis will set the access_token
to null after the time to live (TTL) has reached 0 milliseconds.
Now that we have an access_token
we can make sure that the user who connected is us. We call getUserData()
the function we wrote earlier, and destructure the ID to compare to the user ID we saved to our environment configuration. If it’s not a match, we’ll throw an error message.
After we’re sure that our refresh_token
is trusted, we can save our tokens to our redis cache. We call callStorage
again — once for each token.
Make note that redis does have methods for setting multiple keys, but because we want to expire our access_token
we need to use set()
.
Since this is a client-facing endpoint, we’ll redirect to a URL and append a success or error message for the client to interpret. We’ll set this path up later on the client side.
We’ll need to retrieve our access_token
and refresh it if necessary before we call any other Spotify endpoints. Let’s write an async function to handle that.
async function getAccessToken() {
const redisClient = connectToRedis()
const accessTokenObj = { value: await redisClient.get('access_token') }
if (!Boolean(accessTokenObj.value)) {
const refresh_token = await redisClient.get('refresh_token')
const {
data: { access_token, expires_in }
} = await getSpotifyToken({
refresh_token,
grant_type: 'refresh_token'
})
Object.assign(accessTokenObj, {
value: access_token,
expires: expires_in
})
callStorage(...storageArgs('access_token', { ...accessTokenObj }))
}
redisClient.quit()
return accessTokenObj.value
}
We assign a const accessTokenObj
to an Object with the value of our redis get('access_token')
. If the value is null, we’ll know it’s expired and we need to refresh it. After getting our refresh_token
from our cache, and getting a new access_token
we’ll assign our new values to accessTokenObj
set()
them in redis, and return the access_token
.
Let’s get write our endpoint for getting the currently-playing track. Since we’ll only want recently-played if there’s nothing currently playing, we can write a function for our endpoint to call that handles getting that data if it’s needed.
app.get('/spotify/now-playing/', async (req, res) => {
try {
const access_token = await getAccessToken()
const response = await axios.get(
`${spotifyBaseUrl}me/player/currently-playing?market=US`,
{
headers: {
withCredentials: true,
Authorization: `Bearer ${access_token}`
}
}
)
const { data } = response
setLastPlayed(access_token, data)
const reply = await callStorage('get', 'last_played')
res.send({
item: JSON.parse(reply),
is_playing: Boolean(data.is_playing),
progress_ms: data.progress_ms || 0
})
} catch (err) {
res.send({ error: err.message })
}
})
async function setLastPlayed(access_token, item) {
if (!Boolean(item)) {
const { data } = await axios.get(
`${spotifyBaseUrl}me/player/recently-played?market=US`,
{
headers: {
withCredentials: true,
Authorization: `Bearer ${access_token}`
}
}
)
postStoredTrack(data.items[0].track)
} autre {
postStoredTrack(item)
}
}
function postStoredTrack(props) {
callStorage(
...storageArgs({
key: 'last_played',
body: props
})
)
}
The endpoint gets the Get the User’s Currently Playing Track endpoint and the async function setLastPlayed()
calls the Get Current User’s Recently Played Tracks if nothing is returned from currently-playing. We’ll call our last function postStoredTrack()
with whichever one we have, and retrieve it from our cache to send to the client. Note the we cannot omit the else
closure because we aren’t returning anything in the if
closure.
Vuex: Client-Side Storage And State Management
Now that we have middleware to connect to our services by proxywe can connect those services to our client-side application. We’ll want our users to have automatic updates when we change songs, pause, rewind, or fast-forward, and we can handle those changes with state management.
State is our application’s way of holding onto information in real-time. It is how our application remembers the data it uses, and any changes to that data. State is really a short way of saying “the state of the system’s data”. The state of a Vue application is held in a user’s browser session, and with certain patterns, we can trigger various events to mutate that state. When the state changes, our application can update without requiring storage or server calls.
The pattern we’ll use is called a store pattern. This gives us a single source of truth as a user moves about our application (even though we’ll only have two pages for this particular app).
Vue’s component lifecycle adds the necessary one-way bindings we need, and Nuxt comes with Vuex that does all of the heavy lifting when our data changes. We will want our state to be constantly updating, but we won’t to call our API every few milliseconds to keep a progress bar moving. Instead of constantly polling our API, and reaching Spotify’s rate limitwe can lean on Vuex setters to continuously update the state of our bindings.
The data we’ll be dealing with will only be bound one-way. This means that our component and page views can get the data in store, but in order to mutate that data, they will need to call an action in the store.

As you can see, the data only moves one way. When our application starts, we’ll instantiate our models with some default data, then we will hydrate the state in a middleware function expression built into Nuxt’s implementation of Vuex called nuxtServerInit()
. After the application is running, we will periodically rehydrate the store by dispatching actions in our pages and components.
Here’s the basic structure we’ll need to activate a store in store/index.js:
// instantiated defaults on state
export const state = () => {
property: null
}
// we don’t edit the properties directly, we call a mutation method
export const mutations = {
mutateTheProperty (state, newProperty) {
// we can perform logical state changes to the property here
state.property = newProperty
}
}
// we can dispatch actions to edit a property and return its new state
export const actions = {
updateProperty: ({ commit, state }, newProperty) => {
commit('mutateTheProperty', newProperty)
return state.property // will equal newProperty and trigger subscribers to re-evaluate
}
}
Once you feel comfortable, you can set up more shallow modular storeswhich Nuxt implements based on your file structure in store/
. We’ll use only the index module.
$ touch store/index.js
export const state = () => ({
isConnected: false,
message: null,
nowPlaying: {},
recentlyPlayed: {},
trackProgress: 0,
isPlaying: false
})
We’re going to need a few models to instantiate the state when our app starts. Note that this must be a function that returns an Object.
isConnected
: tells us if we’re already connected via Spotify.message
: tells us if there’s an error during authentication (we set these up in the API on our callback endpoint).nowPlaying
: the song (track) Object that is currently or recently playing.recentlyPlayed
: the track most recently played.trackProgress
: the amount of the track that has already played (a percentage).isPlaying
: if the nowPlaying track is currently being played.
To update these, we’ll need to add mutations for each model. You can mutate more than one model in a mutation function, but to keep things digestible, we’re going to write a flat mutations object.
export const mutations = {
connectionChange(state, isConnected) {
state.isConnected = isConnected
},
messageChange(state, message) {
state.message = message
},
nowPlayingChange(state, nowPlaying) {
state.nowPlaying = nowPlaying
},
isPlayingChange(state, isPlaying) {
state.isPlaying = isPlaying
},
progressChange(state, { progress, duration }) {
state.trackProgress = (progress / duration) * 100
},
recentlyPlayedChange(state, recentlyPlayed) {
state.recentlyPlayed = recentlyPlayed
}
}
We’re not doing much in the way of data massaging for this app, but for progress we’ll need to calculate the percentage ourselves. We’ll return an exact number from 0-100.
export const actions = {
async nuxtServerInit({ commit }) {
try {
const redisUrl = `${clientUrl}/api/spotify/data/`
const {
data: { is_connected }
} = await axios.get(`${redisUrl}is_connected`)
commit('connectionChange', is_connected)
if (Boolean(is_connected)) {
const {
data: { item, is_playing }
} = await axios.get(`${clientUrl}/api/spotify/now-playing`)
commit('nowPlayingChange', item)
commit('isPlayingChange', is_playing)
}
} catch (err) {
console.error(err)
}
},
updateProgress: ({ commit, state }, props) => {
commit('progressChange', props)
return state.trackProgress
},
updateTrack: ({ commit, state }, nowPlaying) => {
commit('nowPlayingChange', nowPlaying)
return state.nowPlaying
},
updateStatus: ({ commit, state }, isPlaying) => {
commit('isPlayingChange', isPlaying)
return state.isPlaying
},
updateConnection: ({ commit, state }, isConnected) => {
commit('connectionChange', isConnected)
return state.isConnected
}
}
nuxtServerInit()
will be run when our server starts automatically, and will check if we are connected to Spotify already with a query to our redis data endpoint. If it finds that the redis cache key of is_connected
is true, it will call our “now-playing” end point to hydrate nowPlaying
with live data from Spotify, or whatever is already in the cache.
Our other actions take our store object and destructure commit()
and state with our new data, commit()
the data to the store with our mutations, and return the new state to the client.
Building The Pages And Components
Now that we have our API setup to give us data from Spotify and our store, we’re ready to build our pages and components. While we’re only going to make a couple of small pieces in this tutorial for brevity, I encourage liberal creativity.
We’ll need to remove the initial pages that the Nuxt scaffolding added, and then we’ll add our components and pages.
$ rm pages/index.vue components/Logo.vue layouts/default.vue
$ touch pages/index.vue components/NowPlaying.vue components/Progress.vue
The basic structure of every layout, page, and component in a single file component is the same. In fact, every layout, page, and component in Nuxt is a Vue component.
You can read further usage outside of the scope of this tutorial on Vue’s component registration documentation. We’re just going to do everything in the file and use plain HTML and CSS.
The repository for the demo will contain some components and styles that are not in this tutorial in order to keep things a little less complex.
// Write plain HTML here, avoid using any logic here
Layout
We need to start with the default layout; this is the root of the application, where Vue will be mounted. The layout is a type of viewof which every page extends. This means that the HTML found in the layout will be the basis of all the html in every page we create.
Login
In the template tag, we need a single root container, and
is where our application will mount.
Note: In the demo code, I’ve added a
and a and the footer is a functional component because all of the data is static.
In this tutorial, I’ve added a
pointed to /auth
.
creates navigational links for routes within your app. I’ve added a conditional aria-current attribute to nuxt-link. By adding a colon (:
) in front of the attribute, I’ve indicated to Vue that the value of the attribute is bound to some data, turning the value into JavaScript that will be interpreted as a string during the component lifecycle, depending on the condition of the expression. In a computed ternary statement, if the user on the route named auth
it will set the aria-current attribute to “page”, giving screen readers context to whether or not the user is on the path the link is pointed to. For more information on Vue’s data-binding, read this documentation.
The script tag can be thought of like a single JavaScript module. You can import other modules, and you export an Object of properties and methods. Above, we set two custom properties: titleShort
and authorName
. These will be mounted onto this.$options
and down the component tree you can access them through $nuxt.layout
. This is useful for information you use at the root level, and in deep-nested children, like for updating the document title, or using our authorName
in other contexts.
There are several functions that Vue will look for and run, like head()
and computed()
in the above example.
head()
will modify the of the HTML document. Here I’ll update the document title, and add a link.
The computed()
method is for reactive data that needs to be evaluated. Whenever the shape of the data changes, it triggers a re-evaluation and a subsequent re-render of the node it is bound to.
In the CSS, you’ll notice I’m using a non-standard font, but no @import
declaration. Since these are rendered on the server, they won’t be able to reach an external resource that isn’t in the build. We can still attach external resources — we just need to do it in a different way. There are workarounds that exist for this, but we just added it to our head()
. You can also add it to nuxt.config.js
.
The :root
selector allows us to set global CSS variables we can use throughout the application. .nuxt-progress
selector is for the progress bar that Nuxt adds during build automatically. We can style it here. I’ve just moved it to the bottom of the app and made it transparent and small.
Authentication Page
Now that we have a default layout, we can work on our authentication page. Pages are another kind of view in Nuxt, which render the HTML, CSS, and JavaScript that is needed for specific routes.
Pages and routes are automatically handled for every Vue file inside of the pages directory. You can also add more complex routing.
Everything has led us to this moment! Finally, we get to render some of our API-retrieved data!
Close
{{ message }}
is used to add transitions between pages and components mounting and unmounting. This will add conditional class names related to the name, and the mode “in-out” will make our transition happen both on entry and exit. For further usage, check out the documentation.
We get at data in the with double curly braces {{}
}. this is implied, so we don’t need to include it in the .
The first thing we need to do is redirect to the authentication server, which will call us back at our callback API proxy, and we setup to redirect us back to /auth
or this file we’re in now. To build the URL, we’ll need to get the environment variables we attached to the context object under the env parameter. This can only be done in pages. To access the context object, we’ll need to add the asyncData()
method to our Object.
This function will be run before initializing the component, so make note that you do not have access to a component’s lexical this (which is always in the context of the local $nuxt
Object) in this method because it does not exist yet. If there is async data required in a component, you will have to pass it down through props from the parent. There are many keys available in context, but we’ll only need env and query. We’ll return spotifyUrl
and query
and they will be automatically merged with the rest of the page’s data.
There are many other lifecycle methods and properties to hook onto, but we’ll really only need mounted()
and computed, data()
props, components, methods, and beforeDestroy()
. mounted()
ensures we have access to the window Object.
In mounted()
we can add our logic to redirect the user (well, us) to login via Spotify. Because our login page is shared with our authentication status page, we’ll check for the message Object we sent back from our callback redirect. If it exists, we will bypass redirecting so we don’t end up in an infinite loop. We’ll also check to see if we’re connected. We can set window.location
to our spotifyUrl
and it will redirect to the login. After logging in, and grabbing the query Object, we can remove it from our URL so our users don’t see it with window.history.replaceState({}
document.title
window.location.pathname
). Let’s commit and dispatch the changes to our state in message and isConnected.
In computed()
we can return our properties from the store and they will be automatically updated on the view when they change.
Note that all properties and methods will have access to the lexical this once the component has been initialized.
Note the scoped attribute added to
We’ll need to import our NowPlaying
component (we will write it next), and we’ll want to conditionally load it with a v-if binding based on whether or not we are connected and we have track data to show. Our computed nowPlaying()
method will return the nowPlaying
Object if it has properties (we instantiated an empty object in the store, so it will always exist), and we’ll dispatch an action that we’re connected. We’re passing the track and isPlaying
props since they are required to show the component.
We’ll need to create our components next, otherwise this page won’t build.
Components
In Nuxt, components are partial views. They cannot be rendered on their own, and instead, can only be used to encapsulate parts of a layout or page view that should be abstracted. It’s important to note that certain methods Page views have access to, like asyncData()
won’t be ever be called in a component view. Only pages have access to a server-side call while the application is starting.
Knowing when to split a chunk of a layout, page, or even component view can be difficult, but my general rule of thumb is first by the length of the file, and second by complexity. If it becomes cumbersome to understand what is going on in a certain view, it’s time to start abstracting.
We’ll split our landing page in three parts, based on complexity:
Index
component: The page we just wrote.NowPlaying
component: The container and track information.Progress
component: The animated track progress indicator.
Now Playing
It’s important we include a link to Spotify, as it is a part of the requirements to use their API free of charge. We’re going to pass the progress and image props to our component.
In addition to our computed()
data, we will also have another type of reactive data on the data property. This property returns an Object with reactive properties, but these do not need to be re-evaluated. We will be using them for our timing intervals, so the updates will be come from setInterval()
.
created()
runs when our component is done being initialized, so we’ll call our function getNowPlaying()
and start one of our two interval timers, staleTimer
which will run getNowPlaying()
once every 10 seconds. You can make this shorter or longer, but keep in mind that Spotify does have rate limiting, so it shouldn’t be any less than a few seconds to avoid getting undesired API failures.
It’s important we add beforeDestroy()
and clear our running intervals as a best practice.
In the methods property, we’ll have three functions: getNowPlaying()
updateProgress()
and timeTrack()
. updateProgress()
will dispatch progress updates to the store, while getNowPlaying()
and timeTrack()
will do the heavy lifting of keeping our track object hydrated and the progress bar moving every 10th of a second so we have a constantly moving progress bar.
Let’s take a closer look at getNowPlaying()
:
async getNowPlaying() {
const { progress_ms, is_playing, item } = await this.$axios.$get(
`/api/spotify/now-playing/`
)
if (Boolean(item)) {
const progress = progress_ms
const duration = item.duration_ms
this.$store.dispatch('updateStatus', is_playing)
clearInterval(this.trackTimer)
if (is_playing) {
this.timeTrack(Date.now(), duration, progress)
} autre {
this.updateProgress(progress, duration)
}
const { id } = this.nowPlaying
if (item.id !== id) {
this.$store.dispatch('updateTrack', item)
}
}
This is an async function because we’re calling out now-playing endpoint, and we’ll want the function to wait until it has an answer to continue. If the item is not null or undefined, we’ll dispatch an update to the status, clearInterval()
of our trackTimer
(which may not be running, but that’s OK). If the is_playing
is true, we’ll call timeTrack()
; if it’s false, we’ll call updateProgress()
. Last, we’ll check if our updated track is different than the one in our store. If it is, we’ll dispatch an update to the track in store to rehydrate our data.
timeTrack(now, duration, progress) {
const remainder = duration - progress
const until = now + remainder
this.trackTimer = setInterval(() => {
const newNow = Date.now()
if (newNow
This function takes a current time, duration, and progress in milliseconds and starts running an interval every 100 milliseconds to update the progress. until
is the time calculated when the track will be finished playing if it is not paused or scrubbed forwards or backwards. When the interval starts, we grab the current time in milliseconds with JavaScript’s Date Object’s now()
method. We’ll compare the current time to see if it is less than until plus a buffer of 2500 milliseconds. The buffer is to allow for Spotify to update the data between tracks.
If we determine the track is theoretically still playing, we’ll calculate a new progress in milliseconds and call out the updateProgress()
function. If we determine the track is complete, we’ll update the progress to 100%, clearInterval()
and call nowPlaying()
to get the next track.
section
is a display-type grid that keeps the album art and song metadata in two columns, and then on viewports up to 600px wide (the layout switches to two rows).
Progress
Now let’s build our Progress component. A simple solution is a bar using the width of a
I wanted to do something a bit different, so I’ve built a square out in SVG:
Above, we create two rect SVGs. One has a pattern fill of our image, the other is the progress bar. It’s important that whatever shape you use has a total perimeter of 100
. This allows us to use the stroke-dasharray to fill the space based on a percentage. The left value is the length of the stroke, the right value is the space between the strokes. The stroke size getting larger pushes the space out of the frame and eventually is the entire length of the perimeter. We added an animation that fills the progress bar from 0
to its current point when the component is rendered.
Head to localhost:3000 and if we did everything right (and you’re playing a song) we should see something like this:

Awesome! ?
Publishing Our Application
Let’s get everything up into our repository!
$ git add .
$ git commit . -m 'Adds Nuxt application ?'
$ git push .
[master b63fb2d] Adds Nuxt application ?.
If you look into your Heroku dashboard and look at the activity feed on the right-hand panel, there should be a build and a deployment:

If everything looks good, open your site!
$ heroku open
Log in with Spotify on production and start sharing your jam sessions!
?
Conclusion
Phew! We built a universal, server-side rendered application, wrote an API proxy on our server, connected to a Redis cache, and hosted on our application on Heroku. That’s pretty awesome!
Now that we know how to build an application using Nuxt, and have an understanding of what kind of data we should handle securely on the server, the possibilities for interesting applications are endless!
Build On Your Knowledge
Spotify’s API has a medley of endpoints to add more interesting experiences to the application we built, or for composing entirely new ones! You can fork my repository to explore some other components I’ve coded, or read through the docs and apply what you’ve learned to share more musical ideas!
Further Reading on SmashingMag:

Source link