Fermer

mai 29, 2018

Créer une application de chat en temps réel avec Sails.js –


Si vous êtes un développeur qui utilise actuellement des frameworks tels que Django, Laravel ou Rails, vous avez probablement entendu parler de Node.js. Vous utilisez peut-être déjà une bibliothèque frontale populaire telle que Angular ou React dans vos projets. Maintenant, vous devriez penser à faire un basculement complet vers une technologie de serveur basée sur Node.js.

Cependant, la grande question est de savoir par où commencer. Aujourd'hui, le monde JavaScript s'est développé à un rythme incroyablement rapide au cours des dernières années, et il semble être en expansion constante.

Si vous avez peur de perdre votre expérience de programmation durement acquise dans l'univers Node, ne craignez rien, comme nous l'avons Sails.js .

Sails.js est un framework MVC en temps réel conçu pour aider les développeurs à créer des applications Node.js prêtes pour la production, de qualité professionnelle, en peu de temps. Sails.js est une solution JavaScript pure qui prend en charge plusieurs bases de données (simultanément) et plusieurs technologies frontales. Si vous êtes un développeur Rails, vous serez heureux d'apprendre que Mike McNeil le fondateur de Sails.js, a été inspiré par Rails. Vous trouverez beaucoup de similitudes entre les projets Rails et Sails.js.

Dans cet article, je vais vous apprendre les principes fondamentaux de Sails.js, en vous montrant comment créer une application de chat simple et conviviale. Le code source complet pour le projet sails-chat peut être trouvé dans ce GitHub repo .

 Sails Chat App

Prérequis

Avant de commencer, vous devez au moins avoir expérience de développement d'applications utilisant l'architecture MVC. Ce tutoriel est destiné aux développeurs intermédiaires. Vous aurez également besoin au moins d'avoir une base de base dans ceux-ci:

Pour le rendre pratique et juste pour tout le monde, ce tutoriel utilisera des bibliothèques de base installées par défaut dans un nouveau projet Sails.js. L'intégration avec les bibliothèques frontales modernes telles que React, Vue ou Angular ne sera pas abordée ici. Cependant, je vous recommande fortement de les examiner après cet article. De plus, nous ne ferons pas d'intégrations de bases de données. Nous utiliserons à la place la base de données par défaut, basée sur le disque local, pour le développement et les tests

Project Plan

Le but de ce tutoriel est de vous montrer comment créer une application de chat similaire à Slack Gitter ou Discord .

Pas vraiment! Beaucoup de temps et de sueur sont allés dans la construction de ces merveilleuses plateformes. Le nombre actuel de fonctionnalités développées est assez important.

Nous allons plutôt construire une version de produit minimale viable d'une application de chat qui consiste en:

  • salle de chat unique
  • authentification de base (sans mot de passe) [19659015]

J'ai ajouté la fonction de profil en bonus afin de couvrir un peu plus les fonctionnalités de Sails.js.

Installation de Sails.js

Avant de commencer En installant Sails.js, nous devons d'abord configurer un environnement Node.js approprié. Au moment de la rédaction, la dernière version stable actuellement disponible est v0.12.14. Sails.js v1.0.0 est également disponible mais est actuellement en version bêta, non recommandé pour une utilisation en production.

La dernière version stable de Node, j'ai accès à est v8.9.4. Malheureusement, Sails.js v0.12 ne fonctionne pas correctement avec le dernier LTS. Cependant, j'ai testé avec Node v.7.10 et j'ai trouvé que tout fonctionnait correctement. C'est toujours bien car nous pouvons utiliser une nouvelle syntaxe ES8 dans notre code.

En tant que développeur JavaScript, vous réaliserez que travailler avec une version de Node.js n'est pas suffisant. Par conséquent, je recommande d'utiliser l'outil nvm pour gérer facilement plusieurs versions de Node.js et de NPM. Si vous ne l'avez pas encore fait, purgez simplement votre installation Node.js existante, puis installez nvm pour vous aider à gérer plusieurs versions de Node.js.

Voici les instructions de base pour installer Node v7 et Sails.js: [19659023] # Installer la dernière version de Node v7 LTS
nvm installer v7

# Rendre le nœud v7 par défaut
nvm alias par défaut v7

# Installer Sails.js Global
npm installer -g voiles

Si vous avez une bonne connexion Internet, cela ne devrait prendre que quelques minutes ou moins. Allons-y maintenant et créons notre nouvelle application à l'aide de la commande du générateur Sails:

 # Accédez au dossier de vos projets
Projets cd

# Générer votre nouvelle application
voiles générer une nouvelle application de chat

# Attendez la fin de l'installation, puis accédez au dossier du projet
cd chat-app

# Démarrer l'application
ascenseur de voiles

L'application devrait prendre quelques secondes pour démarrer. Vous devez ouvrir manuellement l'URL http: // localhost: 1337 dans votre navigateur pour voir votre application web nouvellement créée.

 Sails lift

