Deno: ¿Una alternativa real a Node.js?

Si te mueves en el mundo de JavaScript, seguro que Node.js es tu pan de cada día. Pero, ¿has oído hablar de Deno? Y no, no es solo “Node” al revés (aunque el guiño es genial). Deno es un runtime para JavaScript y TypeScript creado por el mismísimo Ryan Dahl, el padre de Node.js.

Imagínate que creas algo increíble, pero con el tiempo te das cuenta de que podrías haberlo hecho mejor. Eso es Deno para Ryan Dahl. Es su oportunidad de redimirse y arreglar lo que él considera los “errores de diseño” de Node.js. El resultado es un entorno de ejecución más moderno, seguro y con una experiencia de desarrollo que te va a sorprender.

¿Qué hace a Deno tan especial?

¡A programar desde el minuto cero!

Una de las cosas que más me gustan de Deno es que viene con todo lo que necesitas para empezar a trabajar. ¿Cansado de configurar ESLint, Prettier, Jest y un montón de dependencias de desarrollo cada vez que empiezas un proyecto?

Deno incluye out of the box:

  • Un formateador de código (deno fmt): Para que tu código siempre luzca impecable y consistente.
  • Un linter (deno lint): Para detectar problemas y mantener la calidad del código.
  • Un runner de tests (deno test): Con soporte para tests unitarios y de integración, al estilo BDD (como Jest) o al estilo de Deno con Deno.test.
  • Soporte nativo para TypeScript: Sin necesidad de instalar tsc ni configurar tsconfig.json. Escribes en TypeScript y Deno se encarga del resto.
  • Un repositorio de librerías (JSR), Deno tiene su propio ecosistema de dependencias llamado JSR. Pero en las últimas versiones ahora soporta librerías del ecosistema de NPM.

Esto significa menos tiempo configurando y más tiempo programando. ¡Productividad al máximo desde el primer momento! Toda la configuración del entorno queda en el archivo deno.json y de esta forma es fácil de transportar, lo subes al repositorio y todo tu equipo lo usa (y lo va mejorando).

Seguridad: Tú tienes el control

Otro de los pilares de Deno es la seguridad. A diferencia de Node.js, donde un script puede acceder a tu sistema de archivos, a la red o a las variables de entorno sin que te des cuenta, Deno ejecuta el código en un sandbox.

Por defecto, un script de Deno no tiene permisos para hacer nada “peligroso”. Si necesita acceder a la red, al sistema de archivos o a cualquier otra cosa, tienes que dárselo explícitamente con flags al ejecutar el comando:

1
2
3
4
5
6
7
8
# Permite leer del sistema de archivos
deno run --allow-read mi_script.ts

# Permite conexiones de red
deno run --allow-net mi_script.ts

# ¡Permite todo! (Úsalo con cuidado)
deno run -A mi_script.ts

Este modelo de permisos explícitos te da un control total sobre lo que tus dependencias pueden y no pueden hacer, evitando sorpresas desagradables.

Gestión de dependencias

En Deno, no existe la carpeta node_modules ni el archivo package.json. Las dependencias se importan directamente desde una URL, como en el navegador:

1
import { serve } from "https://deno.land/std@0.140.0/http/server.ts";

La forma de hacer los imports con las urls en los archivos TS, a mi modo de ver, es poco mantenible. Se hicieron varios esfuerzos para que sea una vía simple de mantener, pero al parecer llegaron a algo como un administrador de dependencias centralizado en el archivo deno.json. En las últimas versiones, además, agregaron soporte para incluir dependencias del ecosistema de NPM, de esta forma, se amplió la cantidad de librerías que puedes usar.

Si vas a usar dependencias de NPM, a veces se genera un archivo package.json muy básico con el propósito de agregar dependencias solamente.

Deno descarga y cachea las dependencias la primera vez que se ejecuta el script. Si quieres asegurarte de que las dependencias no cambien, puedes usar un archivo deno.json (o deno.jsonc) para gestionar las versiones, de forma similar a como lo harías con un package.json, pero mucho más simple.

