portada construye live chat proyecto

Cómo construir un chat sincronizado en tiempo real – Proyecto – Parte #1

Última actualización:

¿Qué vamos a construir?

Vamos a construir una app de chat con sincronización en tiempo real, es decir, si dos personas están chateando, lo que escriba una y le envíe a la otra podrá ser visto por la otra prácticamente en el mismo momento de haber enviado el mensaje. Además, como usuario también podrás unirte a distintos canales de chat.

Otra de las características será el poder cambiar tu nombre de usuario cuando quieras.

Consideraciones previas y pre-requesitos

Para poder seguir este proyecto sin ir más perdido/a que un pulpo en un garaje, deberás tener conocimientos de:

Estructura inicial del proyecto

📄 index.html
📂 scripts
     📄 app.js
     📄 ui.js
     📄 chat.js
📄 styles.css

En cuanto a los scripts, app.js se encargará de conectar ui.js y chat.js entre sí para poder lanzar nuestra app.

Vamos a configurar el index.html.

1. Añadimos los scripts al final, en este orden: chat.js, ui.js app.js.

2. Añadimos bootstrap y nuestro propio CSS.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <link rel="stylesheet" href="styles.css">
    <title>Live chat</title>
</head>
<body>
    
    <script src="scripts/chat.js"></script>
    <script src="scripts/ui.js"></script>
    <script src="scripts/app.js"></script>
</body>
</html>

Vamos a encargarnos ahora de crear nuestro esqueleto HTML en el <body>. 💀

4. Usamos un container gigante que abarque toda la página y añadimos comentarios en el HTML a modo aclaratorio. 

    <div class="container my-4">
        <h3 class="my-4 text-center">Live chat</h3>
        <!-- buttons for chatrooms -->
        <div class="chat-rooms mb-3 text-center">
            <div class="my-2">Choose a chatroom:</div>
            <button class="btn my-1" id="general">#general</button>
            <button class="btn my-1" id="foodies">#foodies</button>
            <button class="btn my-1" id="tv-shows">#TV shows</button>
            <button class="btn my-1" id="music">#music</button>
        </div>
        <!-- chat window / conversations -->
        <!-- new chat form (message) -->
        <!-- update name form -->
        
    </div>
   

En la sección de chat window es donde se mostrará una conversación entera, diferenciando quién ha dicho qué. Cada vez que un usuario escriba un mensaje y lo mande, lo colocaremos en un <li> dentro del <ul>. Pero eso no lo vamos a escribir de manera estática (en adelante, harcoded) sino dinámica en nuestro código JavaScript (en adelante, JS).

La sección new chat form será la parte donde el usuario escriba su mensaje y se envíe a la base de datos (en inglés, database) que vamos a crear después.

Utilizamos tanto clases de bootstrap como clases propias

Al final de la sección update name form tenemos un <div> que se encargará de darle al usuario un comentario sobre la acción que acabe de hacer.  Vamos, de darle feedback.

        <!-- chat window / conversations -->
        <div class="chat-window">
            <ul class="chat-list list-group"></ul>
        </div>
        <!-- new chat form (message) -->
        <form class="new-chat my-3">
            <div class="input-group">
                <div class="input-group-prepend">
                    <div class="input-group-text">Say something :</div>
                </div>
                <input type="text" id="message" class="form-control" required>
                <div class="input-group-append">
                    <input type="submit" class="btn" value="send">
                </div>
            </div>
        </form>
        <!-- update name form -->
        <form class="new-name my-3">
            <div class="input-group">
                <div class="input-group-prepend">
                    <div class="input-group-text">Update name:</div>
                </div>
                <input type="text" id="name" class="form-control" required>
                <div class="input-group-append">
                    <input type="submit" class="btn" value="update">
                </div>
            </div>
            <div class="update-message"></div>
        </form>

Nuestra app debería tener una pinta como esta:

live chat estado inicial

Vamos a darle un poco de vidilla con CSS, en el archivo styles.css. 👩‍🎨

.container {
    max-width: 750px;
}

