https://d226lax1qjow5r.cloudfront.net/blog/blogposts/building-a-slack-clone-using-vue-js-part-1-dr/Blog_Slack-Clone_1200x600.png

Construire un clone de Slack en utilisant Vue Partie 1

Publié le April 26, 2021

Temps de lecture : 70 minutes

Construire une application de chat Vue.js semblable à Slack

Avez-vous déjà voulu créer une application de chat, mais vous êtes bloqué sur les fonctionnalités à ajouter, ou juste comment le rendre généralement ? Dans cet article, vous allez construire un clone du logiciel de chat préféré de tous, Slack. En utilisant Vue.js, le framework préféré de tous. Et Vonage Conversation API, le service de conversation préféré de tous.

Ce billet est la première partie d'une série de tutoriels en plusieurs parties qui va nous permettre de passer d'un répertoire vide à une application réelle présentant plusieurs des fonctionnalités de Slacks qui définissent le genre.

Voici quelques-unes des choses que vous apprendrez dans ce billet :

Si vous êtes intéressé par l'application de démonstration complète, sans passer par le guide, veuillez consulter le repo GitHub de mon clone Vue js Slack. GitHub pour mon clone Vue.js Slack jusqu'à présent.

Conditions préalables

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

Node et NPM

Pour commencer, vous aurez besoin d'installer Node et NPM. Ce guide utilise Node 8 et NPM 6. Vérifiez qu'ils sont installés et à jour.

node --version npm --version

Node et NPM doivent être installés et à la bonne version. Allez sur nodejs.orgtéléchargez et installez la bonne version si vous ne l'avez pas.

Notre CLI

Pour mettre en place votre application, vous devez installer notre CLI. Installez-le en utilisant NPM dans le terminal.

npm install -g nexmo-cli@beta

Vous pouvez vérifier que vous avez la bonne version avec cette commande. Au moment où j'écris ces lignes, j'utilise la version 0.4.9-beta-3.

nexmo --version

Pour suivre les étapes de cet article, n'oubliez pas de de vous inscrire à un compte Vonage gratuit et de configurer le CLI avec la clé API et le secret qui se trouvent sur votre tableau de bord.

nexmo setup

CLI Express.js

Installer Générateur Express. Vous utiliserez cette bibliothèque pour générer un serveur Express.js de base.

npm install -g express-generator

Vous pouvez vérifier que vous avez la bonne version avec cette commande. Au moment où j'écris ces lignes, j'utilise la version 4.16.1.

express --version

CLI Vue.js

Installer le CLI de Vue. Vous utiliserez cette bibliothèque pour générer une application client Vue.js de base.

npm install -g @vue/cli

Vous pouvez vérifier que vous avez la bonne version avec cette commande. Au moment de la rédaction de ce document, j'utilisais la version 4.1.2 de @vue/cli.

vue --version

Partir de zéro

Cette série va vous emmener d'un répertoire vierge jusqu'à une application de chat du monde réel en utilisant Express.js comme serveur.

Créer un dossier de projet

Tout d'abord, créez un répertoire pour votre travail.

mkdir vuejs-slack-clone

Ensuite, il faut changer de répertoire.

cd vuejs-slack-clone

Générer un serveur Express.js

Ensuite, créez un serveur de base en utilisant le générateur Express.js. Ce que j'aime dans cette CLI, c'est qu'elle configure l'exécutable du serveur et l'application indépendamment l'un de l'autre. En d'autres termes, il reprend la philosophie de l'application extrêmement légère et cool Express Hello World. Il le divise en un fichier exécutable tout aussi cool pour configurer le serveur et l'environnement bin/wwwet l'application elle-même app.js.

L'application étant principalement une API, il est préférable de ne pas installer ce qui sert à gérer les fichiers de modèles. Pour cela, utilisez l'option --no-view option.

Si vous envisagez d'utiliser git comme système de contrôle de version, vous devriez envisager d'utiliser --git pour générer le fichier .gitignore correct.

Comme vous êtes déjà dans le répertoire du projet, spécifiez l'option --force et utilisez . comme répertoire. L'outil générera alors l'application dans le répertoire actuel sans problème.

express --git --no-view --force .

Ensuite, installez les dépendances.

npm install

Exécuter le serveur Express.js localement

Une fois que le serveur a été créé et que les dépendances ont été installées, vous pouvez le démarrer pour vous assurer que tout fonctionne comme prévu.

npm start

Vous pouvez vérifier qu'il fonctionne à l'URL par défaut, localhost:3000.

Screenshot of a basic Express.js server running

Routes et contrôleurs

L'application générée comprend le routage nécessaire. Le routage consiste à déterminer comment une application traite une demande adressée à une URL et à une méthode particulières (GET, POST, etc.). Les contrôleurs, quant à eux, sont responsables du flux d'exécution de l'application. L'application générée ne crée pas de contrôleurs et utilise les routeurs pour renvoyer une réponse.

Créez un nouveau répertoire de contrôleur.

# mkdir is a command that makes a directory mkdir controllers

Créer un nouveau contrôleur dans ce répertoire nommé server.js.

# touch is a command that will create an empty file touch controllers/server.js

Ouvrir controllers/server.js et créez la première méthode pour le serveur.

// controllers/server.js
exports.status = function(req, res, next) {
  res.json({
    status: 'ok'
  });
};

Ce contrôleur pourrait ensuite être chargé de fournir au client une condition, en fonction de diverses vérifications, par exemple si le service de chat est opérationnel ou s'il peut se connecter aux données. L'idée est que si un problème survient sur le serveur, le client recevra l'erreur, la traitera avec élégance et informera l'utilisateur de ce qui s'est passé.

Pour demander cette méthode du contrôleur, créez une nouvelle route dans le répertoire routes existant, nommée server.js.

touch routes/server.js

Ouvrez routes/server.js et ajoutez le code ci-dessous.

// routes/server.js
var express = require('express');
var router = express.Router();

var serverController = require('../controllers/server');

router.get('/status', serverController.status);

module.exports = router;

Cela permet d'acheminer un chemin (/status) vers une méthode du contrôleur (serverController.status). La route fournit le résultat de la méthode du contrôleur au client en tant que réponse.

