Archivo de la etiqueta: node.js

Node.js

Tutorial: implementar un servicio RESTful public API de modelos Mongoose en Node.js

Hola!, He terminado de hacer un ejemplo de como implementar una API pública tipo RESTful en una APP creada con express.
La idea detrás de esta app de ejemplo es la de crear automagicamente todas las URL necesarias para poder gestionar un modelo de Mongoose (aka recurso).

Su uso es realmente simple. Dentro de la carpeta “models” creas un nuevo modelo/entidad, tocas un par de detalles mas, y tienes una API publica completa de ese nuevo recurso :)

Ya no doy mas vueltas, acá la tienen:

https://github.com/capy/Simple-API-style

Chau!

Node.js

Node.js/express: Gestionar las URL de tu proyecto mas eficientemente.

Normalmente cuando haces una aplicación usando express, por comodidad tiendes a poner todas las definiciones de las url’s en la raíz del proyecto:

var express = require('express');
var app = express();
//...
//Configuraciones varias
//...
app.get("/users", function (req, res) {
    return res.render("list users");
});
app.get("/user/:uid", function (req, res) {
    return res.render("show an user");
});
app.get("/articles", function (req, res) {
    return res.render("list articles");
});
app.get("/article/:id", function (req, res) {
    return res.render("show an article");
});
app.get("/article/new", function (req, res) {
    return res.render("new article");
});
http.createServer(app).listen(app.get('port'), function () {
    console.log("http://localhost:" + app.get('port'));
});

Pero esto a la corta se vuelve algo inmanejable. La solución es adoptar un patrón que nos permita extraer las URL a archivos en los que poder juntar las url que se refieran a algo en común (en este ejemplo usuarios por un lado y articles por otro.).

Manos a la obra

crea un directorio llamado routes, crea los archivos “users.js” y “articles.js” y llévate allí las urls.
En el lugar que estaban las URL (dentro de app.js o server.js, como sea que lo hayas llamado.) pon:

var routePath = __dirname + '/routes/';
fs.readdirSync(routePath).forEach(function (file) {
    require(routePath + file)(app);
});

Este snippet se va a encargar de cargar todos los archivos que haya en la carpeta routes. Si te fijas bien vas a ver que está haciendo un require pero le pasa “(app)” al final de este. Eso es porque dentro de nuestros archivos de rutas vamos a devolver funciones que van a ejecutarse ni bien sean cargadas.

Lo ultimo que nos queda es definir la estructura que debe tener cada archivo que esté alojado en routes. Como ya no están en la raíz, vamos a encapsular estas definiciones de rutas dentro de una función, que es la que se va a encargar de hacernos llegar a app (que es la instancia de express que va a recibir nuestras rutas):

users.js:

module.exports = function (app) {
    app.get("/users", function (req, res) {
        return res.render("list users");
    });
    app.get("/user/:uid", function (req, res) {
        return res.render("show an user");
    });
};

articles.js:

module.exports = function (app) {
    app.get("/articles", function (req, res) {
        return res.render("list articles");
    });
    app.get("/article/:id", function (req, res) {
        return res.render("show an article");
    });
    app.get("/article/new", function (req, res) {
        return res.render("new article");
    });
};

Como podemos ver, en cada uno de los archivos que contienen las rutas, hemos metido las rutas dentro de module.exports = function (app){}. Esto sumado a que a cada archivo cargado se le estaba pasando la app por medio del require (require(routePath + file)(app);) todo cobra sentido.

Ya hemos acabado, ya puedes crear tantos archivos .js como tu corazón y lógica dicten, que se van a cargar sin problemas.

Chau!

Node.js

Desplegar una aplicación Node.js como servicio Upstart (demonio)

Cuando quise desplegar mi app, imagine que iba a ser fácil, que lanzaba el comando “node miapp.js” y punto, pero aprendí a palos que así no va la cosa.

Para desplegar una aplicación web, necesitas que funcione como apache por lo menos: que puedas arrancarla, pararla, reiniciarla, ver su estado, acceder a su log, que se reinicie si se produce un error, etc etc etc.

Busque por Internet y leí un montón de artículos y foros que te dicen como montartelo: haciendo scripts en /etc/init.d y combinándolo con monit para reiniciarlo si se cae, Usando Upstart, lanzando la aplicación con “node miapp.js &” (proceso en background pero es la peor solución que leí en años), y alguna que otra mas.