Un ejemplo rápido

La mejor forma de entenderlo es probándolo. ¡Vamos a crear un pequeño proyecto!

1. Instalación

Deno está disponible para la mayoría de los sistemas operativos. Pásate por la página de instalación, busca tu SO y sigue los pasos.

2. Inicializando el proyecto

Una vez instalado, crear un proyecto es tan fácil como esto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ mkdir mi-proyecto-deno && cd mi-proyecto-deno
$ deno init

✅ Project initialized

Run these commands to get started

# Run the program
deno run main.ts

# Run the program and watch for file changes
deno task dev

# Run the tests
deno test

¡Y ya está! Deno te crea tres archivos:

  • deno.json: El corazón de tu proyecto, similar al package.json.
  • main.ts: Tu punto de entrada.
  • main_test.ts: Un archivo de ejemplo para tus tests.

3. El código

El archivo main.ts que se genera es súper simple:

1
2
3
4
5
6
7
8
export function add(a: number, b: number): number {
return a + b;
}

// Learn more at https://docs.deno.com/runtime/manual/examples/module_metadata#concepts
if (import.meta.main) {
console.log("2 + 3 =", add(2, 3));
}

Puedes ejecutarlo con deno run main.ts y verás en la consola:

1
2 + 3 = 5

4. Los tests

Y el archivo de test, main_test.ts, no se queda atrás en simplicidad:

1
2
3
4
5
6
import { assertEquals } from "@std/assert";
import { add } from "./main.ts";

Deno.test(function addTest() {
assertEquals(add(2, 3), 5);
});

Ejecútalo con deno test y verás que todo funciona como la seda:

1
2
3
4
5
Check file:///Users/pcollaog/mi-proyecto-deno/main_test.ts
running 1 test from ./main_test.ts
addTest ... ok (0ms)

ok | 1 passed | 0 failed (3ms)

Conclusión

Como ves, Deno no es solo un capricho. Es una propuesta seria que replantea el desarrollo del lado del servidor con JavaScript/TypeScript, poniendo el foco en la seguridad, la simplicidad y una experiencia de desarrollo moderna y sin fricciones.

Si vienes de Node.js, la curva de aprendizaje es mínima y las ventajas son enormes. Menos configuración, más seguridad y un conjunto de herramientas unificado que te hará la vida más fácil.

¿Mi recomendación? Dale una oportunidad. Puede que te sorprendas y encuentres a tu nuevo mejor amigo para tus proyectos de backend.

¿Y tú, ya has probado Deno? ¡Cuéntame tu experiencia en los comentarios!

En un próximo artículo, profundizaré en mi experiencia con los frameworks web más populares para Deno.


Referencias

AWS Lambda - ESM Node.js

Algunas generalidades previas

En la mayoría de los paradigmas de programación, podemos encontrar sistemas que nos permiten auto organizar el código en pequeñas piezas o para incorporar librerías/bibliotecas de terceros a nuestro código. Al combinar todo lo anterior resulta una pieza de código más grande y compleja.

Desde un inicio Javascript utilizó el sistema de carga de módulos llamado CommonsJS (CJS) y es parte integral de Node.js hasta la version v8.5.0 donde se incorpora un nuevo sistema de carga de módulos, ESM. A partir de la version v13.2.0 de Node.js fue estabilizado e incorporado como un nuevo estándar.

¿ Por qué deberíamos usar ESM en AWS Lambdas ?

James Beswick (Principal Developer Advocate for the AWS Serverless Team) escribió un artículo titulado Using Node.js ES modules and top-level await in AWS Lambda ,donde detalla el por qué y en qué casos deberías usar ESM como cargador de módulos de Javascript en el contexto de un AWS Lambda. Uno de los motivos más importantes de usar ESM, es que la carga en frío de un lambda tarda casi un tercio en comparación con CommonsJS.

