TodoList con Node.js y MongoDB - Parte I

4 minuto de lectura

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):

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

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).

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

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.

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.

Comentar