Tutorial Flask para crear un chat con WebSockets y VueJS

Cuando queremos realizar un chat como toca, se nos debería pasar por la cabeza usar WebSockets. Perminte infinidad de conexiones, es asincrono, utilizas protocolos modernos. E inmediatamente si buscas información pensarás que NodeJS la única alternativa. ¡Grave error!, podemos trabajar en otros lenguajes de programación. Y si usas a diario, o te enamora como a mi, Python… lo puedes realizar en un instante.

Por un lado usando el micro framework favorito de la comunidad Python: Flask. Y por otro VueJS para el frontend. En unos sencillos pasos lo tendremos funcionando.

¿Qué bibliotecas de Python necesitamos?

Preparando nuestro proyecto.

Creamos un entorno virtual con VirtualEnv, lo activamos y después instalamos las bibliotecas antes mencionadas.

virtualenv venv
source venv/bin/activate
pip install flask flask-migrate flask-script flask-socketio flask-sqlalchemy python-dotenv

Ahora creamos un archivo llamado .env. Irá toda nuestra configuración.

SECRET_KEY=secret
DEBUG=True
DATABASE="sqlite:///database.sqlite"
DOMAIN=127.0.0.1:5000

Base de datos

Nuestros mensajes antiguos se van a guardar y recuperar. El chat tendrá un historial.

Creamos un archivo llamado models.py. Con el siguente contenido.

# -*- coding: utf-8 -*-
# Librerias
from flask import Flask
from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from os import environ
from dotenv import load_dotenv, find_dotenv

load_dotenv(find_dotenv())
app = Flask(__name__)

# Settings
app.config['SQLALCHEMY_DATABASE_URI'] = environ.get('DATABASE')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# Variables
db = SQLAlchemy(app)
migrate = Migrate(app, db)
manager = Manager(app)
manager.add_command('db', MigrateCommand)


class Chat(db.Model):
    '''
    Table chat
    '''
    __tablename__ = 'chat'

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(128))
    text = db.Column(db.Text)
    created_at = db.Column(
        db.DateTime, nullable=False, default=datetime.utcnow)

    def __repr__(self):
        return '<Chat {0}>'.format(self.username)


if __name__ == "__main__":
    manager.run()

Después realizamos la migración.

python3 models.py db init
python3 models.py db migrate
python3 models.py db upgrade

Se creará el archivo database.sqlite con la tabla preparada.

Backend con Flask

Creamos un archivo llamado app.py. Dentro pegamos.

from os import environ
from dotenv import load_dotenv, find_dotenv
from flask import Flask, render_template
from flask_socketio import SocketIO, emit
from models import db, Chat

load_dotenv(find_dotenv())
app = Flask(__name__)

# Configuracion
app.config['SECRET_KEY'] = environ.get('SECRET_KEY')
app.config['DEBUG'] = True if environ.get('DEBUG') == 'True' else False
app.config['PORT'] = 80

# Socketio
DOMAIN = environ.get('DOMAIN')
socketio = SocketIO(app)

# Base de datos
app.config['SQLALCHEMY_DATABASE_URI'] = environ.get('DATABASE')
db.init_app(app)

# Cargamos la plantilla HTML con el frontend
@app.route('/<username>/')
def open_chat(username):
    my_chat = Chat.query.all()
    return render_template(
        'chat.html',
        domain=DOMAIN,
        chat=my_chat,
        username=username
    )

# Recibirá los nuevos mensajes y los emitirá por socket
@socketio.on('new_message')
def new_message(message):
    # Emitimos el mensaje con el alias y el mensaje del usuario
    emit('new_message', {
        'username': message['username'],
        'text': message['text']
    }, broadcast=True)
    # Salvamos el mensaje en la base de datos
    my_new_chat = Chat(
        username=message['username'],
        text=message['text']
    )
    db.session.add(my_new_chat)
    db.session.commit()


# Iniciamos
if __name__ == '__main__':
    socketio.run(app)

Frontend con Vuejs y Plantilla HTML

Creamos la carpeta templates, y dentro el archivo chat.html. Pegamos lo siguiente.

<!DOCTYPE html>
<html lang="es">

<head>
    <meta charset="UTF-8">
    <title>Chat</title>
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
</head>

<body>
    <!-- Renderizará los nuevos mensajes -->
    <section>
        <div v-for="message in messages">
            <p><strong>@[[ message.username ]]</strong></p>
            <p>[[ message.text ]]</p>
        </div>
    </section>
    <!-- Formulario para introducir nuevos mensajes -->
    <section>
        <input v-model="newMessage" @keypress.enter="sendMessage" type="text" placeholder="Escribe un mensaje...">
        <button @click="sendMessage">Enviar</button>
    </section>
    <!-- Importamos socket.io -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.4/socket.io.slim.js"></script>
    <!-- Importamos VueJS -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
    <script>
        // Conectamos con nuestro dominio
        var socket = io.connect('{{ domain }}');
        // Instanciamos VueJS
        var app = new Vue({
            el: "#app",
            delimiters: ['[[', ']]'],
            data: {
                username: '{{ username }}',
                // Le damos los mensajes del hitorial
                messages: [
                {% for message in chat %}
                    {
                        username: '{{ message.username }}',
                        text: '{{ message.text }}'
                    }{% if not loop.last %},{% endif %}
                {% endfor %}
                ],
                newMessage: ''
            },
            methods: {
                sendMessage: () => {
                    // Enviamos el nuevo mensaje
                    socket.emit('new_message', {
                        channel: app.channel,
                        username: app.username,
                        text: app.newMessage
                    });
                    // Clear text
                    app.$set(app, 'newMessage', '');
                }
            }
        });

        socket.on('connect', function() {
            console.log('Connect')
        });

        socket.on('new_message', function(msg) {
            // Recibimos los nuevos mensajes y los añadimos a nuestro array
            let my_messages = app.messages;
            my_messages.push({
                username: msg.username,
                text: msg.text
            })
            app.$set(app, 'messages', my_messages);
        });
    </script>
</body>

</html>

Ejecutamos

Ya ha llegado el momento de ver los resultados.

python3 app.py

Abrimos en el navegador la ruta .

En la ruta se especifica el nombre del usuario que aparecerá en el chat.

Con un poco de Bulma… quedaría de la siguente forma.

El código lo tenéis en por si queréis aportar cualquier cosa.

Versión escritorio