Pour ajouter cet itinéraire à l'application, vous devez éditer app.js et effectuer ces changements.

// app.js
- var indexRouter = require('./routes/index');
- var usersRouter = require('./routes/users');

...

- app.use('/', indexRouter);
- app.use('/users', usersRouter);
+ app.use('/api/server', require('./routes/server'));

Vous pouvez ensuite supprimer les éléments routes/index.js et routes/users.js et

Relancez l'application avec npm start; vous pouvez alors accéder à la nouvelle route à l'adresse suivante localhost:3000/api/server/status.

Screenshot of a basic server status API endpoint

Création d'un client

Utilisez l'interface de programmation Vue pour créer une nouvelle application client.

Générer un client Vue.js

Exécutez la commande create avec l'interface de commande Vue. Cet outil génère une application Vue simple sur laquelle nous baserons notre client de chat. Il propose quelques options et vous pouvez sélectionner les valeurs par défaut.

vue create client

Le client est généré dans le répertoire client comme spécifié dans la commande. Il s'exécute également npm install automatiquement.

Maintenant, allez dans le répertoire client répertoire.

cd client

Pour exécuter le client, utilisez cette commande. Notez qu'elle est différente de celle utilisée pour lancer le serveur.

npm run serve

Vous pouvez ensuite accéder à votre client à l'adresse suivante localhost:8080. Vous remarquerez qu'il a un port différent par défaut et dans l'environnement de développement, cela nous aide, comme vous le découvrirez plus loin, à faire fonctionner le serveur et le client simultanément.

Screenshot of a basic Vue.js client running

Rechargement à chaud des fichiers du serveur Express.js

En général, au cours du processus de développement, la plupart des gens souhaitent que l'application recharge automatiquement les fichiers au fur et à mesure qu'ils les modifient. Pour ce faire, nous allons configurer le serveur pour qu'il utilise nodemon pour servir les fichiers.

Installer Nodemon

Si vous êtes toujours dans le répertoire client de tout à l'heure, vous pouvez revenir au répertoire principal du projet en remontant d'un niveau avec cette commande, .. qui indique un répertoire parent.

cd ..

Maintenant, installez Nodemon en tant que dépendance de développement. Installez une dépendance de développement en ajoutant --save-dev comme option de la commande.

npm install nodemon --save-dev

Une fois installé, vous pouvez éditer le fichier package.json et modifier le script start comme indiqué ici.

+     "dev:server": "nodemon ./bin/www",
      "start": "node ./bin/www"

Lorsque vous exécutez l'application avec npm run dev:serverelle utilisera Nodemon. Nodemon surveille les fichiers de l'application et redémarre automatiquement le service lorsque des fichiers sont modifiés.

Remarque : Les métadonnées des fichiers, telles que les autorisations et la date de modification, sont également prises en compte.

Exécution simultanée du serveur et du client

Au fur et à mesure que nous avançons dans ce guide, vous aurez besoin d'exécuter le client et Express.js simultanément. Il existe une option Concurrently pour cela, qui rend très facile l'apprentissage d'applications séparées l'une sur l'autre.

Installation simultanée

Installation simultanée, également en tant que dépendance de développement.

npm install concurrently --save-dev

Démarrer les deux environnements de développement

Modifiez le fichier package.json pour le serveur, comme indiqué ici. Dans la dernière section, nous avons ajouté un script dev:server qui exécutait le serveur à l'aide de Nodemon. Maintenant, nous ajoutons un script dev:client au niveau racine du projet pour exécuter le client à partir d'ici également.

      "dev:server": "nodemon ./bin/www",
+     "dev:client": "cd client && npm run serve",
      "start": "node ./bin/www"

Maintenant, ajoutez cette ligne pour combiner les deux en utilisant Concurrently. Vous remarquerez l'option --kill-others-on-fail qui signifie que Concurrently arrêtera tous les services si une erreur est détectée. Sans cela, si Node ou Webpack (qui sert le client) rencontrait une erreur, vous devriez redémarrer Concurrently pour que le client et le serveur fonctionnent à nouveau.

      "dev:server": "nodemon ./bin/www",
      "dev:client": "cd client && npm run serve",
+     "dev": "concurrently --kill-others-on-fail 'npm run dev:server' 'npm run dev:client'",
      "start": "node ./bin/www"

Lorsque vous exécutez l'application avec npm run develle démarrera le serveur et le client ensemble à l'adresse suivante localhost:3000 et localhost:8080 respectueusement.

Screenshot of a Express.js and Vue.js running concurrently

Proxy des requêtes API vers le serveur Express.js

Pour effectuer des requêtes dans l'environnement de développement vers le serveur à partir du client, vous devez mettre en place un proxy. Vous pouvez configurer Vue.js de manière à ce que toutes les requêtes commençant par une route particulière soient envoyées par proxy.

Configurer le Proxy

Pour ce faire, créez un nouveau fichier dans le répertoire client nommé vue.config.js. Allez ensuite dans le répertoire du client.

cd client

Créer un fichier de configuration vide.

# touch is a command that will create an empty file touch vue.config.js

Collez le code suivant.

// vue.config.js

module.exports = {
  devServer: {
    proxy: {
      "/api": {
        target: "http://localhost:3000",
        secure: false
      }
    }
  }
};

Ce code indique à Vue.js que lors de l'exécution de devServer que toutes les routes correspondant à /api doivent être redirigées vers http://localhost:3000. Il s'agit de l'URL du serveur lorsque vous exécutez le script dev ou le script dev:server directement.

Créer un service consommateur d'API

Pour effectuer des requêtes de Vue.js vers notre serveur à partir du client, installez Axiosqui est une Promise à utiliser dans le code côté navigateur.

npm install axios

Maintenant qu'Axios est installé et que vous pouvez transmettre des requêtes par proxy entre le serveur et le client, il est temps de faire ces requêtes. Dans le répertoire src/ du client, créez un nouveau répertoire nommé services pour contenir tous les fichiers du service API.

mkdir src/services

Créez un service API abstrait, qui définira le chemin pour les services API suivants. N'oubliez pas que dans l'environnement de développement, /api va servir de proxy vers le serveur.