Voir ceci confirme que nous avoir un projet en cours sans erreurs, et que nous pouvons commencer à travailler. Pour arrêter le projet, appuyez simplement sur contrôle + c sur le terminal. Utilisez votre éditeur de code favori (j'utilise Atom) pour examiner la structure du projet généré. Voici les principaux dossiers que vous devriez connaître:

  • api : contrôleurs, modèles, services et politiques (permissions)
  • actifs : images, polices, JS, CSS , Less, Sass etc.
  • config : configuration de projet par exemple base de données, routes, informations d'identification, paramètres régionaux, sécurité, etc.
  • node_modules : packages npm installés
  • tasks : scripts de configuration Grunt et script pipeline pour compiler et injecter des assets
  • views : voir les pages – par exemple, EJS, Jade ou n'importe quel moteur de template que vous préférez
  • .tmp : dossier temporaire utilisé par Sails pour construire et servir votre projet en mode développement

Avant de continuer, il y a deux choses que nous devons faire:

  • Mettre à jour le paquet EJS . Si vous avez EJS 2.3.4 répertorié dans package.json vous devez le mettre à jour en le modifiant immédiatement en 2.5.5. Il contient une faille de sécurité sérieuse. Après avoir changé le numéro de version, faites une installation de npm pour effectuer la mise à jour.
  • Rechargement à chaud . Je vous suggère d'installer sails-hook-autoreload pour activer le rechargement à chaud pour votre application Sails.js. Ce n'est pas une solution parfaite, mais cela facilitera le développement. Pour l'installer pour cette version actuelle de Sails.js, exécutez la commande suivante:
 npm install sails-hook-autoreload@for-sails-0.12 --save

Installation de dépendances frontales

Pour ce didacticiel, nous allons consacrer le moins de temps possible à la création d'une interface utilisateur. Tout framework CSS avec lequel vous êtes à l'aise fera l'affaire. Pour ce tutoriel, je vais utiliser la bibliothèque CSS Semantic UI CSS

Sails.js n'a pas de guide spécifique sur la façon d'installer des bibliothèques CSS. Il y a trois façons ou plus de s'y prendre. Regardons chacun.

1. Téléchargement manuel

Vous pouvez télécharger vous-même les fichiers CSS et les scripts JS, ainsi que leurs dépendances. Après le téléchargement, placez les fichiers dans le dossier assets

Je préfère ne pas utiliser cette méthode,
car cela demande un effort manuel pour garder les fichiers à jour. J'aime automatiser les tâches.

2. Utiliser Bower

Cette méthode nécessite de créer un fichier appelé .bowerrc à la racine de votre projet. Collez l'extrait suivant:

 {
"répertoire": "assets / vendeur"
}

Cela demandera à Bower d'installer le dossier assets / vendor au lieu du dossier bower_components par défaut. Ensuite, installez Bower globalement, et vos dépendances frontales localement en utilisant Bower:

 # Installez le bower globalement via npm-
npm installer -g bower

# Créer un fichier bower.json, accepter les réponses par défaut (sauf choisir y pour privé)
bower init

# Installer Semantic-UI via bower
bower installer semantic-ui --save

# Installer jsrender
bower installer jsrender --save

Je vais expliquer le but de jsrender plus tard. J'ai pensé qu'il était préférable de terminer la tâche d'installer les dépendances en une fois. Vous devriez prendre note que jQuery a été installé aussi, puisqu'il s'agit d'une dépendance pour semantic-ui .

Après l'installation, mettez à jour assets / style / importer.less pour l'inclure ligne:

 @import '../vendor/semantic/dist/semantic.css';

Ensuite, incluez les dépendances JavaScript dans tasks / pipeline.js :

 var jsFilesToInject = [

// Load Sails.io before everything else
'js/dependencies/sails.io.js',

// Vendor dependencies
'vendor/jquery/dist/jquery.js',
'vendor/semantic/dist/semantic.js',
'vendor/jsrender/jsrender.js',

// Dependencies like jQuery or Angular are brought in here
'js/dependencies/**/*.js',

// All of the rest of your client-side JS files
// will be injected here in no particular order.
'js/**/*.js'
];

Lorsque nous exécutons voiles lift les fichiers JavaScript seront automatiquement injectés dans le fichier views / layout.ejs conformément aux instructions pipeline.js . La configuration actuelle grunt prendra soin d'injecter nos dépendances CSS pour nous.

Important: ajoutez le mot fournisseur dans le fichier .gitignore . Nous ne voulons pas que les dépendances des fournisseurs soient sauvegardées dans notre dépôt.

3. Utilisation de npm + grunt.copy

La troisième méthode nécessite un peu plus d'efforts pour la configuration, mais entraînera une empreinte plus faible. Installez les dépendances en utilisant npm comme suit:

 npm install semantic-ui-css jsrender --save

jQuery sera installé automatiquement, puisqu'il est également listé comme dépendance pour semantic-ui-css . Ensuite, nous devons placer le code dans tasks / config / copy.js . Ce code demandera à Grunt de copier les fichiers JS et CSS requis de node_modules vers le dossier assets / vendor pour nous. Le fichier entier devrait ressembler à ceci:

 module.exports = function (grunt) {

grunt.config.set ('copy', {
  dev: {
    fichiers: [{
      expand: true,
      cwd: './assets',
      src: ['**/*.!(coffee|less)'],
      dest: '.tmp / public'
    },
    // Copier JQuery
    {
      expand: vrai,
      cwd: './node_modules/jquery/dist/',
      src: ['jquery.min.js'],
      Dest: './assets/vendor/jquery'
    },
    // Copier jsrender
    {
      expand: vrai,
      cwd: './node_modules/jsrender/',
      src: ['jsrender.js'],
      dest: './assets/vendor/jsrender'
    },
    // copie les fichiers CSS et JS de sémantique-ui
    {
      expand: vrai,
      cwd: './node_modules/semantic-ui-css/',
      src: ['semantic.css', 'semantic.js'],
      dest: './assets/vendor/semantic-ui'
    },
    // copie les polices d'icônes sémantique-ui
    {
      expand: vrai,
      cwd: './node_modules/semantic-ui-css/themes',
      src: ["*.*", "**/*.*"],
      Dest: './assets/vendor/semantic-ui/themes'
    }]
  },
  construire: {
    fichiers: [{
      expand: true,
      cwd: '.tmp/public',
      src: ['**/*'],
      dest: 'www'
    }]
  }
});

grunt.loadNpmTasks ('grunt-contrib-copy');
}

Ajoutez cette ligne à assets / styles / importer.less :

 @import '../vendor/semantic-ui/semantic.css';

Ajouter les fichiers JS à config / pipeline.js :

 // Dépendances du fournisseur
'vendeur / jquery / jquery.min.js',
'vendor / semantic-ui / semantic.js',
'vendeur / jsrender / jsrender.js',

Enfin, exécutez cette commande pour copier les fichiers à partir de node_modules le dossier assets / vendor . Vous n'avez besoin de le faire qu'une fois pour chaque installation propre de votre projet:

 grunt copy: dev

N'oubliez pas d'ajouter le fournisseur à votre .gitignore .

Installation des dépendances de test

Quelle que soit la méthode que vous avez choisie, vous devez vous assurer que les dépendances requises chargé. Pour ce faire, remplacez le code dans view / homepage.ejs par ce qui suit:

Paramètres du compte   
Gérez les paramètres de votre compte et définissez vos préférences de messagerie.

Après avoir enregistré le fichier, faites un élévateur de voiles . Votre page d'accueil devrait maintenant ressembler à ceci:

 semantic-ui-test

Effectuez toujours une actualisation après le redémarrage de votre application. Si l'icône est manquante ou si la police est désactivée, veuillez lire attentivement les étapes et voir ce que vous avez manqué. Utilisez la console du navigateur pour voir quels fichiers ne se chargent pas. Sinon, passez à l'étape suivante.

Création de vues

En ce qui concerne le développement de projet, j'aime commencer par l'interface utilisateur. Nous allons utiliser le modèle JavaScript incorporé pour créer les vues. C'est un moteur de template installé par défaut dans chaque projet Sails.js. Cependant, vous devez savoir qu'il a des fonctionnalités limitées et n'est plus en cours de développement.

Ouvrez config / bootstrap.js et insérez cette ligne afin de donner un titre correct à nos pages Web. Placez-le juste dans la fonction existante avant l'instruction cb () :

 sails.config.appName = "Sails Chat App";

Vous pouvez jeter un oeil à views / layout.ejs pour voir comment le tag title est défini. Ensuite, nous commençons à construire l'interface utilisateur de notre page d'accueil

Design de la page d'accueil

Ouvrez /views/homepage.ejs et remplacez le code existant par ceci:


S'inscrire ou Se connecter

      
      [TODO : Login Form goes here]     
  
  

Pour comprendre les éléments de l'interface utilisateur utilisés dans le code ci-dessus, veuillez vous reporter à la documentation sur l'interface utilisateur sémantique. J'ai décrit les liens exacts ci-dessous:

Créez un nouveau fichier dans assets / styles / theme.less et collez le contenu suivant:

 .banner a {
couleur: #fff;
}

.centered {
margin-left: auto! important;
margin-right: auto! important;
marge-bas: 30px! important;
}

.section {
marge supérieure: 30px;
}

.menu {
border-radius: 0! important;
}

.Remarque {
taille de police: 11px;
couleur: # 2185D0;
}

# chat-content {
hauteur: 90%;
débordement-y: défile;
}

