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](https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/Atom_editor_logo.svg/2000px-Atom_editor_logo.svg.png)
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:
-
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. -
Lo instalamos haciendo doble click. También podemos instalarlo desde la terminal (Ubuntu) con el comando
dpkg -i <nombre del fichero>.deb
-
Iniciamos el editor y en la barra de herramientas hacemos click en
Edit→Preferences
. -
Ahí hacemos click en
Install
. Y buscaremos e instalaremos los siguientes paquetes:-
ide-typescript
-
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 |
JavaScript (ES2015/ES6)
![js](http://wiki.uqbar.org/img/languages/ES6-ecmascript6-logo.jpg)
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.
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:
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.
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:
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.
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
.
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?
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:
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:
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 elreturn
y las llaves:arg ⇒ {return 1}
quedaría comoarg ⇒ 1
.
Otra situación que tradicionalmente ha sido muy molesta en JavaScript era la siguiente:
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:
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.
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:
// 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.
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
Crear aplicación web
Lo primero es crear un directorio donde vamos a introducir todo nuestro código.
Le llamaremos, por ejemplo, server
.
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 npm install Este comando mirará las dependencias listadas en el fichero |
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:
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
: Usanodemon
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 llamadadist/
que contendrá el código javascript que se ha generado conbabel
, 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 comandonpm prestart
, que se asegura de generar el código conbabel
. -
npm test
: Ejecuta los tests. En este caso comprueba que el código cumpla las normas de estilo usandoeslint
.
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:
.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:
npm start
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:
curl http://localhost:8080/
Hola mundo
![CC-BY-SA](https://mirrors.creativecommons.org/presskit/buttons/88x31/png/by-sa.png)