touch src/services/Api.js

Utilisez le code suivant pour créer un service API abstrait qui renvoie une instance d'Axios.

// src/services/Api.js

import axios from 'axios'

export default() => {
  return axios.create({
    baseURL: `/api`,
    headers: {'Cache-Control': 'no-cache, no-store, no-transform'}
  })
}

Vous avez déjà créé un server/status dans le serveur, auquel vous pouvez accéder lorsque le serveur est en cours d'exécution à partir de localhost:3000/api/server/status.

Pour consommer ce point d'accès à partir de l'application cliente, créez un fichier pour le service.

touch src/services/Server.js

Et ajoutez ce code pour créer une méthode fetchStatus sur le nouveau Server service.

// src/services/Server.js

import Api from '@/services/Api'

export default {
  fetchStatus () {
    return Api().get('server/status')
  }
}

Demander l'état du serveur au client

Maintenant que vous avez créé un service pour effectuer des requêtes au serveur, importez le service dans votre composant App.vue composant.

Ouvrez App.vue et ajoutez les lignes comme indiqué ici.

  <template>
    <div id="app">
      <img alt="Vue logo" src="./assets/logo.png">
-     <HelloWorld msg="Welcome to Your Vue.js App"/>
+     <HelloWorld v-if="!!server.status && server.status === 'ok'" msg="Welcome to Your Vue.js App"/>
+     <template v-else>
+       <HelloWorld msg="Connecting..."/>
+     </template>
    </div>
  </template>

  <script>
  import HelloWorld from './components/HelloWorld.vue'
+ import ServerService from '@/services/Server'

  export default {
    name: 'App',
    components: {
      HelloWorld
+   },
+   data () {
+     return {
+       server: {},
+     }
+   },
+   mounted () {
+     this.getServerStatus()
+   },
+   methods: {
+     getServerStatus () {
+       ServerService.fetchStatus()
+         .then((response) => {
+           this.server = response.data
+         })
+     }
    }
  }
  </script>

# ...

Ici, il réutilise le fichier HelloWorld pour afficher l'état de la demande à l'utilisateur.

Remarque : N'oubliez pas que vous êtes probablement encore dans le répertoire client à ce stade. Si vous démarrez (ou redémarrez) à nouveau l'environnement de développement en utilisant npm run dev doit être exécuté dans le répertoire du serveur (cd .. pour passer du client au serveur).

Une fois qu'il est lancé, vous pouvez accéder au client à l'adresse suivante localhost:8080. Si vous êtes assez rapide, vous pouvez voir le message "Connecting...".

Screenshot of the Vue.js client connecting to the Express.js server

Chargement des écrans avec Tailwind et FontAwesome

Lors de la connexion au serveur dans la dernière section, vous aurez réutilisé le fichier HelloWorld . Maintenant, en utilisant le composant Tailwind CSS et FontAwesome, créez un écran de chargement pour le client.

Si vous souhaitez pratiquer cette méthode en dehors de cette application, j'ai écrit sur le sujet suivant Utiliser Tailwind CSS avec Vue.js dans un guide séparé juste pour vous.

Installer Tailwind CSS

Pour utiliser Tailwind CSS dans le client, nous devons l'installer en tant que dépendance et configurer le client pour qu'il l'utilise.

Note : Cette installation est pour le client et non pour le serveur : Cette installation est pour le client, pas pour le serveur. Assurez-vous donc d'être dans le répertoire client dans le répertoire

npm install tailwindcss

Configurer le client Vue.js pour Tailwind CSS

Lorsque l'application cliente se construit, elle recherche un fichier postcss.config.js qui est un fichier de configuration que Vue.js utilise pour savoir comment traiter le CSS. L'installation de Tailwind CSS indique que vous devez l'ajouter en tant que plugin dans votre chaîne de construction.

L'application de démonstration générée par Vue ne crée pas de fichier postcss.config.js fichier. Faites-le maintenant.

touch postcss.config.js

Et configurez-le à l'aide de ce code.

// postcss.config.js

const autoprefixer = require('autoprefixer');
const tailwindcss = require('tailwindcss');

module.exports = {
  plugins: [
    tailwindcss,
    autoprefixer,
  ],
};

Ajouter Tailwind en tant que ressource CSS

L'application de démonstration ne crée pas non plus d'actifs CSS. Au lieu de cela, elle utilise le CSS à l'intérieur des composants Vue.js, comme le montrent de nombreux guides. Ainsi, pour inclure tailwind, créez un fichier CSS de base dans le répertoire assets à l'aide de ces commandes ou de votre éditeur.

mkdir -p src/assets/styles/ touch src/assets/styles/index.css

Utilisez ce code pour inclure la base CSS Tailwind, les composants et les utilitaires dans votre construction CSS. Copiez-le et collez-le dans votre nouveau fichier index.css dans votre nouveau fichier

/* src/assets/styles/index.css */

@tailwind base;
@tailwind components;
@tailwind utilities;

Inclure le CSS Tailwind

Modifiez maintenant votre fichier main.js pour importer index.css vers le client.

  // src/main.js
  import Vue from 'vue';
  import App from './App.vue';

+ import './assets/styles/index.css';

  Vue.config.productionTip = false;

  new Vue({
    render: h => h(App),
  }).$mount(`#app`);

Screenshot of the Vue.js client styles after Tailwind CSS preflight enabled

Remarque : Tailwind CSS utilise preflight (construit au-dessus de normalize.css) pour réinitialiser tous les styles des différents navigateurs au même endroit. Vous remarquerez que certains styles par défaut ont été supprimés. Ne vous inquiétez pas, vous remplacerez cela bientôt.

Installer FontAwesome

La création d'un spinner de chargement se fera avec une font awesome notched circle. Installez-le sur le client avec cette commande.

npm install @fortawesome/fontawesome-svg-core \ @fortawesome/free-solid-svg-icons \ @fortawesome/vue-fontawesome \ @fortawesome/free-regular-svg-icons \

Inclure FontAwesome

Modifier main.js et ajoutez ce code.

  // src/main.js
  import Vue from 'vue';
  import App from './App.vue';