La mas razonable fue la de Upstart, aunque ninguna de las soluciones fue bastante clara o completa como para copiar, pegar y arrancar. Por eso hice este script que toma en cuenta todo lo que hace falta para montar tu app como un servicio:

description "Demonio para mi aplicaci�n en Node.js"
author "Capy - http://ecapy.com"
env LOG_FILE=/var/log/node/miapp.log
env APP_DIR=/var/node/miapp
env APP=app.js
env PID_NAME=miapp.pid
env USER=www-data
env GROUP=www-data
env POST_START_MESSAGE_TO_LOG="miapp ha sido iniciada."
env NODE_BIN=/usr/local/bin/node
env PID_PATH=/var/opt/node/run
env SERVER_ENV="production"
######################################################
# A partir de aqu� no deber�a hacer falta tocar nada #
######################################################
#Nuestro proceso espera hasta que el sistema este cargado
start on runlevel [2345]
#Y se detiene cuando el sistema se apague
stop on runlevel [016]
#Levanta el proceso autom�ticamente si se muere:
respawn
#limitamos el "respawn" a 99 veces con un timeout de 5s para que no intente levantar el proceso infinitamente.
respawn limit 99 5
pre-start script
    #Necesitamos asegurarnos que el path del pid exista antes de arrancar el proceso.
    mkdir -p $PID_PATH
    mkdir -p /var/log/node
end script
script
	#Seteamos NODE_ENV para que nuestra app se ejecute en modo production, development, test, etc.
	export NODE_ENV=$SERVER_ENV
	#El siguiente comando es el que realmente arranca el proceso:
	#Utiliza el usuario y grupo www-data (--chuid)
	#Asigna un pid y lo crea si no existe (--make-pidfile y --pidfile)
	#Cambia el directorio de ejecuci�n a donde est� nuestra app de node (--chdir)
	#Ejecuta la app (--exec)
	#Env�a cualquier output que genere nuestra app al log (>> $LOG_FILE 2>&1)
    exec start-stop-daemon --start --chuid $USER:$GROUP --make-pidfile --pidfile $PID_PATH/$PID_NAME --chdir $APP_DIR --exec $NODE_BIN -- $APP >> $LOG_FILE 2>&1
end script
post-start script
	echo $POST_START_MESSAGE_TO_LOG >> $LOG_FILE
end script

Puede parecer un poco largo, pero si lo lees detenidamente vas a ver que es bastante concreto:

  1. Autor y descripción: son parte básica de un script Upstart
  2. Variables de configuración para que no tengas que meterte entre el código para ajustar el script para cada nueva app que crees.
  3. Cuerpo del script.

Acá esta el mismo script sin comentarios:

description "Demonio para mi aplicaci�n en Node.js"
author "Capy - http://ecapy.com"
env LOG_FILE=/var/log/node/miapp.log
env APP_DIR=/var/node/miapp
env APP=app.js
env PID_NAME=miapp.pid
env USER=www-data
env GROUP=www-data
env POST_START_MESSAGE_TO_LOG="miapp ha sido iniciada."
env NODE_BIN=/usr/local/bin/node
env PID_PATH=/var/opt/node/run
env SERVER_ENV="production"
######################################################
start on runlevel [2345]
stop on runlevel [016]
respawn
respawn limit 99 5
pre-start script
    mkdir -p $PID_PATH
    mkdir -p /var/log/node
end script
script
    export NODE_ENV=$SERVER_ENV
    exec start-stop-daemon --start --chuid $USER:$GROUP --make-pidfile --pidfile $PID_PATH/$PID_NAME --chdir $APP_DIR --exec $NODE_BIN -- $APP >> $LOG_FILE 2>&1
end script
post-start script
	echo $POST_START_MESSAGE_TO_LOG >> $LOG_FILE
end script

Uso:

Por cada aplicación en nodejs que quieras montar tenes que crear un archivo en /etc/init/. El nombre del archivo tiene que terminar en .conf. por ejemplo miapp-service.conf. Una cosa importante es que el servicio que estas creando se llama como el archivo (sin el .conf), por lo que en nuestro ejemplo se va a llamar miapp-service.

Dentro de /etc/init/miapp-service.conf pegas el script y personalizas todo lo que haya hasta la linea divisoria.