Ce sont tous les styles personnalisés que nous utiliserons dans notre projet. Le reste du style proviendra de la bibliothèque Semantic UI

Ensuite, mettez à jour assets / styles / importer.less pour inclure le fichier thème que nous venons de créer:

 @import 'theme.less';

Exécuter élévateur de voiles . Votre projet devrait maintenant ressembler à ceci:

 Vue de la page d'accueil

Ensuite, nous verrons comment construire le menu de navigation.

Ce sera créé comme partiel car il sera partagé par plusieurs fichiers de vue. Dans le dossier vues créez un dossier appelé partials . Créez ensuite le fichier views / partials / menu.ejs et collez le code suivant:


Pour comprendre le code ci-dessus, référez-vous simplement à la documentation du

Si vous inspectez le code ci-dessus, vous remarquerez que nous avons créé un lien pour / chat / profile et / auth / logout . Commençons par créer les vues pour profil et chat room

Profil

Créez le fichier view / profile.ejs et collez le code suivant:

<% include partials/menu %>

Profil mis à jour!


  [ TODO put user-form here]

Vous devriez maintenant être familier avec les éléments de l'interface header et grid si vous avez lu la documentation liée. À la racine du document, vous remarquerez que nous avons un élément container . (Pour plus d'informations, reportez-vous à la documentation Conteneur .