+ import { library } from '@fortawesome/fontawesome-svg-core'
+ import { fas } from '@fortawesome/free-solid-svg-icons'
+ import { far } from '@fortawesome/free-regular-svg-icons'
+ import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'

  import './assets/styles/index.css';

+ library.add(fas, far)

+ Vue.component('font-awesome-icon', FontAwesomeIcon)
+ Vue.component('font-awesome-layers', FontAwesomeLayers)

  Vue.config.productionTip = false;

  new Vue({
    render: h => h(App),
  }).$mount(`#app`);

Créer l'écran de chargement

Pour créer un nouveau composant Vue.js à utiliser comme écran de chargement, ajoutez un nouveau fichier de composant à l'aide de cette commande ou de votre éditeur.

touch source/components/Loading.vue

Maintenant, à l'aide de ce code, ajoutez le spinner à une superposition translucide en plein écran.

<template>
  <div class="w-screen h-screen fixed block top-0 left-0 bg-white opacity-75 z-50 flex">
    <span class="text-green-500 opacity-75 top-1/2 m-auto text-center">
      <font-awesome-icon icon="circle-notch" class="fa-spin fa-5x mb-2"/>
      <p class="text-base">
        {{ message }}
      </p>
    </span>
  </div>
</template>

<script>
export default {
  name: 'Loading',
  props: {
    message: String
  }
}
</script>

Et, ajoutez l'écran de chargement en éditant App.vue et en remplaçant la réutilisation de HelloWorld.vue par le nouveau composant.

  <template>
    <div id="app">
      <img alt="Vue logo" src="./assets/logo.png">
      <HelloWorld v-if="!!server.status && server.status === 'ok'" msg="Welcome to Your Vue.js App"/>
      <template v-else>
-       <HelloWorld msg="Connecting..."/>
+       <Loading message="Connecting..." />
      </template>
    </div>
  </template>

  <script>
  import HelloWorld from './components/HelloWorld.vue'
+ import Loading from '@/components/Loading.vue'
  import ServerService from '@/services/Server'

  export default {
    name: 'App',
    components: {
-     HelloWorld
+     HelloWorld,
+     Loading
    },
    data () {
      return {
        server: {},
      }
    },
    mounted () {
      this.getServerStatus()
    },
    methods: {
      getServerStatus () {
        ServerService.fetchStatus()
          .then((response) => {
            this.server = response.data
          })
      }
    }
  }
  </script>

  ...

Screenshot of the Vue.js client loading screen with spinner

Remarque : Pour tester ceci, vous pouvez modifier la réponse status dans le répertoire controllers/server.js pour qu'elle renvoie autre chose que ok.

Gérer les erreurs du serveur dans le client

Il est temps d'ajouter la gestion des erreurs au client.

Attraper les erreurs de requête

Modifier App.vue et ajoutez le code suivant.

...

  <script>
  import HelloWorld from './components/HelloWorld.vue'
  import Loading from '@/components/Loading.vue'
  import ServerService from '@/services/Server'

  export default {
    name: 'App',
    components: {
      HelloWorld,
      Loading
    },
    data () {
      return {
        server: {},
+       error: null
      }
    },
    mounted () {
      this.getServerStatus()
    },
    methods: {
      getServerStatus () {
        ServerService.fetchStatus()
          .then((response) => {
            this.server = response.data
          })
+         .catch((err) => {
+           this.error = { title: 'Couldn\'t connect to Server', message: 'There may be a problem with your connection. Please check and try again.', reason: err.reason }
+         })
      }
    }
  }
  </script>

  ...

Désormais, si une erreur est renvoyée par le serveur, elle sera détectée par le client et ajoutée aux données du composant.

Créer un composant d'erreur

Pour afficher une erreur, créez un composant Error.vue à l'aide de cette commande ou de votre éditeur.

touch source/components/Error.vue

Ajoutez ce code, qui utilise également les icônes FontAwesome (et les calques) pour produire un graphique approprié.

<template>
  <div class="flex h-screen">
    <div class="m-auto text-center w-2/3">
      <font-awesome-layers class="fa-10x mb-10">
        <font-awesome-icon icon="globe-americas" transform="grow-4" class="text-gray-500"/>
        <font-awesome-icon :icon="['far', 'circle']" transform="grow-5" class="outline text-white"/>
        <font-awesome-icon icon="times" class="cross text-red-500" transform="shrink-8 right-5 up-5"/>
      </font-awesome-layers>
      <h1 class="text-3xl mb-3 text-gray-800">{{ error.title }}</h1>
      <p class="text-base text-gray-800">{{ error.message }}</p>
      <p class="invisible">{{ error.reason }}</p>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Error',
  props: {
    error: Object
  }
}
</script>

<style scoped>
.outline path {
  stroke: white;
  stroke-width: 20px;
}
.cross path {
  stroke: white;
  stroke-width: 20px;
}
</style>

Afficher une erreur du serveur dans le client

Une fois de plus, l'édition App.vueajoutez le code comme indiqué ici. Supprimez l'image en même temps.

  <template>
    <div id="app">
-     <img alt="Vue logo" src="./assets/logo.png">
      <HelloWorld v-if="!!server.status && server.status === 'ok'" msg="Welcome to Your Vue.js App"/>
      <template v-else>
-       <Loading message="Connecting..." />
+       <Loading v-if="!error" message="Connecting..." />
+       <Error v-else :error="error" />
      </template>
    </div>
  </template>

  <script>
  import HelloWorld from './components/HelloWorld.vue'
+ import Error from '@/components/Error.vue'
  import Loading from '@/components/Loading.vue'
  import ServerService from '@/services/Server'

  export default {
    name: 'App',
    components: {
      HelloWorld,
+     Error,
      Loading
    },
    data () {
      return {
        server: {},
        error: null
      }
    },
    mounted () {
      this.getServerStatus()
    },
    methods: {
      getServerStatus () {
        ServerService.fetchStatus()
          .then((response) => {
            this.server = response.data
          })
          .catch((err) => {
            this.error = { title: 'Couldn\'t connect to Server', message: 'There may be a problem with your connection. Please check and try again.', reason: err.reason }
          })
      }
    }
  }
  </script>

  ...