Dejo aquí la comparativa entre CJS y ESM donde en el p99 (carga en frío) la partida se reduce a un tercio mejorando el rendimiento en un 43,5%. Para esta prueba, todas las métricas de ESM salieron por debajo (mejores en tiempo) de las de CJS es simplemente marginal (alrededor de 2-5ms).

¿ Cómo usamos ESM en AWS Lambdas ?

Configuración del artefacto - package.json

Primero que todo se deben ajustar un par de atributos en el archivo package.json para indicar que el módulo es del tipo ESM.

package.json
1
2
3
4
5
6
7
8
9
{
"name": "serverless-lambda-esm",
"version": "1.0.0-beta",
"description": "Serverless Lambda ESM",
"main": "src/Handler.js", // <-- Sera cargado como módulo
"license": "UNLICENSED",
"private": true,
"type": "module", // <-- Este atributo debe tener el valor: module
}

Con este ajuste le indicamos al cargador que trate los archivos como ES módulos según su extensión, es decir, con el atributo type y valor en module todos los archivos con extensión .js serán tratados como ESM. Si por alguna razón quieres mezclar ambos mundos debes hacerlo de forma explícita usando como extensión .cjs. Por el contrario, si no utilizas el atributo type o lo dejas con valor commonjs, todos los archivos con extension .js serán tratados como CJS y si quieres utilizar ESM estos deben tener extension .mjs.

Preparamos el Handler

Handler.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { DateTime } from "luxon";

/**
*
* @param {*} event
* @returns
*/
export const handler = async (event, context) => {
const date = DateTime.now().toFormat("yyyy-MM-dd");
console.log("Date: ", date);
console.log(JSON.stringify(event));

return {
statusCode: 200,
body: JSON.stringify({ message: "ok" })
};
};

El handler debe estar expuesto de esa forma para que sea cargado como ESM. Se puede apreciar que ya no usamos la sentencia require para cargar una dependencia externa (en esta caso luxon) y en su reemplazo utilizamos import.

Algunos detallitos que he ido aprendiendo en el camino y que no está muy explícito en la documentación, es que los imports de nuestros archivos, es decir, el código que está dentro del proyecto, deben ser cargados y nombrados con su extensión, dejo un ejemplo:

Handler.js
1
2
import { DateTime } from "luxon"; // <-- Sin la extension (lib)
import Service from './service/Service.js'; // <-- Con la extension

Export de los módulos

Service.js
1
2
3
4
5
6
7
8
9
10
11

const Service = {
/**
* Funcion
*/
doSomething: async(event)=>{
console.log("Event: ", JSON.stringify(event));
}
};

export default Service; // <-- Así se exponen los módulos al código

Palabras al cierre

Espero les sea útil para sus desarrollos de lambdas con Node.js. Más adelante ire dejando nuevos artículos con ejemplos e ideas para implementar con AWS Lambdas. Pase y deje su comentario.

Enlaces de interés

Escuchando Subterranean Homesick Alien del disco OK Computer

Radiohead

Nuevo Comienzo

De vuelta

Hola, se que no he escrito en mucho tiempo, muchísimo tiempo.

Partiré diciendo que me cambie de sistema de blog desde Jekyll (ruby… puaj) a Hexo (nodejs) solo por salir de una tecnología que simplemente no va conmigo y tampoco le he dado el suficiente tiempo para entenderlo. Hexo es una plataforma para generar contenido estático (blog) a partir de archivos markdown, está desarrollado sobe NodeJS y todos sus componentes (plugins y themes) siguen la misma línea. Debo decir que hay muchos componentes que están bien des-actualizados y muchos de ellos con documentación en algo que parece ser Chino.

Ha pasado muchas cosas desde la última vez que escribí, tuvimos un estallido social en Octubre del 2019 y luego nos remeció una pandemia que nos estuvo encerrados un buen tiempo. De alguna forma ambos eventos forzaron al trabajo remoto y muchas empresas nos vimos obligados a adaptarnos a la nueva forma de trabajar y relacionarnos entre los diferentes equipos.