Nous allons créer le formulaire utilisateur plus tard, une fois l'API créée, puis nous créerons une mise en page pour la salle de conversation. [19659106] Salle de chat

Le salon de discussion se composera de trois sections:

  • Utilisateurs de la discussion – liste des utilisateurs
  • Messages de la discussion – liste des messages [19659015] Chat post – formulaire pour poster de nouveaux messages.

Créer views / chatroom.ejs et collez le code suivant:

 <% include partials/menu %>

     
    [ TODO chat-users ]   
  
         [ TODO chat-messages ]     
         [ TODO chat-post ]   

Avant de pouvoir afficher les pages, nous devons configurer le routage.

Routing

Ouvrir config / routes.js et le mettre à jour comme ceci:

 '/': {
voir: 'page d'accueil'
},
'/profil': {
Voir le profil'
},
'/ chat': {
vue: 'chatroom'
}

Le routage de Sails.js est assez flexible. Il existe plusieurs façons de définir le routage en fonction du scénario. C'est la version la plus basique où nous mappons une URL à une vue.

Lancez votre application Sails ou rafraîchissez simplement votre page si elle fonctionne toujours en arrière-plan. Actuellement, il n'y a pas de lien entre la page d'accueil et les autres pages. Ceci est intentionnel, car nous construirons plus tard un système d'authentification rudimentaire qui redirigera les utilisateurs connectés vers / chat . Pour l'instant, utilisez la barre d'adresse de votre navigateur et ajoutez / chat ou / profile à l'URL de fin.

 chat-layout

 profile-layout

À ce stade, vous devriez avoir les vues ci-dessus. Allons de l'avant et commençons à créer l'API.

Générer une API utilisateur

Nous allons utiliser l'utilitaire de ligne de commande Sails.js pour générer notre API. Nous devrons arrêter l'application pour cette étape:

 voiles générer api User

En une seconde, nous recevons le message "Créé une nouvelle API!" Fondamentalement, un modèle User.js et un UserController.js viennent d'être créés pour nous. Mettons à jour le api / model / User.js avec quelques attributs de modèle:

 module.exports = {

les attributs: {

  prénom: {
    type: 'chaîne',
    requis: vrai
  },

  email: {
    type: 'chaîne',
    requis: vrai,
    unique: vrai
  },

  avatar: {
    type: 'chaîne',
    requis: vrai,
    par défautTo: 'https://s.gravatar.com/avatar/e28f6f64608c970c663197d7fe1f5a59?s=60'
  },

  emplacement: {
    type: 'chaîne',
    requis: faux,
    defaultsTo: ''
  },

  bio: {
    type: 'chaîne',
    requis: faux,
    defaultsTo: ''
  }
}
}

Je crois que le code ci-dessus est explicite. Par défaut, Sails.js utilise une base de données de disques locale qui est essentiellement un fichier situé dans le dossier .tmp . Afin de tester notre application, nous devons créer des utilisateurs. La façon la plus simple d'y parvenir est d'installer le paquet sails-seed :

 npm install sails-seed --save

Après l'installation, vous constaterez que le fichier config / seeds.js a été créé pour vous. Collez les données de base suivantes:

 module.exports.seeds = {
utilisateur: [
  {
    name: 'John Wayne',
    email: 'johnnie86@gmail.com',
    avatar: 'https://randomuser.me/api/portraits/men/83.jpg',
    location: 'Mombasa',
    bio: 'Spends most of my time at the beach'
  },
  {
    name: 'Peter Quinn',
    email: 'peter.quinn@live.com',
    avatar: 'https://randomuser.me/api/portraits/men/32.jpg',
    location: 'Langley',
    bio: 'Rather not say'
  },
  {
    name: 'Jane Eyre',
    email: 'jane@hotmail.com',
    avatar: 'https://randomuser.me/api/portraits/women/94.jpg',
    location: 'London',
    bio: 'Loves reading motivation books'
  }
]
}

Maintenant que nous avons généré une API, nous devons configurer la politique de migration dans le fichier config / models.js :

 migrate: 'drop'

Sails.js utilise trois stratégies de migration pour déterminer comment reconstruire votre base de données à chaque démarrage:

  • safe – ne migrez pas, je le ferai à la main [19659015] alter – migrer mais essayer de conserver les données existantes
  • drop – supprimer toutes les tables et tout reconstruire

Je préfère utiliser drop pour le développement , car j'ai tendance à beaucoup itérer. Vous pouvez définir modifier si vous souhaitez conserver les données existantes. Néanmoins, notre base de données sera alimentée par les données de graine à chaque fois.

Maintenant, laissez-moi vous montrer quelque chose de cool. Feu le projet Sails et naviguez vers les adresses / utilisateur et / user / 1 .

 users-api

 user- api