Maintenant, le client affiche les erreurs envoyées par le serveur.

Screenshot of the Vue.js client catching a server error

Note : Pour voir si cela fonctionne, vous pouvez modifier l'application de votre serveur : Pour que cela fonctionne, vous pouvez modifier l'application de votre serveur controllers/server.jsen remplaçant res.json par res.sendStatus(500) pour fournir au client un code d'erreur 500.

Utiliser les fichiers d'environnement Dotenv

Il n'est pas souhaitable de coder en dur les clés et les informations d'identification dans votre serveur, et encore moins dans votre client.

Installer Dotenv

Installer dotenv afin que vous puissiez définir des variables d'environnement et les lire dans votre application.

Remarque : Il se peut que vous vous trouviez dans le répertoire du client à ce stade. Utilisez cd .. pour passer du client au serveur.

npm install dotenv

Créer un fichier d'environnement

Créez un fichier d'environnement vide pour le serveur à l'aide de cette commande ou de votre éditeur.

touch .env

Configurer l'environnement

Maintenant, éditez .env et ajoutez cet exemple de configuration au fichier. Le jeton et l'identifiant ne sont pas réels.

# server config PORT=3000 # user config VONAGE_USER=username VONAGE_USER_TOKEN=eyJhbGciOiJ.SUzI1NiIsInR.5cCI6IkpXVCJ9 # app config VONAGE_DEFAULT_CONVERSATION_ID=CON-1255bc-1c-4db7-bc48-15a46

Note : Les fichiers sont ignorés par git en raison des fichiers générés : .env Les fichiers de la catégorie " Autres " sont ignorés par git en raison du fait que le fichier de la catégorie " Autres " généré .gitignore généré ajoute .env par défaut. Transmettre votre fichier .env est à peu près aussi sûr que de coder en dur vos informations d'identification. Cela souligne également que ces informations d'identification sont pour cet environnement, l'exécutant localement. Si vous deviez déployer ceci, attendez-vous à gérer l'environnement sur le serveur d'une manière différente. Heroku, par exemple, vous fournit un panneau de contrôle pour configurer l'environnement.

Charger l'environnement

Modifiez maintenant le fichier top du serveur pour inclure l'environnement au démarrage de l'application. Modifiez bin/www (il n'y a pas d'extension de fichier) comme indiqué ici.

#!/usr/bin/env node

+ require('dotenv').config();

  /**
  * Module dependencies.
  */

  ...

Transmettre les valeurs de l'environnement du serveur au client

La première variable d'environnement à partager avec le client est VONAGE_DEFAULT_CONVERSATION_IDqui est l'identifiant par défaut de la "salle" pour le chat ! Vous reviendrez plus tard pour modifier la valeur de cette variable d'environnement.

Modifier controllers/server.js et ajoutez le code indiqué ici.

// controllers/server.js
  exports.status = function(req, res, next) {
    res.json({
+     defaultConversationId: process.env.VONAGE_DEFAULT_CONVERSATION_ID,
      status: 'ok'
    });
  };

Points d'accès utilisateur pour l'authentification du client

Dans les parties suivantes de cette série, un fournisseur d'identité gérera les données de l'utilisateur envoyées par le serveur. En attendant, simulez également ces informations et revenez les modifier lorsque vous les aurez.

Créer un point de terminaison utilisateur

Créez un point de terminaison utilisateur en créant d'abord un user.js à l'aide de votre éditeur ou de cette commande.

touch controllers/user.js

En lui donnant ce code.

// controllers/user.js
exports.session = function(req, res, next) {
  res.json({
    user: process.env.VONAGE_USER,
    token: process.env.VONAGE_USER_TOKEN
  });
};

Maintenant, créez une route pour accéder aux points d'extrémité du contrôleur d'utilisateur à l'aide de votre éditeur ou de cette commande.

touch routes/user.js

Et donnez-lui ce code.

// routes/user.js
const express = require('express');
const router = express.Router();

const userController = require('../controllers/user');

router.get('/session', userController.session);

module.exports = router;

Enfin, modifiez votre fichier app.js et ajoutez la nouvelle route comme indiqué ici.

// app.js
  var express = require('express');
  var path = require('path');
  var cookieParser = require('cookie-parser');
  var logger = require('morgan');

  var app = express();

  app.use(logger('dev'));
  app.use(express.json());
  app.use(express.urlencoded({ extended: false }));
  app.use(cookieParser());
  app.use(express.static(path.join(__dirname, 'public')));

+ app.use('/api/user', require('./routes/user'));
  app.use('/api/server', require('./routes/server'));

  module.exports = app;

Relancez l'application avec npm start; vous pouvez alors accéder à la nouvelle route à l'adresse localhost:3000/api/user/session.

Screenshot of a user session API endpoint

Se connecter à la Conversation API de Vonage

Dans cette section, les étapes suivantes sont les étapes habituelles si vous avez déjà lu l'un de mes tutoriels côté client. Si ce n'est pas le cas, il s'agit de commandes simples pour créer une conversation Vonage à laquelle les utilisateurs peuvent se joindre.

S'installer avec notre CLI

Pour vous connecter à l'API des conversations en tant qu'utilisateur, vous devez d'abord créer une application, une conversation et un utilisateur.

Créer une application

Créez une application dotée de capacités de communication en temps réel (RTC). L'URL d'événement reçoit un journal en direct des événements qui se produisent sur le service, comme les utilisateurs qui rejoignent ou quittent le service, ou qui envoient des messages. Il s'agit d'un exemple d'URL pour le moment, mais vous serez en mesure de capturer et de réagir aux événements dans les parties suivantes de notre série.

nexmo app:create "Vue.js Slack Chat" --capabilities=rtc --rtc-event-url=http://example.com --keyfile=private.key # Application created: 4556dbae-bf...f6e33350d8 # Credentials written to .nexmo-app # Private Key saved to: private.key

Créer une conversation

Deuxièmement, créez une conversation, qui agit comme un salon de discussion. Ou un conteneur pour les messages et les événements.

nexmo conversation:create display_name="general" # Conversation created: CON-a57b0...11e57f56d

Créez votre utilisateur

Créez maintenant un utilisateur pour vous-même.