En Pandemia

En pandemia no había mucho que hacer (fuera de la casa) así que aplicamos creatividad y aprendí a hacer Pan Amasado y pan con Masa Madre (al igual que muchos). Esto debido a que en esos días salir a comprar el pan era de riesgo vital.

En mitad de la pandemia nos cambiamos de casa apenas se dió la oportunidad (los permisos de mudanza estaban cerrados). Ya instalados en el nuevo HQ comenzó la nueva aventura de colegio virtual. No fue fácil y menos para la Vale, no generó un vínculo con sus profesores a través de una pantalla. Por otro lado la Javi se adaptó de una forma increíble a los quehaceres del colegio.

Afortunadamente para nuestro rubro, la pandemia aceleró muchos procesos informáticos y como mencioné mas arriba, presionó para que el trabajo remoto se consolidara como LA nueva forma de trabajo. Muchas compañías necesariamente tuvieron que crear nuevos sistemas y aplicaciones para atender a sus clientes o darles la posibilidad de no ir a hacer un tramite presencial (no se podía), lo que de alguna forma benefició a muchas empresas de desarrollo.

Estando encerrado aprendí cosas muy básicas sobre mantenimiento de guitarras (no quiero decir lutheria porque estoy a años luz de ese conocimiento) para justamente echarles una “manito de gato” a mis guitarras. Fue tanto el entusiasmo que me armé una guitarra que compre en Amazon, un kit DIY Do it yourself estilo PRS.

Por supuesto que no fue fácil y creo que las dos etapas críticas fueron pegar el mástil al cuerpo y la “pintura”, en ese orden de complejidad. Digo “pintura” pero en realidad es una técnica de teñido de la madera con tinturas basadas en anilina y agua. Les dejo parte del proceso:

Ahora esa guitarra es una de mis favoritas por varias razones, tiene un sonido único como hecha para tocar metal, es cómoda y tiene un sustain exagerado. Por lo demás es escala 25” con 24 trastes lo que la hace distinta de las demás guitarras. Cosas que le faltan, aparte de un buen guitarrista XD, mejorar la entonación y cambiar el puente porque lo rompí y no hay repuesto. Afortunadamente la cuerda mas difícil de todas la G quedó perfectamente entonada para el calibre de cuerdas que usa esa guitarra.

Eso es todo por hoy… iré agregando mas contenido a mi blog que dicho sea de paso tiene 19 años (ni yo me lo creo).

Recuento del 2013

El año que recién pasó ha sido uno de los más complicados que me ha tocado en todo ámbito.

Familia

A principios de año nos dimos unas merecidas vacaciones en familia con destino La Serena, disfrutamos de unos lindos y temperados días. La Javi conoció la piscina y se largo a nadar (con alitas) sola.

Un hecho que nos pilló total y absolutamente de sorpresa fue la muerte de la mamá de la @guzyy Maruca, estábamos planificando un viaje para que viniera a conocer a la Valentina cuando naciera en un par de meses más. Fue un mes duro para la familia.

A mediados de año nuestra segunda hija, Valentina que llegó para revolucionar nuestras vidas. Si no sabe por qué, lea este post Mi valentina. Hemos aprendido (con la @guzyy y @javicollaov) a llevar este tema adelante y preparándonos para lo que se nos viene por delante, tratando de anticipar y planificar al corto plazo.
Mientras tanto, disfrutar al máximo a nuestra hija Valentina con cada logro, por muy pequeño que sea, desde sonreír hasta tomar algún objeto con su mano o tratar de sentarse sola.

La Javiera por su parte, ha aprendido muchas cosas y le interesan muchas otras que ni si quiera se pueden imaginar, como por ejemplo, los planetas, las estrellas o por qué la sangre es roja o qué son los anticuerpos. Está en la etapa en que todo se transforma en un:

…¿y po qué?

@javicollaov

