Introducción

En esta guía veremos cómo realizar una aplicación web que cumpla con los requisitos del trabajo del curso 16/17 para la asignatura FAST (Grando en Ingeniería de las Tecnologías de las Telecomunicaciones de la Universidad de Sevilla).

Sin embargo, en lugar de usar las tecnologías que se imparten durante el curso, usaremos otras más actuales. Si quieres ver el porqué, échale un ojo a este repositorio.

Estructura del documento

A lo largo de esta guía veremos lo siguiente:

  • JavaScript ES2015: Aquí veremos algunas de las nuevas características de JavaScript que no se ven durante el curso. Muchas de estas características mejoran la legibilidad del código así como disminuyen los bugs que podemos tener.

  • Enunciado: Adaptaremos el enunciado de forma que nos quedaremos sólamente con el qué hay que hacer y eliminaremos las indicaciones del cómo hay que hacerlo.

  • Backend: Realizaremos toda la parte correspondiente al backend. Aquí veremos Node.js junto con express.js y MongoDB.

  • Frontend: Haremos la parte de frontend con Vue.js. Tocaremos HTML, CSS3 y JavaScript.

Configuración del entorno

Aunque en este documento se supodrá que se usa GNU/Linux (Ubuntu en concreto), es posible (aunque no se detallará) desarrollar la aplicación bajo Windows u OSX.

Editor

Atom

Lo primero y más importante es disponer de algún editor que nos permita escribir código de forma cómoda. En principio sirve cualquier editor de texto, aunque es recomendable usar un editor de texto preparado para código. Algunos son:

También podemos usar un IDE, que es un entorno de desarrollo completo. Para JavaScript, uno de los más completos es WebStorm.

Veremos cómo instalar Atom:

  1. Acceder a https://atom.io y descargar el fichero .deb si estamos en Ubuntu u otra distribución basada en Debian. Usaremos el .rpm para distrubuciones basadas en Red Hat, como Fedora o Centos.

  2. Lo instalamos haciendo doble click. También podemos instalarlo desde la terminal (Ubuntu) con el comando dpkg -i <nombre del fichero>.deb

  3. Iniciamos el editor y en la barra de herramientas hacemos click en Edit→Preferences.

  4. Ahí hacemos click en Install. Y buscaremos e instalaremos los siguientes paquetes:

    1. ide-typescript

    2. linter-eslint

Con esto ya tendremos Atom preparado para desarrollar JavaScript.

Node.js

El otro elemento principal que tenemos que tener instalado es node.js. La forma más cómoda es instalarlo usando nvm. Este script ayuda con la instalación de node.js ya que permite elegir qué versión instalar, además de ofrecer otras utilidades. Instalamos nvm ejecutando:

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash

Y para instalar la última versión estable de node.js ejecutamos:

nvm install node
nvm use node
Es posible que después de instalar nvm necesites cerrar la terminal y volverla a abrir para poder ejecutar los dos últimos comandos.

Para comprobar que tenemos node.js correctamente instalado, podemos ejecutar:

node --version

Esto debería mostrarnos la versión actualmente instalada. También debería haber sido instalado npm, que es el gestor de paquetes de node.js. Este gestor nos permite instalar fácilmente las dependencias de un proyecto.

Cuando nos descargamos un proyecto de un repositorio, lo primero que habría que hacer es entrar a la carpeta del proyecto y ejecutar npm install. Este comando descargará automáticamente todas las dependencias y las almacenará en el directorio node_modules.

JavaScript (ES2015/ES6)

js

Antes de ponernos manos a la obra deberíamos repasar JavaScript ya que va a ser el lenguaje que nos va a acompañar durante todo el desarrollo de la aplicación web.

Vamos a hacer incapié en las nuevas funcionalidades definidas en el estándar ES2015 (también conocido como ES6), que pasan desapercibidas en la asignatura, a pesar de mejorar mucho el lenguaje.

Aunque existen versiones más modernas aún, todavía no están completamente soportadas en la mayoría de los navegadores.

Declaración de variables

Tradicionalmente en JavaScript se ha usado el keyword var para la declaración de variables. Declarar variables de esta forma tiene algunas peculiaridades que nos puede inducir a errores en nuestro código, por lo que no deberíamos usar var jamás.