Un par de aclaraciones:
env APP=app.js define el nombre del “index” de tu aplicación, y por lo general se usan o app.js o server.js. Pon el nombre de tu app sin el path hasta ella. Si tu aplicación lleva parámetros podes encerrar en comillas dobles algo asi como “env APP=app.js -extrasettings ../settrings.json”
env APP_DIR=/var/node/miapp el path hasta tu aplicación
env PID_NAME=miapp.pid pon el nombre que quieras, pero que sea único. por ejemplo, si tu app es una pagina web pon el nombre de tu pagina “mipaginaweb_com.pid” o algo así.
env USER=www-data y env GROUP=www-data no sería muy responsable usar root para ejecutar tu aplicación salvo que esta si que necesite estar en root, pero en el caso de que sea una pagina web, usa el usuario y grupo www-data así podes unificar criterios. Es solo una sugerencia, yo para mi app uso www-data aunque podes usar el grupo y usuario que te parezca mejor.
POST_START_MESSAGE_TO_LOG es solo un mensaje que se envía al log de la app cuando esta arranca.
NODE_BIN indica donde esta ubicado node, por lo general está en /usr/local/bin/node aunque si no estuviera allí podes cambiar este parámetro.
env SERVER_ENV=”production” Posiblemente tu aplicación web utilice entornos de “development“, “test” y “production“. Bien, especificalo acá.

El resto del script está comentado en código así que no añado nada.

Cuando hayas acabado ya vas a disponer de tu servicio y tratarlo como cualquier otro:

start miapp-service
stop miapp-service
restart miapp-service
status miapp-service

Espero que les sirva.

Chau!

Node.js

Preprocesar variables de templates en express framework (nodejs)

Bien, el caso de uso es el siguiente: tenemos varios templates en los que queremos mostrar mensajes al usuario si los hubiera.
Lo normal seria hacer algo así a la hora de renderizar un template:

Para la página principal:

app.get('index', function (req, res) {
    var messages = req.flash();
    var render_options = {
        titulo: "P�gina principal"
    };
    if (messages.error || messages.info || messages.success) {
        if (messages.error) messages.hasError = messages.error.length > 0;
        if (messages.info) messages.hasInfo = messages.info.length > 0;
        if (messages.success) messages.hasSuccess = messages.success.length > 0;
        render_options.messages = messages;
    }
    res.render('index', render_options);
});

Para la página del usuario:

app.get('user', function (req, res) {
    var messages = req.flash();
    var render_options = {
        titulo: "Perfil de " + req.session.user.name
    };
    if (messages.error || messages.info || messages.success) {
        if (messages.error) messages.hasError = messages.error.length > 0;
        if (messages.info) messages.hasInfo = messages.info.length > 0;
        if (messages.success) messages.hasSuccess = messages.success.length > 0;
        render_options.messages = messages;
    }
    res.render('index', render_options);
});

Bien, la idea se aprecia, constantemente tenemos que trabajarnos la variable “messages” y si hay mensajes la incluimos en las opciones de renderizado del template.

¿El inconveniente? el código se repite, lo que lo hace poco mantenible y ensucia. Solo por dar un ejemplo, podés ver que estamos preparando errores del tipo “error“, “info” y “success“, y si quisieras agregar un nuevo tipo de error llamado “warning“, te las verías p… negras. Bueno se entiende el inconveniente.

Solución: Encapsular en un preprocesador

Express nos da la posibilidad de preparar variables que van a parar al template haciendo uso de app.use() y res.locals.

NOTA importante: da igual el sistema de templates que uses: Jade, Hogan.js, Mustache, Handlebars, etc. Express es agnóstico en este sentido a la hora de preparar variables. Un puntazo.

Bueno, como decía, podemos agregar un app.use() en app.configure(function () {}); (suele estar en app.js, la base de tu aplicación).
Acá está el ejemplo. Hemos quitado la lógica de tratamiento de mensajes y la hemos colocado en este middleware (si, es uno de esos famosos):

app.use(function (req, res, next) {
    var messages = req.flash();
    if (messages.error || messages.info || messages.success) {
        if (messages.error) messages.hasError = messages.error.length > 0;
        if (messages.info) messages.hasInfo = messages.info.length > 0;
        if (messages.success) messages.hasSuccess = messages.success.length > 0;
        res.locals.messages = messages;
    }
    next();
});