Grâce à l'API Blueprints de Sails.js nous avons une API CRUD entièrement fonctionnelle sans que nous n'écrivions une seule ligne de code. Vous pouvez utiliser Postman pour accéder à l'API utilisateur et effectuer des manipulations de données telles que créer, mettre à jour ou supprimer des utilisateurs.

Procédons maintenant à la construction du formulaire profil

Formulaire profil

Ouvrir view / profile. ejs et remplacer la ligne TODO existante par ce code:

 <img class = "ui petite image circulaire centrée" src = "http://www.sitepoint.com/<%= data.avatar %>">
  <form action = "<%= '/user/update/'+ data.id %>" méthode = "post" class = "ui forme centrée">     
      <input type = "text" name = "nom" valeur = "<%= data.name %>">     
    
      <input type = "text" name = "email" value = "<%= data.email %>">     
    
      <input type = "text" name = "location" value = "<%= data.location %>">     
    
<input type = "caché" name = "avatar" value = <%=data.avatar %>>        

Nous utilisons Semantic-UI Form pour construire l'interface de formulaire. Si vous examinez la valeur de l'action du formulaire, / user / update / '+ data.id vous vous rendrez compte que j'utilise une route Blueprint. Cela signifie que lorsqu'un utilisateur clique sur le bouton Update l'action de mise à jour de Blueprint sera exécutée

Cependant, pour charger les données utilisateur, j'ai décidé de définir une action personnalisée dans le User Controller. Mettez à jour le fichier api / controllers / UserController avec le code suivant:

 module.exports = {

rendu: async (requête, réponse) => {
  essayez {
    laissez data = wait User.findOne ({
      email: 'johnnie86@gmail.com'
    });
    if (! data) {
      return response.notFound ('L'utilisateur n'a PAS été trouvé!');
    }
    response.view ('profile', {data});
  } catch (err) {
    response.serverError (err);
  }
}
}

Dans ce code, vous remarquerez que j'utilise la syntaxe async / await pour extraire les données utilisateur de la base de données. L'alternative consiste à utiliser des rappels, qui pour la plupart des développeurs ne sont pas clairement lisibles. J'ai également codé en dur le compte d'utilisateur par défaut à charger temporairement. Plus tard, lorsque nous établirons l'authentification de base, nous la changerons pour charger l'utilisateur actuellement connecté.

Enfin, nous devons changer la route / profile pour commencer à utiliser la nouvelle ] UserController . Ouvrez config / routes et mettez à jour l'itinéraire du profil comme suit:

 ...
'/profil': {
  controller: 'UserController',
  action: 'render'
},
...

Naviguez vers l'URL / profile et vous devriez avoir l'affichage suivant:

 profile-view

Essayez de changer l'un des champs du formulaire et cliquez sur la mise à jour bouton. Vous serez amené à cette vue:

 mise à jour du profil

Vous remarquerez que la mise à jour a fonctionné, mais les données affichées sont au format JSON. Idéalement, nous devrions avoir une page de profil d'affichage seulement dans views / user / findOne.ejs et une page de profil de mise à jour dans /views/user/update.ejs . Le système Blueprint devinera les vues à utiliser pour le rendu des informations. S'il ne trouve pas les vues, il affichera simplement JSON. Pour l'instant, nous allons simplement utiliser cette astuce. Créez le fichier /views/user/update.ejs et collez le code suivant:

  

La prochaine fois que nous ferons une mise à jour, nous serons redirigés vers la page / profile . Maintenant que nous avons des données utilisateur, nous pouvons créer le fichier views / partials / chat-users.js à utiliser dans views / chatroom.ejs . Après avoir créé le fichier, collez ce code:

 

Membres


// Modèle jsrender

Pour cette vue, nous avons besoin d'une approche de rendu côté client pour mettre la page à jour en temps réel. Ici, nous utilisons la bibliothèque jsrender un moteur de template plus puissant que EJS. La beauté de jsrender est qu'il peut soit prendre un tableau ou un seul objet littéral et le modèle sera toujours rendu correctement. Si nous devions le faire dans ejs nous aurions besoin de combiner une déclaration et une pour boucle pour gérer les deux cas.

expliquer le flux de notre code JavaScript côté client:

  1. loadUsers () . Lors du premier chargement de la page, nous utilisons la bibliothèque de socket Sails.js pour effectuer une requête GET pour les utilisateurs. Cette requête sera gérée par l'API Blueprint. Nous transmettons ensuite les données reçues à la fonction renderChatUsers (data)
  2. Toujours dans la fonction loadUsers () nous enregistrons un écouteur en utilisant io.socket.on fonction. Nous écoutons les événements appartenant au modèle utilisateur . Lorsque nous sommes notifiés, nous récupérons les utilisateurs et remplaçons la sortie HTML existante.
  3. renderChatUsers (data) . Ici nous prenons un script avec l'id usersTemplate en utilisant une fonction jQuery templates () . Notez que le type est text / x-jsrender . En spécifiant un type personnalisé, le navigateur ignore et ignore cette section car il ne sait pas ce que c'est. Nous utilisons ensuite la fonction template.render () pour fusionner le modèle avec les données. Ce processus génère une sortie HTML que nous prenons ensuite et l'insère dans le document HTML.