Remarque : Dans cette démonstration, vous ne discuterez pas entre deux utilisateurs. D'autres guides vous montrent comment créer des conversations entre plusieurs utilisateurs. Ce guide se concentre sur la mise en forme de l'interface utilisateur de votre message d'une manière simple mais attrayante.

nexmo user:create name=USER_NAME display_name=DISPLAY_NAME # User created: USR-6eaa4...e36b8a47f

Ajouter l'utilisateur à une conversation

Ensuite, ajoutez votre nouvel utilisateur à la conversation. Un utilisateur peut être membre d'une application, mais il doit tout de même rejoindre la conversation.

nexmo member:add CONVERSATION_ID action=join channel='{"type":"app"}' user_id=USER_ID # Member added: MEM-df772...1ad7fa06

Générer un jeton d'utilisateur

Enfin, générez un jeton pour votre nouvel utilisateur. Ce jeton représente l'utilisateur lorsqu'il accède à l'application. Ce jeton d'accès l'identifie, de sorte que toute personne l'utilisant sera supposée être le bon utilisateur.

En pratique, vous configurerez l'application avec ce jeton. En production, ces jetons doivent être gardés secrets et exposés avec précaution à l'application cliente, si tant est qu'ils le soient.

Le jeton n'est utilisable que pendant 24 heures. Après cette période, vous devrez réexécuter cette commande nexmo jwt:generate pour accorder à nouveau l'accès à votre utilisateur client.

nexmo jwt:generate ./private.key sub=USER_NAME exp=$(($(date +%s)+86400)) acl='{ "paths": { "/*/users/**": {}, "/*/conversations/**": {}, "/*/sessions/**": {}, "/*/devices/**": {}, "/*/image/**": {}, "/*/media/**": {}, "/*/push/**": {}, "/*/knocking/**": {} } }' application_id=APPLICATION_ID # eyJhbGciOi...XVCJ9.eyJpYXQiOjE1NzM5M...In0.qn7J6...efWBpemaCDC7HtqA

Stocker les informations d'identification dans l'environnement

Maintenant, modifiez .env et ajoutez les informations d'identification que vous avez générées.

# server config PORT=3000 # user config VONAGE_USER=username # USER_NAME from the above commands VONAGE_USER_TOKEN=eyJhbGciOi...XVCJ9.eyJpYXQiOjE1NzM5M...In0.qn7J6...efWBpemaCDC7HtqA # as generated from `nexmo jwt:generate` # app config VONAGE_DEFAULT_CONVERSATION_ID=CON-a57b0...11e57f56d # as generated from `nexmo conversation:create`

Créer un service pour la session utilisateur

Créer un service User.js pour consommer le point de terminaison de la session utilisateur à partir de l'application cliente.

# back in the client directory cd client

Créez le fichier à l'aide de cette commande ou de votre éditeur.

touch src/services/User.js

Et ajoutez ce code pour créer une méthode fetchSession sur le nouveau User service.

// src/services/User.js

import Api from '@/services/Api'

export default {
  fetchSession () {
    return Api().get('user/session')
  }
}

Connecter le client à la Conversion API

Pour connecter le client à l'API Conversation, vous devez installer la dernière version du fichier nexmo-client.

npm install nexmo-client

Créez un nouveau composant Vonage.vue à l'aide de votre éditeur ou de la commande ci-dessous, qui sera chargé de se connecter à l'API de Conversation à l'aide de la bibliothèque nexmo-client bibliothèque.

touch src/components/Vonage.vue

Comme pour le composant App.vue le composant Vonage.vue demande au serveur des informations sur la session de l'utilisateur, à l'aide des boutons Loading.vue et Error.vue de la même manière.

<template>
  <div>
    <HelloWorld v-if="!!app && !!conversation" msg="Welcome to Your Vue.js App"/>
    <template v-else>
      <Loading v-if="!error" message="Logging you in..." />
      <Error v-else :error="error" />
    </template>
  </div>
</template>

<script>
import HelloWorld from '@/components/HelloWorld.vue'
import Loading from '@/components/Loading.vue'
import Error from '@/components/Error.vue'
import UserService from '@/services/User'
import Client from 'nexmo-client'

export default {
  name: 'Vonage',
  props: {
    server: Object
  },
  components: {
    ChatWindow,
    Error,
    Loading
  },
  data () {
    return {
      app: null,
      conversation: null,
      error: null
    }
  },
  mounted () {
    this.fetchSession()
  },
  methods: {
    _errorHandler (err) {
      this.error = { title: 'Chat Service Error', message: err.reason }
    },
    fetchSession () {
      UserService.fetchSession()
        .then((response) => {
          const { token } = response.data

          new Client()
            .createSession(token)
            .then(app => {
              this.app = app

              return app.getConversation(this.$props.server.defaultConversationId)
            })
            .then((conversation) => {
              this.conversation = conversation
            })
            .catch(this._errorHandler)
        })
        .catch(this._errorHandler)
    }
  }
}
</script>

Remplacez maintenant l'utilisation du HelloWorld.vue par le nouveau composant Vonage.vue à l'intérieur de App.vue en effectuant ces changements.

  <template>
    <div id="app">
-     <HelloWorld v-if="!!server.status && server.status === 'ok'" msg="Welcome to Your Vue.js App"/>
+     <Vonage v-if="!!server.status && server.status === 'ok'" :server="server" />
      <template v-else>
        <Loading v-if="!error" message="Connecting..." />
        <Error v-else :error="error" />
      </template>
    </div>
  </template>

  <script>
- import HelloWorld from './components/HelloWorld.vue'
+ import Vonage from '@/components/Vonage.vue'
  import Error from '@/components/Error.vue'
  import Loading from '@/components/Loading.vue'
  import ServerService from '@/services/Server'

  export default {
    name: 'App',
    components: {
-     HelloWorld,
+     Vonage,
      Error,
      Loading
    },
    data () {
      return {
        server: {},
        error: null
      }
    },
    mounted () {
      this.getServerStatus()
    },
    methods: {
      getServerStatus () {
        ServerService.fetchStatus()
          .then((response) => {
            this.server = response.data
          })
          .catch((err) => {
            this.error = { title: 'Couldn\'t connect to Server', message: 'There may be a problem with your connection. Please check and try again.', reason: err.reason }
          })
      }
    }
  }
  </script>