Lo mas notable del código anterior es que hemos asignado el resultado a res.locals.messages. res.locals, va a “mergearse” con las variables que uses en res.render() antes de llamar al template.
NOTA: next(); es necesario para que el flow de ejecución siga adelante.

Hecho lo anterior, les presento las versiones simplificadas de los ejemplos que di al principio:

app.get('index', function (req, res) {
    var render_options = {
        titulo: "P�gina principal"
    };
    res.render('index', render_options);
});
app.get('user', function (req, res) {
    var render_options = {
        titulo: "Perfil de " + req.session.user.name
    };
    res.render('index', render_options);
});

Mucho mejor ¿no?

Chau!

Node.js

req.flash() en express 3

A partir de la versión 3 de http://expressjs.com/ framework req.flash() fue quitado del core ya que estrictamente hablando, ésta funcionalidad no es “core”.

No pasa nada, seguimos teniendo los mensajes flash pero en un modulo aparte:

Para instalarlo:

npm install connect-flash

O bien lo agregas a tu proyecto expressjs dentro de las dependencias y lo actualizas con npm:

{
    "name": "application-name",
    "version": "0.0.1",
    "scripts": {
        "start": "node app"
    },
    "dependencies": {
        "express": "3.1.0",
        "connect-flash": "*"
    }
}
path/a/tu/app$: npm update

A partir de allí, su uso sigue siendo el mismo que en versiones anteriores:

Cargamos el modulo:

app.configure(function () {
    app.use(express.session());
    //flash(); SIEMPRE VA ENTRE express.session() Y app.router
    app.use(flash());
    app.use(app.router);
});

Y ya lo podemos usar. Por ejemplo para indicar a nuestro usuario que ha puesto mal sus datos de login:

app.post('/user/login', function(req, res){
  req.flash('error', 'Tu usuario o contrase�a est�n mal!')
  res.redirect('/user/login');
});
app.get('/user/login', function(req, res){
 res.render('index', { messages: req.flash('info') });
});
Node.js

Hacer proxy con Apache para aplicaciones node.js

Tenemos una app desarrollada en nodejs que corre en el puerto 4000 de nuestro servidor y queremos exponerla por el puerto 80 para que la gente pueda usarla. El problema obvio es que por lo general tenemos corriendo un apache, nginx o cualquier otro servidor http.

La solución es usar los módulos de proxy que incluyen cada uno de estos servidores web. Hoy puntualmente lo vamos a hacer con Apache.

Habilitamos los modulos proxy y proxy_http para permitir el reenvío de peticiones HTTP y HTTPS:

sudo a2enmod proxy
sudo a2enmod proxy_http

Luego nos queda crear un vhost:

sudo nano /etc/apache2/sites-available/mipaginaweb.com

Y ponerle lo siguiente:

<virtualhost *:80>
    ServerName mipaginaweb.com
    ServerAlias www.mipaginaweb.com
    ProxyRequests off
    <proxy *>
        Order deny,allow
        Allow from all
    </proxy>
    <location "/">
        ProxyPass http://localhost:4000/ retry=0
        ProxyPassReverse http://localhost:4000/
    </location>
</virtualhost>

Habilitamos nuestro nuevo vhost:

sudo a2ensite mipaginaweb.com

Hemos terminado. reiniciamos apache y a correr!

sudo service apache2 restart

Bonus: si estas en un entorno de desarrollo y quieres acceder a mipaginaweb.com, edita tu archivo hosts (sudo nano /etc/hosts) y añade tu nuevo dominio:

127.0.0.1 localhost
127.0.0.1 mipaginaweb.com www.mipaginaweb.com
Node.js

Instalar node.js y npm de forma automatizada

Es increíble lo fácil que nos lo llegan a poner unas pocas lineas de código…

Copien estas lineas a un archivo de texto y guárdenlo con el nombre que quieran y con extensión .sh

echo 'export PATH=$HOME/.local/bin:$PATH' >> ~/.bashrc
. ~/.bashrc
mkdir ~/node-latest-install
cd ~/node-latest-install
curl http://nodejs.org/dist/node-latest.tar.gz | tar xz --strip-components=1
./configure --prefix=~/.local
make install

Desde la linea de comandos le damos permisos de escritura y lo ejecutamos:

chmod 777 instaladorNodeJs.sh
./instalador.sh

Listo.