Código 1. Declarando variable con var
console.log(nombre);
var nombre = 'Carlos';

Parece un error obvio, y de hecho lo es. Sin embargo, errores como este ocurren a menudo y la mayoría de las veces (cuando hay miles de líneas de código) no son para nada obvios. En otros lenguajes esperaríamos que este código de algún tipo de error, puesto que claramente estamos usando la variable nombre antes de ser declarada. Desgraciadamente, JavaScript (debido a una funcionalidad conocida como hoisting) convierte el código anterior internamente en el siguiente:

Ejemplo 1. Hoisting en JavaScript
var nombre;
console.log(nombre);
nombre = 'Carlos';
undefined

Por lo tanto, este código no dará ningún error y funcionará. Sin embargo, funcionará haciendo algo que no es lo que esperamos, lo cual es mucho peor. En este caso el programa imprimirá undefined, ya que la variable ha sido declarada pero no tiene asignado un valor.

En la programación siempre se prefiere un programa que no funcione antes que un programa que funcione de forma inesperada.

Presentando let y const. Declarando una variable usando let en lugar de var evita este problema ya que sí dará un error al ejecutarlo y no tendremos un comportamiento inesperado.

Ejemplo 2. Declarando con let
console.log(nombre);
let nombre = 'Carlos';
ReferenceError: nombre is not defined

Otro caso en el que var nos la puede jugar es el siguiente:

Código 2. Alcance de var
function varTest(condicion) {
  var x = 31;

  if (condicion) {
    var x = 71;
    console.log(x); (1)
  }

  console.log(x); (2)
}
1 Aquí se imprimiría por pantalla 71
2 ¡Aquí también se imprimiría por pantalla 71!

En este caso hemos declarado una variable dentro del bloque if con el mismo nombre que una de fuera. En la mayoría de los lenguajes se produciría lo que se conoce como shadowing, es decir, que dejaríamos de ver la variable x de fuera y tendríamos una x local, por lo que si modificamos su valor, la x de fuera quedaría intacta. No es el caso de var. En este caso habríamos cambiado el valor de la variable x. Una vez más, usando let nos ahorraríamos este tipo de comportamientos que nos conducen a errores.

Código 3. Alcance de let
function varTest(condicion) {
  let x = 31;

  if (condicion) {
    let x = 71;
    console.log(x); (1)
  }

  console.log(x); (2)
}
1 Aquí se imprimiría por pantalla 71
2 Aquí se imprimiría por pantalla 31

const tiene las mismas propiedades que let, pero además nos impide reasignar la variable, lo cual nos da un plus de seguridad. Por regla general deberíamos declarar todo usando const y sólamente cuando sea completamente necesario usar let.

Ejemplo 3. Usando const
const nombre = 'Carlos';
nombre = `Ernesto`;
TypeError: Assignment to constant variable.
Nunca declarar variables con var.

Triple igual ===

Cuando se empieza con JavaScript un error que se suele cometer es pensar que el doble igual == para las comparaciones es equivalente al doble igual visto en otros lenguajes. Esto no es así. En JavaScript tenemos el triple igual === que es el equivalente al doble igual en los otros lenguajes. ¿Cuál es la diferencia?

Código 4. Doble igual
null == undefined;
0 == '';
0 == [];
0 == false;
'' == [];

Aparentemente estamos comparando cosas completamente diferentes, sin embargo, todos los casos anteriores devuelven true. Por supuesto, esto es otra fuente muy común de errores que puede dar muchos dolores de cabeza. En cambio, usando el triple igual, todos los casos anteriores son false.

Nunca usar el operador doble igual ==.

Flecha gorda (fat arrow)

En JavaScript es muy común encontrarnos con el siguiente patrón:

Código 5. Pasando una función como parámetro
const fs = require('fs'); (1)

fs.readFile('/home/carlos/fichero', 'utf8', function(err, data) { (2)
  if (err) {
    return console.log(err);
  }

  console.log(data);
});

const plusOne = function(number) {
  return number + 1;
}

plusOne(7); (3)
1 Importamos el módulo fs de Node.js que se usa para leer ficheros.
2 Pasamos una función como parámetro.
3 Devuelve 8.