.btn {
    background-color: rgb(11, 165, 192);
    color: #fff;
}

.btn:hover,
.btn:active {
    background-color: rgb(15, 132, 153);
    color: #fff;
    transition: all 0.2s;
}

.btn:focus {
    box-shadow: none;
}

Cómo conectar nuestra app a Firebase

Terminado nuestro proceso de chapa y pintura, pasamos a configurar nuestra app 👩‍💻. Nuestro primer paso será conectarla a Firebase.

1. Para eso, creamos un proyecto en Firebase (en este post tienes los pasos necesarios). En algún momento de la configuración llegaremos a esta pantalla:

firebase setup

Así que no tenemos más que copiar ese snippet en nuestro index.html, justo arriba de nuestros scripts. Así Firebase sabrá a qué frontend conectarse. 

Para añadir Cloud Firestore, seguimos las indicaciones de este link https://firebase.google.com/docs/firestore/quickstart.

2. Iniciamos nuestra database y la guardamos en una variable a la que llamaremos db.

<!-- The core Firebase JS SDK is always required and must be listed first -->
    <script src="https://www.gstatic.com/firebasejs/7.4.0/firebase-app.js"></script>
    <!-- Add SDKs for Firebase products that you want to use
     https://firebase.google.com/docs/web/setup#available-libraries -->
    <script src="https://www.gstatic.com/firebasejs/7.4.0/firebase-firestore.js"></script>
    <script>
        // Your web app's Firebase configuration
        var firebaseConfig = {
            apiKey: "AIzaSyCS9wtLbIPDl6-5LDG6LJrgJkVFnvfMmX8",
            authDomain: "live-chat-2bfbc.firebaseapp.com",
            databaseURL: "https://live-chat-2bfbc.firebaseio.com",
            projectId: "live-chat-2bfbc",
            storageBucket: "live-chat-2bfbc.appspot.com",
            messagingSenderId: "742567843734",
            appId: "1:742567843734:web:eeecfe54679034e73aafb7"
        };
        // Initialize Firebase
        firebase.initializeApp(firebaseConfig);
        const db = firebase.firestore();
    </script>

Guarda los cambios y asegúrate de que no tienes ningún error en la consola antes de continuar.

Lo siguiente que haremos será crear una collection llamada chats. No es necesario crear una collection manualmente, ya que si a través de nuestro código creásemos un document e hiciésemos referencia a una collection que no existe, Firestore lo detectaría y crearía una collection por nosotros. 🤓

Pero en este caso, vamos a crearla nosotros, por aquello de "la práctica hace al maestro". 👩‍🏫

3. Creamos no solamente la chats collection, sino también un par de documentsNuestros campos serán: message, username, channel y created_at. Dejamos el document ID en blanco para que Firebase le asigne un ID automáticamente

Creación de la clase "Chatroom"

Con los documents creados en Firebase, vamos a pasar a la creación de nuestra primera clase de JS.

Cada vez que se construye un proyecto, es necesario estructurarlo previamente para saber qué pasos vamos a seguir para construirlo. Detallaré esos pasos al principio del chat.js, donde vamos a hacer toda la configuración durante esta sección.

Existen infinitas maneras de construir este tipo de app, y esta es una de tantas. Nuestro enfoque va a ser construir dos clases de JS, Chatroom ChatUILa clase Chatroom será la responsable de gestionar todos los datos que intercambiemos con Firebase, y la ChatUI, de mostrar en la UI de nuestra web todos los cambios que suceden en los datos.

// BUILDING PROCESS
// - add new chat documents
// - set up a real-time listener to get new chats
// - update the username
// - update the channel

Vamos a por esa primera clase.

1. Creamos el constructor dentro de la clase, que aceptará dos parámetros, channel username. Le añadimos una propiedad que no le pasaremos por parámetro (chats), que será una referencia a nuestra chats collection. 

2. Creamos una instance de nuestra clase. Hacemos un console.log para ver qué aspecto tiene. 

