dimarts, 24 de març del 2015

Iniciación rápida a nodejs y Express

Como iniciación rápida a nodejs ,desarrollaremos una pequeña página web, con el framework Express que sirva para subir archivos al servidor.

Añadir el PPA de node.js y instalar


Usaremos la versión 0.10.37 (comprobar con node -v). Si no la tenemos:


sudo apt-get install -y curl
curl -sL https://deb.nodesource.com/setup | sudo bash -
sudo apt-get install -y nodejs


Instalar express y express-generator


sudo npm install express
sudo npm install express-generator -g

Generar el "esqueleto" de nuestra aplicación-web


express -H myapp   
cd myapp
sudo npm install


La opción -H es para usar el lenguaje Hogan, por defecto se usa JADE.
Instala automáticamente todos los módulos que requiere nuestra app.


Iniciar la aplicación-web


npm start






Estructura de la aplicación

Con los comandos anteriores, Express nos ha generado unos ficheros y directorios que serán el esqueleto de nuestra aplicación. Lo que tendremos que hacer nosotros es programar en base a estos archivos. Vamos ha echarles un ojo. Abre el directorio /myapp  con el editor Brackets.


/bin - Aquí encontramos el script www, que inicia la aplicación (es llamado por npm start)
/node_modules - Los módulos que instalamos en nuestra app
/public - Los archivos de este directorio son accesibles de forma estática, directamente
/routes - Aquí pondremos nuestros scripts, a los que les tendremos que asignar una ruta (URL)
/views - Las plantillas HTML (en lenguaje HOGAN)
app.js - Configuración de la app. Principalmente los módulos y asignación de rutas a scripts


Estructura del archivo app.js


Pequeña descripción de las instrucciones del archivo  app.js:


Requerimiento de los módulos que se utilizan
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');


Requerimiento de los scripts de la aplicación (aquí añadiremos las referencias a nuestros scripts)
var routes = require('./routes/index');
var users = require('./routes/users');


Se crea la aplicación express
var app = express();


Configuración del motor de plantillas Hogan
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'hjs');


Configuración del módulo favicon
// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));


Configuración del módulo de depuración
app.use(logger('dev'));


Configuración de los módulos bodyParser y cookieParser
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());


Configuración del directorio de archivos estáticos
app.use(express.static(path.join(__dirname, 'public')));


Asignación de las rutas a nuestros scripts (añadiremos nuestras asignaciones de rutas a scripts)
app.use('/', routes);
app.use('/users', users);


Gestión de errores
// catch 404 and forward to error handler
app.use(function(req, res, next) {
 var err = new Error('Not Found');
 err.status = 404;
 next(err);
});


// error handlers
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
 app.use(function(err, req, res, next) {
   res.status(err.status || 500);
   res.render('error', {
     message: err.message,
     error: err
   });
 });
}


// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
 res.status(err.status || 500);
 res.render('error', {
   message: err.message,
   error: {}
 });
});


Exportamos nuestra app como un módulo [que iniciaremos con npm]
module.exports = app;


Rutas


Cuando se accede a la dirección http://localhost:3000, se ve una página como esta:


1.png


¿Por qué nos está mostrando esto? Comencemos...

En el archivo app.js tenemos puesta esta asignación:


var routes = require('./routes/index');
app.use('/', routes);


Aquí estamos diciendo que cuando nos hagan una petición a la raiz ('/'), se tiene que ejecutar routes, que hemos indicado que es el archivo './routes/index'. Así que ese es el script que se ejecuta. El script ./routes/index.js contiene el siguiente código:


var express = require('express');
var router = express.Router();


/* GET home page. */
router.get('/', function(req, res, next) {
 res.render('index', { title: 'Express' });
});


module.exports = router;


El router es el que se encarga de ejecutar un script u otro dependiendo de la petición que llegue.
Aquí le estamos diciendo que cuando reciba un petición por el método GET y la ruta sea la raiz (router.get('/'), ejecute esta función:


function(req, res, next) {
 res.render('index', { title: 'Express' });
}


De esta forma, el router, cuando recibe una petición, ejecuta esta función poniendo los valores que correspondan a nuestros parámetros (req, res, next). En el parámetro req nos pondrá todos los datos sobre la petición que ha llegado. En el parámetro res tendremos que poner nosotros la respuesta que queremos enviar al navegador (código html, o datos). [En el parámetro next nos pone la siguiente función que se ha asignado a la misma ruta, si hay alguna, ya que se pueden asignar más de una función a la misma ruta y ejecutarlas en cadena.]
Para nuestra aplicación, debemos modificar la ruta index (‘/’), y también añadir las que queramos.


Plantillas


La instrucción res.render('index', {title: 'Express'}) que hay dentro de la función , hace que se responda al navegador con el resultado de renderizar la plantilla 'index' con los datos {title: 'Express'}.
Esto es posible gracias a que en la app.js hemos configurado HOGAN como el motor de renderizado:


app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'hjs');