Après l'écran de chargement "Connecting...", vous verrez un écran de chargement "Logging you in..." avant de charger le composant HelloWorld.vue composant.

Screenshot of client logging into the Conversation API

Note : Vous n'atteindrez Hello World que si votre application s'est connectée avec succès au serveur : Vous n'atteindrez le Hello World que si votre application s'est connectée avec succès au serveur, a obtenu un statut "OK", a demandé la session de l'utilisateur et a ensuite utilisé le jeton de l'utilisateur pour se connecter à l'API Conversation à l'aide de la bibliothèque nexmo-client bibliothèque.

Créer les composants du chat

Maintenant que vous êtes connecté à la Conversation API, vous pouvez commencer à créer votre interface utilisateur de messagerie. Commencez par la structure de base de votre application, la fenêtre de conversation.

Fenêtre de chat

Pour ce faire, créez les composants ChatWindow.vue, ChatWindowHeader.vue, ChatWindowEvents.vue, et ChatWindowFooter.vue à l'aide de la commande ou de votre éditeur.

touch src/components/{ChatWindow,ChatWindowHeader,ChatWindowEvents,ChatWindowFooter}.vue

L'édition ChatWindow.vue, donnez-lui le code suivant.

<template>
  <div class="flex flex-col min-h-screen max-h-screen bg-white overflow-hidden">
    <ChatWindowHeader :channelName="'#' + conversation.display_name"/>
    <ChatWindowEvents :conversation="conversation" :user="user" :members="members" />
    <ChatWindowFooter :conversation="conversation" />
  </div>
</template>

<script>
import ChatWindowHeader from '@/components/ChatWindowHeader.vue'
import ChatWindowEvents from '@/components/ChatWindowEvents.vue'
import ChatWindowFooter from '@/components/ChatWindowFooter.vue'

export default {
  name: 'ChatWindow',
  props: {
    app: Object,
    conversation: Object
  },
  components: {
    ChatWindowHeader,
    ChatWindowEvents,
    ChatWindowFooter
  },
  data () {
    return {
      user: {},
      members: new Map(),
    }
  },
  mounted () {
    this.user = this.$props.app.me
    this.fetchMembers()
  },
  methods: {
    fetchMembers () {
      this.members = this.$props.conversation.members
    }
  }
}
</script>

Le composant ChatWindow.vue est responsable de la structuration de la présentation du chat. L'en-tête se trouve en haut, les messages au milieu et le pied de page en bas. Il transmet le nom du canal, préfixé par un hash, en tant que prop channelName à l'en-tête. Il transmet également la conversation, l'utilisateur et les membres au composant d'événements. Enfin, il transmet la conversation au pied de page.

Ensuite, éditez ChatWindowHeader.vue et lui donner ce code.

<template>
  <div class="border-b flex px-6 py-2 items-center">
    <div class="flex flex-col">
      <h4 class="text-grey-darkest mb-1 font-extrabold">{{ channelName }}</h4>
    </div>
  </div>
</template>

<script>
export default {
  name: 'ChatWindowHeader',
  props: {
    channelName: String,
    members: Number
  }
}
</script>

Le composant ChatWindowHeader.vue se contente pour l'instant d'afficher le nom du canal.

Maintenant, éditez ChatWindowEvents.vue et donnez-lui ce code.

<template>
  <div class="py-4 flex-auto overflow-y-auto" ref="chatWindow">
    <template v-if="!!events.length">
      <div class="px-6 hover:bg-gray-100" v-for="event in events" v-bind:key="'event' + event.id">
        <div v-if="event.type === 'text'">
          <strong>{{ members.get(event.from).display_name }}</strong> on <strong>{{ event.timestamp.split("T")[0] }}</strong> at <strong>{{ event.timestamp.split("T")[1].split(".")[0] }}</strong> says {{ event.body.text }}
        </div>
        <div v-else-if="event.type === 'member:joined'">
          <strong>{{ event.body.user.display_name }}</strong> has joined <strong>#{{ event.conversation.display_name }}</strong>.
        </div>
      </div>
    </template>
    <Loading v-else message="Loading messages..." />
    <Error v-else :error="error" />
  </div>
</template>

<script>
import Loading from '@/components/Loading.vue'
import Error from '@/components/Error.vue'

export default {
  name: 'ChatWindowEvents',
  components: {
    Loading,
    Error
  },
  props: {
    user: Object,
    conversation: Object,
    members: Map,
  },
  data () {
    return {
      events: [],
      error: null
    }
  },
  mounted () {
    this.getEventHistory()
    this.registerListeners()
  },
  methods: {
    registerListeners () {
      const { conversation } = this.$props

      conversation.on('text', (user, event) => {
        this.events.push(event)
      })

      conversation.on("member:joined", (user, event) => {
        this.events.push(event)
      })
    },
    getEventHistory () {
      this.$props.conversation
        .getEvents({ page_size: 40, order: 'desc' })
        .then(eventsPage => {
          eventsPage.items.forEach(event => {
            this.events.unshift(event)
          })
        })
        .catch(err => {
          this.error = { title: 'Chat Service Error', message: err.message }
        })
    },
  },
}
</script>

Le composant ChatWindowEvents.vue est chargé de dresser la liste de tous les événements de la conversation. Il le fait de haut en bas, les événements les plus anciens se trouvant en haut de la fenêtre. Faites défiler la fenêtre vers le bas pour voir les messages les plus récents. Il charge un total de 40 messages. Plus loin dans cette série, vous verrez comment charger des messages plus anciens.

Enfin, éditez ChatWindowFooter.vue et lui donner ce code.