class Chatroom {
    constructor(channel, username) {
        this.channel = channel;
        this.username = username;
        this.chats = db.collection('chats');
    }

const chatroom = new Chatroom('music', 'Jim');
console.log(chatroom);

3. Lo siguiente que haremos será crear un método dentro de la clase para crear y añadir objetos con formato de documents. Será un método asíncrono, porque tardará cierto tiempo en completarse. ⌚  Aceptará como parámetro el mensaje que queramos escribir. 

Recuerda que un objeto con aspecto de document no es más que un objeto normal de JS, y con ese formato debemos enviarlo a Firestore para que lo reconozca como un document. 📃

Creamos una variable llamada now que equivaldrá a la fecha en la que te encuentres hoy. La utilizaremos para darle al mensaje una fecha de creación.

Creamos el objeto con un formato adecuado, conteniendo, igual que los campos que hemos creado manualmente en Firebase, un message, un username, un channel y una fecha de creación (created_at). Es como crear una cáscara para darle formato a un objeto que enviaremos a Firestore y que éste lo reconozca como un document válido. Lo llamamos chat.

    async addChat(message) {
        const now = new Date();
        const chat = {
            message: message,
            channel: this.channel,
            username: this.username,
            created_at: firebase.firestore.Timestamp.fromDate(now)
        };
    }

Podemos usar ES6 y acortar los key-value pairs cuando la key tiene el mismo nombre que el value, como ocurre en el caso de message.  

Añadiendo chats a la base de datos

Creado ya el método para generar documents, vamos a configurar la parte para guardar documents en Firestore. Para eso, creamos una variable llamada response y utilizamos awaitporque el método addChat devuelve una promesa. Utilizamos nuestra chats collections para aplicarle el método add()al que le pasamos nuestro objeto-cáscara (chat).

Hacemos un return de response. 

Con estos cambios, ya podemos usar el método addChat(). 👍 ¡Vamos a ello! Espera un message como argumento, así que le pasamos el texto que queramos. 

Recordemos que el método addChat() devuelve una promesa, así que le enganchamos un método then() y un método catch(). 

    async addChat(message) {
        const now = new Date();
        const chat = {
            message,
            channel: this.channel,
            username: this.username,
            created_at: firebase.firestore.Timestamp.fromDate(now)
        };

        // save documents in Firestore
        const response = await this.chats.add(chat);
        return response;
    }
}

const chatroom = new Chatroom('music', 'Jim');
// console.log(chatroom);

chatroom.addChat('any rock music fan here?')
    .then(() => console.log('chat added!'))
    .catch(err => console.log(err));

Si vas a Firebase, ya deberías ver ese document añadido correctamente. ¡Genial! 🤙

THE END!

¡Y hasta aquí la primera parte de este proyecto de construcción de un chat en tiempo real! Espero que hayas aprendido algo nuevo 😊.  Si te queda alguna duda, ¡nos vemos en los comentarios!

Si quieres seguir aprendiendo, nos vemos en la parte #2 (disponible próximamente).

Y si crees que este post puede serle útil a alguien, ¡compártelo!

Otros artículos que pueden interesarte

Días del 353 al 386
Objetivos versus realidad Y nuevamente, llegó otro día clave. Llegó…y pasó. El pasado 4 de marzo este Reto Computer Geek[...]
Angular: Entendiendo la Directiva ngModel
Angular es un framework que nos permite, entre otras cosas, añadir contenido dinámico a nuestros archivos HTML. Una de las formas[...]
Días del 2 al 4
"Si buscas resultados distintos no hagas siempre lo mismo" - Albert EinsteinEstos días estoy aprendiendo a hacer loops en JavaScript[...]

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Como toda web legal que se precie, utilizamos cookies para asegurar que damos la mejor experiencia al usuario en nuestro sitio web. Si continúas utilizando este sitio asumiremos que estás de acuerdo. más información

Los ajustes de cookies de esta web están configurados para "permitir cookies" y así ofrecerte la mejor experiencia de navegación posible. Si sigues utilizando esta web sin cambiar tus ajustes de cookies o haces clic en "Aceptar" estarás dando tu consentimiento a esto.

Cerrar