Le hemos dicho que busque las plantillas en el directorio 'views'. Allí tenemos la plantilla 'index' (el archivo /views/index.hjs), que se ha renderizado con los datos {title: 'Express'}, o sea, en la plantilla se ha cambiado la marca {{ title }} por el texto “Express”.


<!DOCTYPE html>
<html>
 <head>
   <title>{{ title }}</title>
   <link rel='stylesheet' href='/stylesheets/style.css' />
 </head>
 <body>
   <h1>{{ title }}</h1>
   <p>Welcome to {{ title }}</p>
 </body>
</html>


Es muy recomendable utilizar lenguajes de plantillas como HOGAN en lugar de HTML puro y duro. Al igual que usar LESS en lugar de CSS directamente. Hay muchos lenguajes de plantillas interesantes. Como hemos dicho, Express utiliza JADE por defecto.


Si se quiere, se puede seguir utilizando HTML. Prueba a crear el archivo HTML /public/index.html y recarga http://localhost:3000.


Static


La instrucción app.use(express.static(path.join(__dirname, 'public'))) que hay en /app.js hace que los archivos del directorio /public sean accesibles directamente. Podemos poner las imágenes, el CSS, o los scripts que tenga que descargar el navegador.
Pongamos una imagen en /public/favicon.ico, y ya podemos descomentar esta linea del archivo app.js.


app.use(favicon(__dirname + '/public/favicon.ico'));


Este icono lo piden los navegadores para usarlo en las pestañas o la barra de favoritos.


Parsers


La gestión de archivos y cookies la veremos más adelante


Errores


Sobre los errores, decir que se ha puesto una función para el caso de que llegue una petición a un ruta no asociada a ningún script, y así, se envie un mensaje de error propio, en lugar de no enviar nada y que el navegador muestre su mensaje.
Después, se han configurado dos niveles de error: uno para "desarrollo", que muestra los errores del javascript en la página del navegador, y otro para "producción" que no lo hace.


Programación de la aplicación

Hemos echado un vistazo rápido al esqueleto generado para nuestra aplicación. Ahora añadiremos el código para hacer nuestra web de subir archivos.

Cuando toquemos un script que sea ejecutado por node, habrá que reiniciar el servidor con:

npm start.


Upload


Para realizar la subida de archivos necesitaremos programar dos partes:
  1. La página que permite al usuario seleccionar los archivos que quiere subir, y los envía al servidor
  2. El programa del servidor que recibe los archivos y los guarda en una carpeta del disco.


PARTE 1: Crear la página donde se seleccionarán los archivos y se enviarán


Podemos utilizar la página index o crear otra en otra ruta. Haremos los segundo.
Añadiremos estas dos instrucciones al archivo app.js  (cada una en su lugar correspondiente):


var upload = require('./routes/upload');
app.use('/upload', upload);


A partir de este momento, cuando se haga una petición a http://localhost:3000/upload se ejecutará el script /routes/upload.js . Vamos a crear este script:


var express = require('express');
var router = express.Router();


router.get('/', function(req, res, next) {
 res.render('upload', { title: 'Upload' });
});


module.exports = router;


Ordenamos que cuando se reciba una petición GET se renderice la plantilla 'upload' (/views/upload.hjs). En este archivo ya podemos poner lo siguiente:


<!DOCTYPE html>
<html>
 <head>
   <title>{{ title }}</title>
   <link rel='stylesheet' href='/stylesheets/style.css' />
 </head>
 <body>
   <h1>{{ title }}</h1>
   <p>Welcome to {{ title }}</p>
 </body>
</html>


En esta página es donde pondremos el código que ejecutará el navegador para permitir seleccionar archivos y enviarlos al servidor. Vamos por partes:

- Seleccionar los archivos


Tenemos dos opciones para crear la página web donde se seleccionarán los archivos que se enviarán:
  1. Poner un formulario que tenga un campo <input type=”file”>
  2. Permitir el Drag’n’Drop


(a) Habrá que añadir este formulario (al archivo /views/upload.hjs), dentro del <body>:


<form enctype="multipart/form-data" action="/upload" method="post">
 <input type="file">
 <input type="submit" value="Upload Image">
</form>


(b) La segunda opción requiere añadir un script que recoja el evento de soltar archivos (drop). Añadiremos un cuadro (div) donde el usuario tendrá que arrastrar los archivos. Después asociaremos al evento "drop" de este cuadro una función que envie los archivos enviados. Escribimos el siguiente código en el archivo /views/upload.hjs


<div id="dropzone" style="width: 100px; height: 40px; border: 4px dashed lightgray" >