Ya que la Javi tiene gustos un tanto “científicos”, la lleve a conocer, en un tour de fin de año, El Planetario (que esta de pelos),el Museo Ferroviario de Santiago, El Museo Nacional de Historia Natural, el Museo de Ciencia y Tecnología. Aprendió sobre los planetas, estrellas y galaxias, además fuimos a conocer la ballena (que la encontró fome porque eran puros “huesitos”) y fuimos a experimentar con la luz, el sonido y la física.

La Javi acompañó a la @guzyy durante unos meses en el post-natal ayudando con su propio estilo de hacer las cosas. Aprendió a escuchar música rock y su grupo favorito es “el de los martillos” por Pink Floyd y The Wall. Fanática de 31 Minutos y no nos perdimos el show que dieron con Radio Guaripolo donde pudimos disfrutar (yo más que ella seguramente) de Fredy Turbina y la canción Mi Equilibrio Espiritual.

El Trabajo

En el trabajo, me tocó compartir con un selecto grupo de egipcios a los que estimo mucho, vaya para ellos un abrazo (@rodchile, @lecaros, @solemoris, Camilo no más, la Caroline, Don Richie,el responsivo @rosoristico, @perrefe, @pabloc6, ordenados en orden de desaparición XD). Con ellos compartimos la creación de un nuevo producto, compartimos muchas reuniones donde definimos desde nuestro punto de vista, que era lo mejor para dicho producto, desde lo mas geek/nerd técnico hasta los colores y estilos del sitio. Vivimos discusiones épicas y desayunos con choriqueques. Lamento personalmente que algunos de ellos ya no estén en nuestro equipo de trabajo, por diversas razones. En este equipo además aprendimos a querer a nuestro amigo hubot quien todos los días nos daba ánimos para poder seguir trabajando.

Palabras al cierre

Sin duda me quedaron muchas cosas que nos pasaron este año que no puse aquí, pero no quiero dejar la oportunidad de dar a conocer.

Fui testigo de matrimonio de @rodchile con @sarahfriedland y ahora forman una linda y koalesca familia.

Con la @guzyy conocimos la Fundación Ser de la que estamos muy agradecidos, hemos aprendido mucho sobre el mundo de los niños con necesidades especiales. Conocimos otros papás que enfrentan los mismos desafíos que nosotros y con los que hemos podido compartir ideas, consejos y la sonrisa de sus hijos.

Cosas irrelevantes que sólo a un geek le importan

Si no es un geek, por favor no continúe leyendo, le advierto que no entenderá un carajo de lo que voy a escribir. Ahora bien si lo hace… después no se queje XD

  • Comencé a programar en node.js y angular
  • Migré mi blog de WordPress a Octopress
  • Me cambie de shell a zsh + oh-my-zsh
  • Sigo odiando las bases de dato (ahora mas que nunca)
  • Me gustan cada día más noSQL (mongoDB y redis)
  • Confirmo que sigo odiando ruby y sus derivados (aunque los uso y les he dado varias oportunidades)
  • Comencé a usar intensivamente Sublime Text (hasta he escrito plugins) y pague por su licencia… kudos para ellos!
  • Las nuevas APIs de Spring Framework singuen sorprendiéndome, especialmente Spring Data, Spring Boot y Spring XD.
  • Entendí la mitad de Spring Security tratando de implementar algo que no estaba soportado en la API.
  • Maven es mi copiloto
  • Si se trata de optimizar aplicaciones Java aún no alcanzo el nivel Dios del 1800 % (#elquesaesae)
  • Tengo un cluster de Raspberry Pi para aplicaciones web y una la uso para mediacenter con Xbian (xbmc)
  • Use por primera vez nginx para servir mi blog y estoy pensando seriamente en migrar de apache2.
  • open-iscsi realmente la llea!
  • Volví a instalar cyanogenmod y como siempre en la versión inestable.
  • Le tomé cariño a Jira y Confluence. (aunque sean propietarios)
  • Un hubot bien configurado es la mano para insultos motivacionales.

Que tengan todos un Feliz Año 2014 y a echarle pa adelante (que pa atrás no cunde).

TodoList Con Node.js Y MongoDB - Parte II

Lo prometido es deuda, aquí vamos con la segunda parte donde integraremos Passport para poder validar los request y contronlar sesiones de usuarios.

Primero debemos agregar los siguientes imports al archivo app.js y la estrategia (plugin de autenticación) a utilizar. Además se agregan los archivos UserModel.js y security.js extensión del modelo de User y métodos de validación del request respectivamente:

1
2
3
4
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var security = require('./config/security');
var User = require('./model/UserModel');

Ahora configuramos Express para que pueda utilizar Passport como administrador de sesiones de usuario. Es necesario que express use su propio administrador de sesiones y además debe ser inicializado antes que el de passport.

1
2
3
4
5
6
app.use(express.cookieParser('your secret here'));
app.use(express.session());

// passport initialize
app.use(passport.initialize());
app.use(passport.session());

Luego agregamos mecanismos de autenticación, serialización y deserialización a passport:

1
2
3
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());

