Fermer

mars 12, 2019

Créer une application utilisant Spotux avec Nuxt.js


Avec Spotify, l'apprentissage automatique et les médias sociaux sont devenus musicaux. Grâce à leur application, vos amis peuvent vérifier ce sur quoi vous êtes en train de jouer. Et si le reste de l'Internet pouvait expérimenter votre algo-rythme aussi? Dans ce tutoriel, vous apprendrez à composer votre propre application pour partager ce que vous écoutez sur Spotify à l’aide de Vue.js et de Nuxt.

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 .

 Une image représentant le produit final du didacticiel. Contient les informations de la chanson «Rompre avec ta petite amie, je m'ennuie» de Ariana Grande avec un lien vers la chanson sur Spotify et une photo de la pochette de l'album
Résultat final créé avec Nuxt.js, Redis, et Spotify. ( Grand aperçu )

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

  1. Planning Our Application
    Nous exposerons les fonctionnalités attendues et une représentation visuelle de ce que nous prévoyons voir une fois terminé.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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: // .herokuapp.com .

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.

 Github sélectionné sur une capture d'écran de la zone du tableau de bord de Heroku
Github sélectionné dans Heroku tableau de bord ( Grand aperçu )
 Sélection du référentiel sous Github sur une capture d'écran de la zone du tableau de bord de Heroku
Sélection du référentiel sous Github ( Grand aperçu )
 Configuration du déploiement automatique avec Github sur une capture d'écran du tableau de bord de Heroku
Configuration du déploiement automatique avec Github ( Grand aperçu )

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"
   },
package.json

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:

 Une capture d'écran de l'application initiale Nuxt. La demande initiale contient le titre de la demande, le logo animé de Nuxt et des liens vers la documentation de Nuxt
Etat initial de la demande Nuxt après un échafaudage ( Grand aperçu )

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.

 Une capture d'écran de l'application hébergée par Heroku. La page parquée initiale indique «Bienvenue dans votre nouvelle application!» Et contient un lien vers la documentation de Heroku
Page parquée par défaut pour notre nouvelle application Heroku ( Grand aperçu )

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.

 Une capture d'écran du client. Identifiants et jetons de secret client dans le tableau de bord de Spotify - l'identifiant et le secret de client ont été brouillés
Ne partagez jamais vos identifiants de client et vos jetons de secret client! ( Grand aperçu )
 $ 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).

 Capture d’écran des paramètres de modification du tableau de bord de Spotify. Sous les adresses URI de redirection, deux entrées ont été ajoutées: "http: // localhost: 3000 / api / callback" et "http://cherislistening.herokuapp.com/api/callback" .[19659009HERlhistederappeldesURLdansletableaudeborddeSpotify(<a href= Grand aperçu )

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 = 
.env

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
  }
nuxt.config.js

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 HTMLainsi 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']
 }
nuxt.config.js

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']
   }
nuxt.config.js

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"
   },
package.json

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?

 Un modèle conçu pour montrer comment nous allons déplacer nos données dans notre application - au centre, se trouve notre couche d'API interne - connectée à celui-ci les données de deux manières sont l'API de Spotify, Redis Storage, et le client - le client est divisé en une Vue Page et une Vuex Store
Plan de flux de données dans un graphique ( Grand aperçu )

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
 }
api / index.js

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
 }
api / index.js

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.

 L’image décrit le chemin du point de terminaison sous la forme de spotify. / data, param en tant que /: clé, demande en tant que demande, et réponse en tant que résolution
Décomposition d'une fonction de point de terminaison API dans Expressjs ( Grand aperçu )

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
}
api / index.js

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
 }
api / index.js

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))
 }
api/index.js

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 0will 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_tokenwhich 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)
   }
 })
api/index.js

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))
api/index.js

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)
   }
 })
api/index.js

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.

The image maps a test parameter in the API endpoint to the key argument in the Redis client call function, and a query of value set to 1 mapped to the second argument of value
Mapping an endpoint to a Redis server call (Large preview)

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 Spotify
  • GET 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'
api/index.js

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_tokenwhich 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'
    }
  })
api/index.js

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}`
     }
   })
api/index.js

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}`)
  }
api/index.js

Our callback endpoint must match the URL we added to our settings in the Spotify dashboard exactly. We used /api/spotify/callbackso 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_tokenour refresh_tokenand 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_tokenwe 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_tokenwe 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
}

api/index.js

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_tokenwe’ll assign our new values to accessTokenObjset() 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
    })
  )
}
api/index.js

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.

A model drawn to show how data flows one way in our app - in the center is our Vuex store, which sends data our view, the pages and components - the view calls actions to mutate the model, which in turn update the Vuex store
One way data flow in our application (Large preview)

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
})
store/index.js

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
  }
}
store/index.js

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
  }
}
store/index.js

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.

  

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.


layouts/default.vue

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