Le modèle que nous avons écrit dans profile.ejs a été rendu sur le serveur Node, puis envoyé au navigateur en HTML . Pour le cas de utilisateurs de chat nous devons effectuer le rendu côté client. Cela permettra aux utilisateurs de chat de voir de nouveaux utilisateurs rejoindre le groupe sans actualiser leur navigateur.

Avant de tester le code, nous devons mettre à jour views / chatroom.ejs pour inclure le nouveau utilisateurs de chat partiel. Remplacez [ TODO chat-users ] par ce code:

 ... html
<% include partials/chat-users.ejs %>
...

Dans le même fichier, ajoutez ce script à la fin:

  

Ce script appelle la fonction loadUsers () . Pour confirmer que cela fonctionne, faisons un élévateur à voiles et naviguez vers l'URL / chat .

 chat-users

Votre avis devrait? comme l'image ci-dessus. Si c'est le cas, passons à la construction de l'API de Chatroom.

API ChatMessage

Comme précédemment, nous utiliserons Sails.js pour générer l'API:

 voiles génère api ChatMessage

Ensuite, remplissez api / models / ChatMessage.js avec les attributs suivants:

 module.exports = {

les attributs: {

  message: {
    type: 'chaîne',
    requis: vrai
  },

  créé par : {
    modèle: 'utilisateur',
    requis: vrai
  }
}
}

Notez que nous avons déclaré une association un-à-un avec le modèle User via l'attribut createdBy . Ensuite, nous devons remplir notre base de données de disques avec quelques messages de discussion. Pour cela, nous allons utiliser config / bootstrap.js . Mettez à jour le code entier comme suit. Nous utilisons la syntaxe async / await pour simplifier notre code et éviter l'appel de l'enfer:

 module.exports.bootstrap = fonction async (cb) {

sails.config.appName = "Sails Chat App";

// Générer des messages de chat
essayez {
  laissez messageCount = ChatMessage.count ();
  if (messageCount> 0) {
    revenir; // ne répète pas les messages
  }

  laisse les utilisateurs = attendre User.find ();
  if (users.length> = 3) {
    console.log ("Génération de messages ...")

    let msg1 = attend ChatMessage.create ({
      message: 'Hey tout le monde! Bienvenue dans la communauté! ',
      createdBy: utilisateurs [1]
    });
    console.log ("Message de chat créé:" + msg1.id);

    let msg2 = attend ChatMessage.create ({
      message: "Comment ça va?",
      createdBy: users [2]
    });
    console.log ("Message de chat créé:" + msg2.id);

    let msg3 = attend ChatMessage.create ({
      message: 'Super excité!',
      createdBy: users [0]
    });
    console.log ("Message de chat créé:" + msg3.id);

  } autre {
    console.log ('sauter la génération du message');
  }
} catch (err) {
  console.error (err);
}

// Il est très important de déclencher cette méthode de rappel lorsque vous avez terminé avec Bootstrap! (Sinon, votre serveur ne se soulèvera jamais, puisqu'il attend sur Bootstrap)
cb ();
}

La grande chose est que le générateur de graines fonctionne avant bootstrap.js . De cette façon, nous sommes sûrs que les données Users ont été créées en premier afin que nous puissions l'utiliser pour remplir le champ createdBy . Avoir des données de test nous permettra d'itérer rapidement pendant que nous construisons l'interface utilisateur

Interface de messagerie instantanée

Allez-y et créez un nouveau fichier views / partials / chat-messages.ejs puis placez ce code:

 

Conversations de communauté


La logique ici est très similaire à chat-users . Il y a une différence clé dans la section listen. Au lieu de remplacer la sortie rendue, nous utilisons append. Ensuite, nous faisons une animation de défilement vers le bas de la liste pour s'assurer que les utilisateurs voient le nouveau message entrant.

Ensuite, mettons à jour chatroom.ejs pour inclure les nouveaux messages de discussion partial et aussi mettre à jour le script pour appeler la fonction loadMessages () :

 ...

    <% include partials/chat-messages.ejs %>
 ...  

Votre vue devrait maintenant ressembler à ceci:

 chat-messages

Construisons maintenant un formulaire simple qui permettra aux utilisateurs de poster des messages sur la salle de discussion.

Créer un nouveau fichier views / partial / chat-post.ejs et collez ce code:

 
  
    

Ici, nous utilisons un élément sémantique-ui pour construire le formulaire. Ensuite, ajoutez ce script au bas du fichier:

  

This script is made up of two functions:

  • activateChat(). This function binds the post button to a click event and the message box (post field) to a key press (enter) event. When either is fired, the postMessage() function is called.

  • postMessage. This function first does a quick validation to ensure the post input field is not blank. If there’s a message is provided in the input field, we use the io.socket.post() function to send a message back to the server. Here we’re using a classic callback function to handle the response from the server. If an error occurs, we display the error message. If we get a 200 status code, meaning the message was captured, we clear the post input field, ready for the next message to be typed in.

If you go back to the chat-message script, you’ll see that we’ve already placed code to detect and render incoming messages. You should have also noticed that the io.socket.post() is sending data to the URL /postMessage. This is not a Blueprint route, but a custom one. Hence, we need to write code for it.