<template>
  <div class="px-4">
    <textarea
      v-bind:class="{ 
        'disabled:opacity-75': isSending,
        'bg-gray-300': isSending,
        'border-gray-400': isSending,
        'border-gray-400': !isSending
      }"
      v-bind:disabled="isSending"
      v-bind:value="inputMessage"
      v-on:input="inputMessage = $event.target.value"
      v-on:keydown.enter.exact.prevent
      v-on:keyup.enter.exact="sendMessage"
      v-on:keyup="typingEvents"
      type="text"
      :placeholder="'Message ' + conversation.display_name"
      class="w-full rounded border text-sm border-gray-700 overflow-hidden py-2 px-4 resize-none"
      rows="1"
      ref="inputBox"
    >
    </textarea>
    <div class="grid grid-cols-10 h-6 text-xs">
    </div>
  </div>
</template>

<script>
export default {
  name: 'ChatWindowFooter',
  props: {
    conversation: Object,
  },
  data () {
    return {
      inputMessage: '',
      isSending: false
    }
  },
  methods: {
    typingEvents () {
      this.resizeInput()
    },
    resizeInput () {
      const inputRows = this.inputMessage.split(/\r?\n/).length
      this.$refs.inputBox.rows = inputRows
    },
    sendMessage () {
      if (this.inputMessage.replace(/\s/g,'').length > 0) {
        this.isSending = true

        this.$props.conversation
          .sendText(this.inputMessage.trim())
          .then(() => {
            this.isSending = false
            this.$nextTick(() => {
              this.$refs.inputBox.focus()
              this.inputMessage = ''
              this.resizeInput()
            });
          })
          .catch(err => {
            console.error(err) // eslint-disable-line no-console
          })
      }
    }
  }
}
</script>

<style scoped>
textarea:focus{
  outline: none;
}
</style>

Une fois vos composants créés, modifiez Vonage.vue et remplacez HelloWorld.vue par votre nouveau ChatWindow.vue composant.

  <template>
    <div>
-     <HelloWorld v-if="!!app && !!conversation" msg="Welcome to Your Vue.js App" />
+     <ChatWindow v-if="!!app && !!conversation" :app="app" :conversation="conversation" />
      <template v-else>
        <Loading v-if="!error" message="Logging you in..." />
        <Error v-else :error="error" />
      </template>
    </div>
  </template>

  <script>
- import HelloWorld from '@/components/HelloWorld.vue'
+ import ChatWindow from '@/components/ChatWindow.vue'
  import Loading from '@/components/Loading.vue'
  import Error from '@/components/Error.vue'
  import UserService from '@/services/User'
  import VonageClient from 'nexmo-client'

  export default {
    name: 'Vonage',
    props: {
      server: Object
    },
    components: {
-     HelloWorld,
+     ChatWindow,
      Error,
      Loading
    },
    data () {
      return {
        app: null,
        conversation: null,
        error: null
      }
    },
    mounted () {
      this.fetchSession()
    },
    methods: {
      ...
    }
  }
  </script>

Il y a beaucoup de choses à copier et à coller ici. Une fois en marche, voyez à quoi cela ressemble.

Screenshot of the chat client working

Remarquez la marge, laissée par l'application de démonstration ! Enfin, supprimez ce style en éditant src/App.vue comme suit.

  <template>
    <div id="app">
      <Vonage v-if="!!server.status && server.status === 'ok'" :server="server" />
      <template v-else>
        <Loading v-if="!error" message="Connecting..." />
        <Error v-else :error="error" />
      </template>
    </div>
  </template>

  <script>
  ...
  </script>
-
- <style>
- #app {
-   font-family: Avenir, Helvetica, Arial, sans-serif;
-   -webkit-font-smoothing: antialiased;
-   -moz-osx-font-smoothing: grayscale;
-   text-align: center;
-   color: #2c3e50;
-   margin-top: 60px;
- }
- </style>

Pendant que vous y êtes, supprimez HelloWorld.vue. Enfin.

rm src/components/HelloWorld.vue

Screenshot of the chat client working beautifully

Chat de travail atteint !

Partie 1, terminée ! Vous avez construit un client de chat qui commence à ressembler à Slack. Voici une liste de ce que vous avez fait jusqu'à présent :

  • Création d'une application Express.js à utiliser comme API

  • Création d'une application Vue.js à utiliser comme client

  • Création de points d'extrémité d'API dans Express.js

  • Points d'extrémité d'API consommés dans Vue.js

  • Ajout du rechargement à chaud des fichiers Express.js

  • Ajout simultané à Express.js et Vue.js avec une seule commande

  • Requêtes API proxy de Vue.js vers Express.js

  • Styliser Vue.js avec Tailwind CSS

  • Icônes animées avec FontAwesome

  • Création d'un composant de chargement plein écran

  • Connecté à l'API Conversation de Vonage

  • Création d'une interface utilisateur de messagerie

Si vous êtes intéressé par l'application de démonstration complète, veuillez consulter le repo GitHub pour mon clone Vue js Slack. GitHub pour mon clone Vue.js Slack jusqu'à présent.

Restez à l'écoute pour la deuxième partie, dans laquelle nous aborderons les incontournables de l'expérience utilisateur.

  • Historique à défilement infini

  • Positions de défilement collantes lors du défilement de l'historique

  • Ping à la fin de l'envoi des messages

  • Notifications de messages non lus

  • Bouton "marquer comme lu

  • Nombre de membres du canal

  • Suppression des messages

  • Notification des événements de frappe de l'utilisateur (plusieurs personnes sont en train de taper)

  • Messages multilignes

  • Style Slack Markdown

À la fin de la deuxième partie, vous aurez obtenu quelque chose qui ressemble à ceci !

Screenshot of the sneak peek of chat from Part 2

Pour en savoir plus

Voici d'autres articles qui pourraient vous être utiles dans votre démarche de création d'une application de chat en ligne.

Et n'oubliez pas, si vous avez des questions, des conseils ou des idées que vous souhaitez partager avec la communauté, n'hésitez pas à vous rendre sur notre espace de travail Slack de la communauté 👇

Partager:

https://a.storyblok.com/f/270183/250x250/451101b4f0/lukeoliff.png
Luke OliffAnciens de Vonage

Éducateur technique sympathique, père de famille, défenseur de la diversité, il discute probablement un peu trop. Anciennement ingénieur backend. Parlez-moi de JavaScript (frontend ou backend), de l'incroyable Vue.js, de DevOps, de DevSecOps, de tout ce qui concerne JamStack. Rédacteur sur DEV.to