En este caso, le estamos pasando a la función fs.readFile tres parámetros, los dos primeros son de tipo string y el segundo es una función lambda.

Este patrón es típico en JavaScript, básicamente consiste en definir una función, pero en lugar de llamarla nosotros, la pasamos a fs.readFile para que la llame cuando haya terminado de leer el fichero. Cuando usamos una función de esta forma le llamamos función callback. Nosotros nunca llamamos a la función callback, sino que la llama la función a la cual se la hemos pasado. De hecho, no podríamos llamarla aunque quisíeramos porque es una función anónima.

Las funciones lambda son aquellas que definen y pasan como parámetro a otra función en su llamada. No es una definición muy rigurosa, pero para este caso es suficiente.

Esta notación es tan frecuente en JavaScript que en la versión ES2015 tenemos una forma compacta de escribirla, conocida como notación fat arrow:

Código 6. Fat arrow
const fs = require('fs');

fs.readFile('/home/carlos/fichero', 'utf8', (err, data) => {
  if (err) {
    return console.log(err);
  }

  console.log(data);
});

const plusOne = number => number + 1;
plusOne(7);

En el caso de la función plusOne vemos que se ha quedado aún más compacta porque la notación fat arrow permite lo siguiente:

  • Cambiar function(arg1, arg2, arg3) { …​ } por (arg1, arg2, arg3) ⇒ { …​ }

  • Si hay un sólo parámetro se pueden omitir los paréntesis arg ⇒ { …​ }

  • Si la función sólo tiene una línea que es un return, se puede eliminar el return y las llaves: arg ⇒ {return 1} quedaría como arg ⇒ 1.

Otra situación que tradicionalmente ha sido muy molesta en JavaScript era la siguiente:

Código 7. Pérdida de la referencia a this
const Alumno = function(nombre) {
  this.nombre = nombre;
}

Alumno.prototype.mostrarNota = function() {
  const self = this; (1)
  obtenerNota(function(nota) {
    console.log('La nota de ' + self.nombre + ' es :' + nota);
  });
}

const carlos = new Alumno('Carlos');
carlos.mostrarNota();
1 Guardamos la referencia a this en self.

En el código anterior queremos usar el atributo nombre de la clase Alumno, pero no podemos usar la referencia this dentro de una función callback ya que this en este contexto se refiere a la función callback y no a la instancia de Alumno. Esto siempre ha sido una molestia hasta la llegada de la función fat arrow. Con ella podemos escribir el mismo código de la siguiente forma:

Código 8. Uso de fat arrow con this
const Alumno = function(nombre) {
  this.nombre = nombre;
}

Alumno.prototype.mostrarNota = function() {
  obtenerNota(nota => console.log('La nota de ' + this.nombre + ' es :' + nota));
}

const carlos = new Alumno('Carlos');
carlos.mostrarNota();

Como vemos, la notación fat arrow no sobrescribe la referencia a this.

Usar la notación fat arrow siempre que la usemos como una función lambda. De hecho, muchas veces se le llama lambda a las funciones definidas usando la notación fat arrow.

Interpolación

JavaScript, al ser un lenguaje con un tipado débil, permite realizar operaciones entre tipos que pueden no tener sentido. Para los números, está definida la suma, la resta, la multipliación, etc. La confusión viene cuando hacemos cosas como sumar enteros con cadenas, sumar arrays con objetos, restar null a números, etc.