Head over to api/controllers/UserController.js and insert this code:

module.exports = {

postMessage: async (request, response) => {
    // Make sure this is a socket request (not traditional HTTP)
  if (!request.isSocket) {
    return response.badRequest();
  }

    try {
        let user = await User.findOne({email:'johnnie86@gmail.com'});
        let msg = await ChatMessage.create({message:request.body.message, createdBy:user });
        if(!msg.id) {
            throw new Error('Message processing failed!');
        }
        msg.createdBy = user;
        ChatMessage.publishCreate(msg);
    } catch(err) {
        return response.serverError(err);
    }

    return response.ok();
}
}

Since we haven’t set up basic authentication, we are hardcoding the user johnnie86@gmail.com for now as the author of the message. We use the Model.create() Waterline ORM function to create a new record. This is a fancy way of inserting records without us writing SQL code. Next we send out a notify event to all sockets informing them that a new message has been created. We do that using the ChatMessage.publishCreate() function, which is defined in the Blueprints API. Before we send out the message, we make sure that the createdBy field is populated with a user object. This is used by chat-messages partial to access the avatar and the name of the user who created the message.

Next, head over to config/routes.js to map the /postMessage URL to the postMessage action we just defined. Insert this code:

...
'/chat': {
view: 'chatroom'
}, // Add comma here
'/postMessage': {
controller: 'ChatMessageController',
action: 'postMessage'
}
...

Open views/chatroom.js and include the chat-post partial. We’ll also call the activateChat() function right after the loadMessages() function:

...
<% include partials/chat-messages.ejs %>
...  

Refresh the page and try to send several messages.

chat-post

You should now have a functional chat system. Review the project source code in case you get stuck.

Basic Authentication

Setting up a proper authentication and authorization system is outside the scope of this tutorial. So we’ll settle for a basic password-less authentication system. Let’s first build the signup and login form.

Login/Sign Up Form

Create a new file views/auth-form.ejs and paste the following content:

     <input type="text" name="name" placeholder="Full Names" value="<%= typeof name != 'undefined' ? name : '' %>">
  <input type="email" name="email" placeholder="Email" value="<%= typeof email != 'undefined' ? email : '' %>">

*Provide email only for Login

<% if(typeof error != 'undefined') { %>
<%= error.title %>

<%= error.message %>

<% } %>

Next open views/homepage.ejs and replace the TODO line with this include statement:

...
<% include partials/auth-form.ejs %>
...

We’ve created a form that allows you to create a new account by providing an input for name and email. When you click Signup & Logina new user record is created and you get logged in. However, if the email is already being used by another user, an error message will be displayed. If you just want to log in, just provide the email address and click the Login button. Upon successful authentication, you’ll be redirected to the /chat URL.

Right now, everything I’ve just said isn’t working. We’ll need to implement that logic. First, let’s navigate to / address to confirm that the auth-form looks goods.

auth-form

Policy

Now that we’re setting up an authentication system, we need to protect /chat and /profile routes from public access. Only authenticated users should be allowed to access them. Open config/policies.js and insert this code:

ChatMessageController: {
'*': 'sessionAuth'
},

UserController: {
'*': 'sessionAuth'
},

By specifying the name of the controller, we have also effectively blocked all routes provided by the Blueprint API for Users and Chat Messages. Unfortunately, policies only work with controllers. This means the route /chat can’t be protected in its current state. We need to define a custom action for it. Open api/controller/ChatroomController.js and insert this code:

...
render: (request, response) => {
    return response.view('chatroom');
},

Then replace the route config for /chat with this one one config/routes.js:

...
'/chat': {
     controller: 'ChatMessageController',
     action: 'render'
 },
 ...

The /chat route should now be protected from public access. If you restart your app and try to access /profile/chat/user or /chatmessageyou’ll be met with the following forbidden message:

forbidden

If you’d like to redirect users to the login form instead, head over to api/policies/sessionAuth and replace the forbidden call with a redirect call like this:

...
// return res.forbidden('You are not permitted to perform this action.');
return res.redirect('/');
...

Try accessing the forbidden pages again, and you’ll automatically be redirected to the home page. Let’s now implement the Signup and Login code.

Auth Controller and Service

You’ll need to stop Sails.js first in order to run this command:

sails generate controller Auth

This will create a blank api/controllers/AuthController for us. Open it and insert this code:

authenticate: async (request, response) => {

    // Sign up user
    if(request.body.action == 'signup') {
        // Validate signup form

        // Check if email is registered

        // Create new user
    }

    // Log in user
},

logout: (request, response) => {
    // Logout user
}

I’ve placed in comments explaining how the logic will flow. We can place the relevant code here. However, Sails.js recommends we keep our controller code simple and easy to follow. To achieve this, we need to write helper functions that will help us with each of the above commented tasks. To create these helper functions, we need to create a service. Do this by creating a new file api/services/AuthService.js. Insert the following code:

/**
* AuthService.js
*
**/

const gravatar = require('gravatar')

// Where to display auth errors
const view = 'homepage';