Model

Ahora extendemos el modelo de usuario para agregar nuestros atributos (si es es necesario, por ahora lo dejaremos vacío):

1
2
3
4
5
6
7
8
9
10
var mongoose = require('mongoose');
var passportLocalMongoose = require('passport-local-mongoose');

var UserSchema = new mongoose.Schema({});

UserSchema.plugin(passportLocalMongoose);

var UserModel = mongoose.model('User', UserSchema);

module.exports = UserModel;

Seguridad

Hay un archivo que se llama security.js que contiene una función que valida si el request solicitado está autenticado por passport, si está autenticado lo “deja pasar” (next), de lo contrario lo redirecciona al template de login.

1
2
3
4
5
6
exports.ensureAuthenticated = function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
res.redirect('/login');
};

Nos queda asegurar las urls o recursos que necesitemos con la función ensureAuthenticated en el archivo app.js. La idea es que sea lo menos intrusivo posible. Hay dos formas de hacerlo, la primera es intrusiva (y no recomendada aunque puede utilizarse con fines específicos) y se debe realizar por cada request en los archivos que contienen los Controllers:

1
2
3
4
5
6
7
exports.authenticationExample = function(req, res) {
if (req.isAuthenticated()) {
// Do something
} else {
res.redirect('/login');
}
}

La segunda es interceptando los request y validando según algún patrón, que es nuestro caso. Esta configuración esta en app.js donde se declaran las rutas a los Controllers:

1
2
app.all('/api/*', security.ensureAuthenticated);
app.all('/todos', security.ensureAuthenticated);

Con esto interceptamos todos los request que van a la API y los que van a /todos y los hacemos pasar por la función de autenticación de request. Con esto evitamos poner dichas líneas de autenticación en cada controller.

Login y Logout

Finalmente nos queda la última parte respecto de la autenticación y es configurar el login y el logout. Para el login usamos la API de passport y delegamos el trabajo de autenticación de la siguiente forma (configuración):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Funcion sin autenticación que permite desplegar el template de Login
*/
app.get('/login', function(req, res) {
res.render('login', {});
});

/**
* Función de login que permite delegar la autenticacion en la API de
* passport, se configura además cuando la autenticación es exitosa a que url
* lo debe redireccinar y lo mismo en caso de error.
*/
app.post('/login', passport.authenticate('local', {
successRedirect: '/todos',
failureRedirect: '/login'
}));

El logout es más sencillo y lo único que hay que hacer es invalidar el request, destruir la sesión del usuario y redireccionar al login.

1
2
3
4
app.get('/logout', function(req, res) {
req.logout();
res.redirect('/login');
});

Me queda pendiente en la próxima parte del tutorial explicar cómo consumir estos servicios usando angular y desplegar los templates.

TodoList con Node.js y MongoDB - Parte I

