En este ocasión aprenderemos a integrar Vue.Js, uno de los frameworks frontend más populares en la actualidad, junto con el micro framewok Flask, de tal forma que podamos desarrollar aplicaciones web robustas en muy poco tiempo y con muy pocas líneas de código 😎.
Bien, no perdamos más tiempo y comencemos.
Instalación
Para este post será necesario contar con Flask y Vue instalado en tu equipo de computo. Si tú aun no cuentas con estas tecnologías, no te preocupes 😃.
Para generar nuestra aplicación Vue nos apoyaremos de vue-cli. La instalación la lograremos al ejecutar la siguiente sentencia.
$ npm install -g vue-cli
Para poder consumir servicios web utilizando JS haremos uso de la librería axios. Su instalación es muy sencilla.
npm install --save axios
La instalación de Flask lo haremos a través de pip.
$ pip install flask
Yo de forma personal te recomiendo que hagas la instalación de flask, y de cualquier otra librería de Python, mediante un entorno virtual, de esta forma podemos definir de forma concretas las dependencias entre un proyecto y otro, evitando conflictos de versiones entre librerías.
Crear un entorno virtual mediante virtual_env.
virtualenv -p python3 nombre_entorno_virtual
Activar entorno virtual
source nombre_entorno_virtual/bin/active
Desactivar entorno virtual
deactivate
Aplicación Frontend
Bien, una vez tengamos instalado todo lo necesario el siguiente paso será crear una nueva carpeta, carpeta en donde alojaremos nuestro servidor Flask y nuestro proyecto en Vue. En mi caso la carpeta tendrá por nombre flask_tutorial, tú puedes colocar el nombre que tú desees.
Dentro de la carpeta creamos nuestro projecto Vue.
$ mkdir flask_tutorial
$ cd flask_tutorial
$ vue init webpack fronted
Mi configuración para el proyecto queda de la siguiente manera.
? Project name fronted
? Project description A Vue.js project
? Author Eduardo Ismael García Pérez <eduardo78d@gmail.com>
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? Yes
? Pick an ESLint preset Standard
? Set up unit tests No
? Setup e2e tests with Nightwatch? No
? Should we run `npm install` for you after the project has been created? (recommended) npm
Probemos que el servidor funciones correctamente.
cd frontend
npm install
npm run dev
Una vez el servidor se encuentre a la escucha, debemos ir a nuestro navegador e ingresar a localhost:8080. Si todo ha salido bien obtendremos la siguiente salida.
El siguiente paso será crear una nueva vista, de tal forma que posteriormente nuestro servidor Flask pueda servirlas.
Dentro de nuestra carpeta components (frontend/src) creamos un nuevo archivo con extensión .vue, en mi caso el archivo tendrá por nombre Home.vue
<template>
<div>
<p>Hola Mundo desde Vue!</p>
</div>
</template>
Habilitamos una nueva url para desplegar dicho componenten, para ello modificamos nuestro archivo index.js (frontend/src/router). El archivo pudiese quedar de la siguiente manera.
import Vue from 'vue'
import Router from 'vue-router'
import Main from '@/components/Main'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'main',
component: Main
},
],
mode: 'history'
})
En este caso importo el nuevo componente y le asignó la url principal ('/'), también eliminó todo lo relacionado al componente default 😛, finalmente asignó 'history' al atributo mode.
Si ingresamos a localhost:8080 obtendremos nuestro Hola Mundo desde Vue!, junto con el icono de Vue, algo que en lo personal no me agrada 😰.
Para quitar dicho icono nos situamos en el archivo App.vue (frontend/src) y eliminamos la etiqueta tag.
<template>
<div id="app">
<router-view/>
</div>
</template>
Cool, ahora agreguemos un poco de interacción a nuestro componente, haremos que este consuma un servicio Web, servicio que por supuesto lo proveerá nuestro servidor Flask.
El componenten quedaría de la siguiente manera (Home.vue).
<template>
<div>
<p>{{ mensaje }}</p>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'Main',
data () {
return {
mensaje: 'Sin mensaje!'
}
},
methods: {
getMensaje () {
const path = 'http://localhost:5000/api/v1.0/mensaje'
axios.get(path).then((respuesta) => {
this.mensaje = respuesta.data
})
.catch((error) => {
console.log(error)
})
}
},
created () {
this.getMensaje()
}
}
</script>
Nos apoyamos de la librería axios para poder consumir un servicio web. Una vez obtengamos respuesta por parte del servidor actualizamos nuestro atributo mensaje, atributo que se encuentra bind en nuestro template. Todos esto lo logramos gracias a las promesas, si no te encuentras familiarizado con este termino, no te preocupes, te recomiendo tomes este taller, el cual sin duda te será de mucha ayuda.
Si actualizamos nuestra página web obtendremos como resultado Sin mensaje!, valor por default de nuestra variable mensaje, esto se debe a que no existe (por ahora) servidor a la escucha en el puerto 5000 que pueda responder a nuestra petición.
Servidor Flask
Nuestro servidor será muy sencillo. Dentro de la carpeta flask_tutorial, fuera de la carpeta frontend, creamos un nuevo archivo con extensión .py, en mi caso el archivo tendrá por nombre server.py.
El archivo quedaría de la siguiente manera.
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/api/v1.0/mensaje')
def create_task():
return jsonify('Hola mundo desde Flask')
if __name__ == '__main__':
app.run(debug=True)
Si ejecutamos e ingresamos a localhost:5000/api/v1.0/mensaje a través de nuestro navegador obtendremos el siguiente resultado.
Sin embargo 😰, si ingresamos a http://localhost:8080/ seguiremos obteniendo el mensaje Sin mensaje! :P. Si inspeccionamos la consola nos toparemos con pequeño error:
'http://localhost:5000/api/v1.0/mensaje' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Este error se debe a que nuestro servidor Flask no puede responder peticiones de servidores externos, en este caso Vue se encuentra en a la escucha en el puerto 8080 y Flask en el 5000.
Para solucionar este problema instalaremos flask-cors.
pip install -U flask-cors
En el archivo server.py quedaría de la siguiente manera.
from flask import Flask, jsonify
from flask_cors import CORS
app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
De esta forma nuestro servidor podrá responder cualquier petición por parte de Vue a la API. Si actualizamos localhost:8080 obtendremos la siguiente salida 😎.
Fusion
Hasta este punto tenemos dos servidores que trabajan en conjunto, y esta bien, sin embargo, lo ideal sería unicamente contar con uno y que este sea el encargado de proporcionar el API y servir las diferentes vistas, es por ello que en este último apartado haremos que sea nuestro servidor Flask el encargado proporcionar el HTML para el pintado.
Lo primero que haremos será compilar nuestro proyecto Vue, para ello ejecutamos la siguiente sentencia (Dentro de la carpeta frontend).
npm run build
Al nosotros compilar el proyecto obtendremos como resultado una nueva carpeta llamada dist, es en esta carpeta donde se encuentran todo nuestro frontend (HTML, CSS, y JS).
Ahora nos situamos en nuestro archivo server.py, ya que haremos un par de modificaciones.
from flask import render_template
... Código anterior
app = Flask(__name__,
static_folder = "./fronted/dist/static",
template_folder = "./fronted/dist")
... Código anterior
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def dender_vue(path):
return render_template("index.html")
... Código posterior
Primero modificamos los parámetros static_folder y template_folder, indicando que los archivos los encontraremos en una nueva ruta, dentro de la carpeta frontend/dist. Posteriormente indicamos que cualquier petición sobre '/' (raíz) será manejada por el archivo index.html. Con esta función podemos tener la n cantidad de urls definidas en Vue y Flask será capaz de servirlas sin ningún problema.
Issues
Aun que esto es funcional (de forma local), al momento de realizar el deploy a un servidor será necesario realizar un par de modificaciones al proyecto.
Cuando nos encontremos en producción no será necesario hacer uso de la clase CORS.
Una muy buena idea es agregar a nuestro archivo .gitgnore la carpeta dist, de tal forma que sea el mismo proceso de deploy quien construya los archivos. Si así lo deseamos podemos generar dicha carpeta fuera de frontend, basta con modificar el archivo index.js.
index: path.resolve(__dirname, '../../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../../dist'),
En este caso la carpeta dist se genera al mismo nivel que el archivo server.py.