En JavaScript ES2015 tenemos la interpolación de cadenas. Esto es, una forma de definir una cadena usando los caracteres ` en lugar de comillas simples o dobles. Usando método podemos usar variables dentro de cualquier cadena sin tener que usar el operador suma para concatenarlas.

Código 9. Interpolación de cadenas
const nombre = 'Carlos'
const apellido = 'Fernández'
const edad = 31

console.log(
  'Hola, me llamo ' + nombre + ' ' + apellido + ' y tengo ' + edad + ' años.',
)
console.log(`Hola, me llamo ${nombre} ${apellido} y tengo ${edad} años.`)

Las dos formas anteriores son equivalentes. La segunda forma, además de ser más limpia, es más segura al no usar el operador + que muchas veces da resultados inesperados.

Nunca usar el operador + entre diferentes tipos y preferir siempre la interpolación a la concatenación.

Clases

JavaScript no tiene clases, no nos engañemos. En lugar de clases tiene un sistema de prototipos. Todos los objetos se crean a partir de un prototipo, por lo que si modificamos el prototipo, los objetos creados a partir de éste también portarán los cambios.

Técnicamente el sistema de prototipos es más potente que el sistema de clases, ya que el sistema de prototipos permite "emular" el sistema de clases, pero no al revés. Con la popularidad que ha adquirido el paradigma de orientación a objetos, es muy típico usar JavaScript orientado a objetos, aunque su sintaxis no es la más limpia. Veamos un ejemplo:

Ejemplo 4. Clases en JavaScript pre-ES2015
// Constructor de la clase Alumno
function Alumno(_nombre, _edad) {

  // Atributos privados
  let edad = _edad

  // Atributos públicos
  this.nombre = _nombre

  // Método privado
  function sumaEdad(años) {
    edad += años
  }

  // Método público
  this.cumpleAños = function() {
    sumaEdad(1)
    console.log(`Me llamo ${this.nombre} y he cumplido ${edad} años!`)
  }
}

const carlos = new Alumno('Carlos', 31)
carlos.cumpleAños()
Me llamo Carlos y he cumplido 32 años!

Como hemos dicho, no es lo más bonito del mundo, pero funciona. En las nuevas versiones de JavaScript tenemos una forma mucho más elegante para definir clases.

Código 10. Creando clases en JavaScript ES2015
class Alumno {
  constructor(nombre, edad) {
    this.nombre = nombre
    this.edad = edad
  }

  sumaEdad(años) {
    this.edad += años
  }

  cumpleAños() {
    this.sumaEdad(1)
    console.log(`Me llamo ${this.nombre} y he cumplido ${this.edad} años!`)
  }
}

const carlos = new Alumno('Carlos', 31)
carlos.cumpleAños()

Como vemos es mucho más elegante usar esta sintaxis. Sin embargo, hemos perdido la capacidad de declarar métodos y atributos privados. Esto es un problema que podrá solucionarse en futuras versiones de JavaScript. Por ahora si necesitamos atributos o métodos privados, debemos recurrir a la sintaxis antigua.

Recuerdo que en JavaScript no existen las clases. El código anterior, a pesar de usar el keyword class, internamente se convierte al código que hemos visto en Clases en JavaScript pre-ES2015

Backend

Objetivo

El trabajo consiste en la realización de una aplicación web que ayude a la gestión del inventario y estado de reparación de una serie de dispositivos. Los usuarios necesitan introducir su usuario y contraseña para poder acceder, y hay dos tipos de usuarios:

  • Clientes: Pueden introducir dispositivos para reparar y ver el estado de reparación de sus dispositivos.

  • Administrador: Además puede modificar el estado de reparación de todos los dispositivos y crear/modificar clientes (crear clientes nuevos y asignarle contraseña, o modificar la contraseña de los clientes existentes).

Los usuarios se identifican por su nombre de usuario, y los dispositivos por su MAC. Cada usuario (puede ser administrador o no) tiene asociada una contraseña. Cada dispositivo tiene asociado su dueño, un tipo de dispositivo y un estado de reparación. Los tipos de dispositivo tienen un identificador y una descripción. El estado de reparación puede tomar dos valores que representan arreglado o pendiente.

Creación y configuración de la aplicación y del servidor

node express

Crear aplicación web

Lo primero es crear un directorio donde vamos a introducir todo nuestro código. Le llamaremos, por ejemplo, server.

Código 11. Crear una aplicación con express
mkdir server
cd server
npm init

Este último comando nos preguntará una serie de cosas, podemos rellenarlo con los siguientes datos:

package name: (server) fast-reboot-server

version: (1.0.0) 0.1.0

description: El trabajo de FAST como si fuese 2017

entry point: (index.js) src/index.js

test command:

git repository:

keywords:

author: Diego Fernández Barrera <Introduce tu nombre>

license: (ISC)

Esto nos habrá generado un fichero llamado package.json que contiene información sobre nuestra aplicación. En este fichero también se listarán las dependencias para que puedan ser instaladas posteriormente.

Si más adelante queremos instalar todas las dependencias del proyecto, en el directorio server ejecutaremos:

npm install

Este comando mirará las dependencias listadas en el fichero package.json y las instalará en la carpeta node_modules.

Instalación de dependencias

A continuación vamos a instalar algunas dependencias ejecutando el siguiente comando:

npm install --save express (1)
npm install --save-dev babel-cli babel-preset-es2015 nodemon eslint (2)
1 Se instala express.js. Usando --save se añade el módulo a la lista de dependencias en el fichero package.json.
2 Se instalas babel-cli y babel-preset-es2015 y otros se añaden a la lista de dependencias de desarrollo. Las dependencias de desarrollo son opcionales, pues sólo son necesarias si vamos a realizar tareas de desarrollo.

Esto creará una carpeta llamada node_modules en nuestro proyecto donde se instalarán estos módulos y sus dependencias.

Babel

Es un módulo que nos permite usar nuevas funciones de JavaScript que aún no han sido implementadas. Babel se encarga de convertir el código que escribimos a código antiguo que es compatible con navegadores o versiones de node.js anteriores.

ESLint

Es un linter que nos permite comprobar que nuestro código cumple unas normas de estilo impuestas. También puede comprobar si hay algunos errores antes de que ejecutemos nuestro código.

Nodemon

Es un módulo que ejecuta nuestro servidor y cuando detecta cambios en el código fuente lo reinicia. Muy útil cuando estamos desarrollando.

Configuración de scripts

Ahora vamos a modificar la sección scripts de nuestro fichero package.json para que quede de la siguiente forma:

Código 12. Sección scripts de package.json
// ...
"scripts": {
  "dev": "nodemon -w src --exec \"babel-node src --presets es2015\"",
  "build": "babel src -s -D -d dist --presets es2015",
  "prestart": "npm run -s build",
  "start": "node dist",
  "test": "eslint src"
},
// ...

Los scripts son comandos que podemos ejecutar con npm de forma cómoda. Veamos los que hemos añadido:

  • npm run dev: Usa nodemon para ejecutar nuestro servidor en modo desarrollo, es decir, detectará cambios en el código fuente y se reiniciará automáticamente.

  • npm run build: Generará una carpeta llamada dist/ que contendrá el código javascript que se ha generado con babel, o sea, el código compatible con versiones anteriores de JavaScript. Este es el código que nosotros ejecutaremos.

  • npm start: Ejecuta el servidor. Atomáticamente se ejecutará el comando npm prestart, que se asegura de generar el código con babel.

  • npm test: Ejecuta los tests. En este caso comprueba que el código cumpla las normas de estilo usando eslint.

Para evitar que git realice control de versiones sobre la carpeta node_modules y la carpeta dist, crearemos un fichero llamado .gitignore en nuestro proyecto con el siguiente contenido:

Código 13. .gitignore
node_modules/
dist/

Ejecución del servidor

Por último creamos un fichero llamado index.js en la carpeta src, en él introducimos lo siguiente:

import http from 'http'; (1)
import express from 'express'; (2)

const app = express(); (3)
const server = http.createServer(app); (4)

app.get('/', (req, res) => { (5)
    res.end('Hola mundo');
});

server.listen(8080); (6)
1 Se importa el módulo http, que es un módulo nativo de node.js.
2 Se importa el módulo express.js.
3 Se crea una instancia de la aplicación express.js.
4 Se crea un servidor http y se le pasa como parámetro la instancia de la aplicación express.js. La función createServer recibe como parámetro una función que se encarga de manejar las peticiones. En este caso estamos delegando este trabajo en express.js.
5 Configuramos una función que se encarge de manejar las peticiones GET a la ruta /. Esta función simplemente devuelve la cadena "Hola Mundo".
6 Por último ponemos a escuchar nuestra aplicación en el puerto 8080.

Para comprobar que todo funciona, ejecutamos nuestra aplicación con el comando:

Código 14. Ejecutar servidor
npm start
Código 15. Ejecutar servidor con recarga automática
npm run dev

Podemos comprobar en un navegador que todo funciona correctamente entrando a la URL http://localhost:8080. También podemos usar el comando curl de la siguiente forma:

Ejemplo 5. Comprobando que el servidor web responde
curl http://localhost:8080/
Hola mundo

CC-BY-SA