Llevo un tiempo estudiando y haciendo algunas cosas con Node.js y me he llevado una grata impresión con esta plataforma. Al principio cuesta un poco entender el cómo funciona ya que el paradigma es muy distinto del que he venido usando durante este último tiempo.

Me animé y empece a usar Node.js para hacer algo simple y así tratar de entender el cómo funciona. Para hacer algo mas sabrosa la experiencia agregué algunos ingredientes adicionales, un framework para desarrollo web llamado Express, Mongoose para modelar los documentos que van a ir a parar a MongoDB, Passport para autenticar los request (control de usuarios y permisos), uso de templates con EJS y finalmente para la vista usamos el framework AngularJS. Como podrán ver es un stack de tecnologías basadas en Javascript.

Para iniciar voy a explicar algunas partes del código que personalmente me costó entender e implementar. Algunos de esos puntos fueron la integración con passport para la autenticación de los request, control de usuario y uso de datos en sesión.

Configuración de node.js

Cuando utilizamos nodejs siempre hay un archivo principal donde se configura toda la plataforma, en este caso el archivo es llamado app.js. Voy a ir mostrando ciertas partes del código (si quieren ver el código, esta disponible en github):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var express = require('express');

var indexController = require('./routes/IndexController');
var todosController = require('./routes/TodosController');
var registerController = require('./routes/RegisterController');

var http = require('http');
var path = require('path');
var app = express();
var db = require('./config/database');


var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;

var security = require('./config/security');
var User = require('./model/UserModel');

Controllers

Lo primero es incluir el módulo de express y luego los Controllers de la aplicación. Veamos el código de IndexController

1
2
3
4
5
exports.index = function(req, res) {
res.render('todos', {
user: req.user
});
};

Este es el Controller mas simple de la aplicación, lo único que hace es exponer mediante exports la función index cuya responsabilidad es desplegar la página principal de la aplicación. Lo demás está en el controlador de la API que será llamada desde dicha página (usando ajax).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
var Todo = require('../model/TodosModel');

/**
* Busca todos los todos para el usuario registrado
* @param {Object} req request
* @param {Object} res response
*/
function findAllTodosByUser(req, res) {
var userId = req.user.id;

Todo.find()
.where('creator')
.equals(userId)
.sort('date')
.exec(function(err, todos) {
if (err) {
res.send(err);
}
res.json(todos);
});
}

exports.allTodos = findAllTodosByUser;

/**
* Crea un todo para el usuario
* @param {Object} req request
* @param {Object} res response
* @return {Object} Lista de todos
*/
exports.createTodo = function(req, res) {
var userId = req.user.id;
var textTodo = req.body.text;

Todo.create({
text: textTodo,
done: false,
creator: userId
}, function(error, todo) {
if (error) {
res.send(error);
}
findAllTodosByUser(req, res);
});
}

/**
* Elimina un Todo por su ID
* @param {Object} req request
* @param {Object} res response
* @return {Object} Lista de todos
*/
exports.deleteTodo = function(req, res) {
Todo.remove({
_id: req.params.todo_id
}, function(error, todo) {
if (error) {
res.send(error);
}
findAllTodosByUser(req, res);
});
}

Este controlador expone la API REST con la que se comunicará la pagina principal, haciendo llamadas ajax a los distintos métodos. En esta pieza se hace un require de otro script que representa el Model, en este caso es un document para MongoDB.

Model con mongoose

Aquí mostraré como se modeló el documento principal de la aplicación Todo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var mongoose = require('mongoose');

var TodoSchema = new mongoose.Schema({
text: String,
date: {
type: Date,
default: Date.now
},
done: {
type: Boolean,
default: false
},
creator: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
});

var TodoModel = mongoose.model('Todo', TodoSchema);

module.exports = TodoModel;

Primero se incluye el módulo de mongoose que nos permitirá modelar los documentos para MongoDB. Luego se crea un schema para modelar y se le agregan los atributos que va a poseer y sus respectivas validaciones. Por ejemplo, el atributo date es de tipo Date y valor por omisión la fecha de hoy.