module.exports = {

sendAuthError: (response, title, message, options) => {
  options = options || {};
  const { email, name} = options;
  response.view(view, { error: {title, message}, email, name });
  return false;
},

validateSignupForm: (request, response) => {
  if(request.body.name == '') {
    return AuthService.sendAuthError(response, 'Signup Failed!', "You must provide a name to sign up", {email:request.body.email});
  } else if(request.body.email == '') {
    return AuthService.sendAuthError(response, 'Signup Failed!', "You must provide an email address to sign up", {name:request.body.name});
  }
  return true;
},

checkDuplicateRegistration: async (request, response) => {
  try {
    let existingUser = await User.findOne({email:request.body.email});
    if(existingUser) {
      const options = {email:request.body.email, name:request.body.name};
      return AuthService.sendAuthError(response, 'Duplicate Registration!', "The email provided has already been registered", options);
    }
    return true;
  } catch (err) {
    response.serverError(err);
    return false;
  }
},

registerUser: async (data, response) => {
  try {
    const {name, email} = data;
    const avatar = gravatar.url(email, {s:200}, "https");
    let newUser = await User.create({name, email, avatar});
    // Let all sockets know a new user has been created
    User.publishCreate(newUser);
    return newUser;
  } catch (err) {
    response.serverError(err);
    return false;
  }
},

login: async (request, response) => {
  try {
        let user = await User.findOne({email:request.body.email});
        if(user) { // Login Passed
            request.session.userId = user.id;
            request.session.authenticated = true;
            return response.redirect('/chat');
        } else { // Login Failed
      return AuthService.sendAuthError(response, 'Login Failed!', "The email provided is not registered", {email:request.body.email});
    }
    } catch (err) {
        return response.serverError(err);
    }
},

logout: (request, response) => {
  request.session.userId = null;
    request.session.authenticated = false;
    response.redirect('/');
}
}

Examine the code carefully. As an intermediate developer, you should be able to understand the logic. I haven’t done anything fancy here. However, I would like to mention a few things:

  • Gravatar. You need to install Gravatar. It’s a JavaScript library for generating Gravatar URLs based on the email address.

    ```bash
    npm install gravatar --save
    `` `
    
  • User.publishCreate(newUser). Just like ChatMessageswe fire an event notifying all sockets that a new user has just been created. This will cause all logged-in clients to re-fetch the users data. Review views/partial/chat-users.js to see what I’m talking about.

  • request.session. Sails.js provides us with a session store which we can use to pass data between page requests. The default Sails.js session lives in memory, meaning if you stop the server the session data gets lost. In the AuthServicewe’re using session to store userId and authenticated status.

With the logic inAuthService.js firmly in place, we can go ahead and update api/controllers/AuthController with the following code:

module.exports = {

authenticate: async (request, response) => {
    const email  = request.body.email;

    if(request.body.action == 'signup') {
        const name = request.body.name;
        // Validate signup form
        if(!AuthService.validateSignupForm(request, response)) {
            return;
        }
        // Check if email is registered
        const duplicateFound = await AuthService.checkDuplicateRegistration(request, response);
        if(!duplicateFound) {
            return;
        }
        // Create new user
        const newUser = await AuthService.registerUser({name,email}, response);
        if(!newUser) {
            return;
        }
    }

    // Attempt to log in
    const success = await AuthService.login(request, response);
},

logout: (request, response) => {
    AuthService.logout(request, response);
}
}

See how much simple and readable our controller is. Next, let’s do some final touches.

Final Touches

Now that we have authentication set up, we should remove the hardcoded value we placed in the postMessage action in api/controllers/ChatMessageController. Replace the email code with this one:

...
let user = await User.findOne({id:request.session.userId});
...

I’d like to mention something you may not have noticed, if you look at the logout URL in views/partials/menu.ejswe’ve placed this address /auth/logout. If you look at config/routes.jsyou’ll notice that we haven’t placed a URL for it. Surprisingly, when we run the code, it works. This is because Sails.js uses a convention to determine which controller and action is needed to resolve a particular address.

By now you should be having a functional MVP chat application. Fire up your app and test the following scenarios:

  • sign up without entering anything
  • sign up by filling only name
  • sign up by only filling email
  • sign up by filling name and a registered email — for example, johnnie86@gmail.com or jane@hotmail.com
  • sign up using your name and email
  • update your profile
  • try posting a blank message
  • post some messages
  • open another browser and log in as another user, put each browser side by side and chat
  • log out and create a new account.

duplicate-registration

multiple-chats-test

Phew! That’s a lot of functionality we’ve just implemented in one sitting and then tested. With a few more weeks, we could whip out a production ready chat system integrated with more features, such as multiple chat rooms, channel attachments, smiley icons and social accounts integration!

Summary

During this tutorial, we didn’t put the name of the logged-in user somewhere at the top menu. You should be capable of fixing this yourself. If you’ve read the entire tutorial, you should now be proficient in building applications using Sails.js.

The goal of this tutorial is to show you that can come from a non-JavaScript MVC framework and build something awesome with relatively few lines of code. Making use of the Blueprint API will help you implement features faster. I also recommend you learn to integrate a more powerful front-end library — such as React, Angular or Vue — to create a much more interactive web application. In addition, learning how to write tests for Sails.js to automate the testing process is a great weapon in your programming arsenal.






Source link