A los eventos se asignan funciones, y cuando estos ocurren, el navegador las ejecuta poniendo en los parámetros la información sobre el evento que ha ocurrido.
Al final del <body> (del archivo /views/upload.hjs) añadiremos el código que asociará el evento "drop" con la función que envia los archivos:


<script>
   var dropzone = document.getElementById("dropzone");
   dropzone.ondragover = dropzone.ondragenter = function(event) {
       event.stopPropagation();
       event.preventDefault();
   }


   dropzone.ondrop = function(event) {
       event.stopPropagation();
       event.preventDefault();


       var filesArray = event.dataTransfer.files;
       for (var i=0; i<filesArray.length; i++) {
           sendFile(filesArray[i]);
       }
   }
</script>


Explicación:


En la primera instrucción recuperamos en la variable dropzone el div que hemos creado con ese identificador.
Después, se anula las acciones por defecto del navegador para los eventos ondragover y ondragenter.
Finaliza asignando una función al evento ondrop que hace dos cosas: primero anula las acciones que podría emprender el navegador, y segundo, con los datos que el navegador pone el parámetro event, extrae la lista de archivos arrastrados (event.dataTransfer.files), y pasa cada uno de estos archivos a la función sendFile(file), que programaremos a continuación, que enviará los archivos al servidor.


- Enviar los archivos al servidor


(a) Si se utiliza el formulario para enviar los archivos, el navegador los enviará cuando el usuario pulse el botón de submit. Habremos indicadeo que el método de envío es POST, y que la ruta donde se tienen que enviar los archivos es también "/upload" (http://localhost:3000/upload).


(b) Si hemos utilizado el D’n’D, tenemos que programar la función sendFile(file), que es la que llamamos desde el evento ondrop para cada archivo que se haya arrastrado. Añadiremos este código en el <script> anterior:


   function sendFile(file) {
       var xhr = new XMLHttpRequest();
       var fd = new FormData();
           
       xhr.open("POST", "/upload", true);
       xhr.onreadystatechange = function() {
           if (xhr.readyState == 4 && xhr.status == 200) {
               // funció executar quan s’hagi completat la pujada
           }
       };
           
       xhr.upload.onprogress = function(e){
           // funció a executar cada cop que es faci un progrés en la pujada
       }
           
       fd.append('myFile', file);
       xhr.send(fd);
   }


Explicación


Primero asignamos a la variable xhr el código que permite realizar peticiones.
Después asignamos a la variable fd el código que sostendrá les datos que queremos enviar.
Abrimos una conexión POST con la ruta /upload. Con el parámetro true indicamos que queremos hacer una subida de archivos asíncrona, o sea, que la ejecución de nuestro script continua mientras se envían los archivos.
En la función que asignamos al evento onreadystatechange de la petición xhr, pondremos el código que se tendrá que ejecutar cuando la subida haya finalizado. (*)
En la función que asignamos al evento onprogress de la subida de la petición xhr.upload, pondremos una función que se ejecutará cada vez que se haga un progreso en la subida del archivo. (**)
Las dos últimas instrucciones son: la que añade el archivo y la que lo envía.


(*) Para la función de "subida finalizada", si el archivos creado es una imagen, podemos poner un código que añada la imagen subida a la página. (Crear un elemento "img", ponerle como "src" la ruta "/upload+nombre_archivo_subido". Añadir el elemento a la página). Algo parecido a esto:


var img = document.createElement("img");
img.src = "/uploads/" + JSON.parse(xhr.response).myFile.name;
document.body.appendChild(img);


(**) Para la de "progreso de subida", pondremos un código para mostrar al usuario el porcentage de subida, que podemos obtener con la fórmula: e.loaded * 100 / e.total


PARTE 2: Programar el servidor para que reciba los archivos subidos y los guarde


Para recibir los archivos que nos envíen usaremos el módulo Multer:


npm install multer


Este módulo lo utilizaremos también en la ruta "/upload" (asociada al script /routes/upload.js), igual que antes, pero esta vez asociada al método POST, que es mediante el cual hemos indicado que se envien los archivos. Añadiremos el requerimiento del módulo y lo asociaremos a la raiz de la ruta "/upload".

var multer = require('multer');


router.post('/',  
 [multer({
   dest: './public/uploads/',
   rename: function (fieldname, filename) {
     return filename+Date.now();
   }
 })],        
 function(req, res, next) {
   res.json(req.files);
 }
);


Se configura la carpeta donde se guardarán los archivos, y una función que añade la fecha al nombre del archivo. 
Como resultado de la subida, se envía una respuesta al navegador con la información del archivos subido (en JSON). Esta info es la usada por la función "subida finalizada" de antes para ponerla como "src" de la nueva imagen (img.src = "/uploads/" + JSON.parse(xhr.response).myFile.name).

Esta sería la funcionalidad básica de Multer. A partir de aquí se tienen que configurar los parámetros de seguridad adecuados (ver https://github.com/expressjs/multer).

Y mil cosas más...