Además este schema contiene una referencia a otra Collection para dejar relacionado el usuario creador en sus Todos.

Route y manejo de URLs

Finalmente para que la aplicación funcioné, hay que indicarle a node.js que las urls que se soliciten hay que enviarlas a alguien que las atienda, en este caso, los controladores y sus funciones.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
app.get('/login', function(req, res) {
res.render('login', {});
});

app.post('/login', passport.authenticate('local', {
successRedirect: '/todos',
failureRedirect: '/login'
}));

app.get('/logout', function(req, res) {
req.logout();
res.redirect('/login');
});

app.get('/', function(req, res) {
res.redirect('/login')
})

app.get('/register', registerController.index);
app.post('/register', registerController.registerUser);

app.all('/api/*', security.ensureAuthenticated);
app.all('/todos', security.ensureAuthenticated);

app.get('/todos', indexController.index);
app.get('/api/todos', todosController.allTodos);
app.post('/api/todos', todosController.createTodo);
app.delete('/api/todos/:todo_id', todosController.deleteTodo);

No hay mucho que explicar, a buen programador pocas lineas de código, las urls que ahi aparecen son ruteadas a los controladores o se implementa el callback inline, como por ejemplo las urls de login y logout. Los controller que digan security o passport, serán materia del próximo post.

Quedan algunos temas sin tocar en este capítulo, falta que revisemos la integración con AngularJS, el despliegue de los templates con EJS y la integración con passport para autenticar y autorizar los request.

Si tienen preguntas, bienvenidas sean.

Instalación de Node.js con NVM

Instalar NVM

Primero instalaremos las herramientas que nos permitan administrar distintas versiones de nodejs. Una muy buena alternativa es instalar nvm (node version manager) algo siminar a rvm (ruby version manager).

1
$ curl https://raw.github.com/creationix/nvm/master/install.sh | sh

Este script instala nvm en el directorio $HOME/.nvm , luego se deben asegurar que los scripts de nvm se inicien con el terminal o shell que usen. En mi caso uso zsh y debo agregar la siguiente linea al final del archivo .zshrc (si usas bash debes agregarla en el archivo .bashrc):

1
[[ -s $HOME/.nvm/nvm.sh ]] && . $HOME/.nvm/nvm.sh # This loads NVM

Luego reiniciar la consola o cargar tu archivo de inicio:

1
$ source ~/.zshrc 

ó

1
$ source ~/.bashrc

Comprobar la instalación de nvm

Ejecutamos los siguientes comandos para comprobar que nvm funciona correctamente.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ nvm

Node Version Manager

Usage:
nvm help Show this message
nvm install [-s] <version> Download and install a <version>
nvm uninstall <version> Uninstall a version
nvm use <version> Modify PATH to use <version>
nvm run <version> [<args>] Run <version> with <args> as arguments
nvm ls List installed versions
nvm ls <version> List versions matching a given description
nvm ls-remote List remote versions available for install
nvm deactivate Undo effects of NVM on current shell
nvm alias [<pattern>] Show all aliases beginning with <pattern>
nvm alias <name> <version> Set an alias named <name> pointing to <version>
nvm unalias <name> Deletes the alias named <name>
nvm copy-packages <version> Install global NPM packages contained in <version> to current version

Example:
nvm install v0.4.12 Install a specific version number
nvm use 0.2 Use the latest available 0.2.x release
nvm run 0.4.12 myApp.js Run myApp.js using node v0.4.12
nvm alias default 0.4 Auto use the latest installed v0.4.x version

Instalar node.js

1
2
3
4
5
6
7
8
9
10
11
12
$ nvm ls-remote

v0.11.0
v0.11.1
v0.11.2
v0.11.3
v0.11.4
v0.11.5
v0.11.6
v0.11.7
v0.11.8
v0.11.9

##Instalar la versión v0.11.8 de node.js

1
$ nvm install v0.11.8

##Comprobar la instalación de node.js

1
2
3
$ node --version

v0.11.8