Permalink

7

Guia express & mongoose para Node.js – Parte II #nodejs

En la Parte I de este tutorial llegamos a manejar datos de nuestro repositorio local.

Sin embargo, estos datos son dummy. Liberemos el poder de la base de datos MongoDB, recordemos que introdujimos este documento:

> db.productos.findOne()
{
  "_id" : ObjectId("4fe6454a8c136cf49721359f"),
  "nombre" : "Papas Fritas",
  "descripcion" : "Crujientes, sabor mediterraneo",
  "precio" : 2.5
}

¿Cómo conectamos nuestra base de datos MongoDB usando node.js? Usando la librería mongoose.

El momento de la base de datos llegó, recordemos que al principio creamos el archivo models/producto.js:

  • models/producto.js
var Schema = require('mongoose').Schema

var producto_schema = new Schema({
  nombre        :   String,
  descripcion   :   String,
  precio        :   Number
})

var Producto = module.exports = producto_schema

… Y, Modifiquemos nuestros archivos de la siguiente manera:

  • package.json
{
    "name": "herman-mongoose-suggestion"
  , "version": "1.0.0"
  , "dependencies": {
      "express"     : "2.5.8"
    , "jade"        : "0.25.x"
    , "mongoose"    : "2.5.10"
  }
}

Una vez modificado package.json, no olvidemos de actualizar nuestro modulos de node via npm install -f en el directorio de nuestra aplicación.

  • controllers/producto.js
// Creación de la Conexión
var mongoose        = require('mongoose')
  , db_lnk          = 'mongodb://localhost/supermercado'
  , db              = mongoose.createConnection(db_lnk)

// Creación de variables para cargar el modelo
var producto_schema = require('../models/producto')
  , Producto = db.model('Producto', producto_schema)

Ahora, existe, por supuesto la posibilidad de montar de una manera general la conexión para toda la aplicación. No la tocaremos sin embargo en este tutorial.

Modificamos la función exports.index, siempre dentro de controllers/producto.jspara que recoja los productos:

  • controllers/producto.js
exports.index = function (req, res, next) {

  Producto.find(gotProducts)

  // NOTA: Creo que es bueno usar verbos en inglés para las funciones,
  //       por lo cómodo que son en estos casos (get, got; find, found)
  function gotProducts (err, productos) {
    if (err) {
      console.log(err)
      return next()
    }

    return res.render('index', {title: 'Lista de Productos', productos: productos})
  }
}

Nótese la estructura de callbacks: Cuando se pide la lista de productos, sólo al llegar la respuesta invocamos a gotProducts, el cual llama a la renderización a través de res.render de la página. Para los que vienen de otros lenguajes esta manera de modelar puede ser confusa al principio. Pero tiene sus ventajas en el escenario web, el cual, a mi parecer es completamente orientado al evento.

Tenemos listo el back-end! Ya tenemos una lista de productos, ejecutamos? Aún no, ya que res.render cargaría el template jade, pero no le está insertando los datos. Por lo que modificaremos views/index.jade para tal efecto:

  • views/index.jade
h2 Tabla de Productos
table(border='1')
  tr
    th Producto
    th Descripción
    th Precio
  - if (productos)
    - each producto in productos
      tr
        td
          a(href="/producto/" + producto._id.toString()) #{producto.nombre}
        td #{producto.descripcion}
        td #{producto.precio}

Bien. CTRL+C$ node app.js y veamos el resultado:

Node.js Express Mongoose Guia 5

Página de Edición de un Producto (GET /producto/:id)

Si clickamos en el link del primer producto obtenidos, tendremos un mensaje que no podemos ver el producto. Tomaremos las medidas para ello.

En general, se pueden dar buenos argumentos para no usar la misma id de producto de la base de datos como indicador id para la ruta. Pero recordemos que estamos en un ejemplo didáctico. Necesitaremos desarrollar (Y aquí veremos lo atractivo que es el paradigma MVC), una función de controlador y una vista, no necesitaremos en este ejemplo hacer funciones de modelos, dado que mongoose nos entrega todo. En la medida que veamos más complejidad, es necesario encapsular las funciones de mongoose y las lógicas que necesitemos en funciones de modelo.

  • controllers/producto.js
exports.show_edit = function (req, res, next) {

  // Obtención del parámetro id desde la url
  var id = req.params.id

  Producto.findById(id, gotProduct)

  function gotProduct (err, producto) {
    if (err) {
      console.log(err)
      return next(err)
    }

    return res.render('show_edit', {title: 'Ver Producto', producto: producto})
  }
}

Mongoose nos ahorra muchos problemas de desarrollo, tiene la función findById, (Ver documento), la que, dado un id en string, devuelve el objecto correspondiente (o null, si no existe)

Necesitamos renderizar el objecto. Acá usaremos la misma plantilla para edición y mostrar el producto:

  • /views/show_edit.jade
h2 #{title}
form(method='post')

  p
    label(for="nombre") Nombre:
      input(type='text', name='nombre', value=producto.nombre)

  p
    label(for="descripcion") Descripción:
      input(type='text', name='descripcion', size=100, value=producto.descripcion)

  p
    label(for="precio") Precio:
      input(type='text', name='precio', value=producto.precio)

  p
    input(type='submit', value='Guardar')

Obtenemos la siguiente pantalla:

Node.js Express Mongoose Guia 6

Sin embargo si presionamos el botón guardar cambios, nada ocurre. Es lo que habilitaremos en el siguiente apartado…

Enviar los cambios de un producto (POST producto/:id)

Este es un trabajo completo sólo en el controlador (ya que tenemos la vista y modelo en mongoose):

  • /controllers/producto.js
exports.update = function (req, res, next) {
  var id = req.params.id

  var nombre      = req.body.nombre       || ''
  var descripcion = req.body.descripcion  || ''
  var precio      = req.body.precio       || ''

  // Validemos que nombre o descripcion no vengan vacíos
  if ((nombre=== '') || (descripcion === '')) {
    console.log('ERROR: Campos vacios')
    return res.send('Hay campos vacíos, revisar')
  }

  // Validemos que el precio sea número
  if (isNaN(precio)) {
    console.log('ERROR: Precio no es número')
    return res.send('Precio no es un número !!!!!')
  }

  Producto.findById(id, gotProduct)

  function gotProduct (err, producto) {
    if (err) {
      console.log(err)
      return next(err)
    }

    if (!producto) {
      console.log('ERROR: ID no existe')
      return res.send('ID Inválida!')
    } else {
      producto.nombre       = nombre
      producto.descripcion  = descripcion
      producto.precio       = precio

      producto.save(onSaved)
    }
  }

  function onSaved (err) {
    if (err) {
      console.log(err)
      return next(err)
    }

    return res.redirect('/producto/' + id)
  }
}

Controlamos los errores de no ID y parámetros en blanco. A next() le estamos dando un parámetros. En iteraciones posteriores debemos configurar que si next recibe parámetros, entregarle un error 500 al usuario. Pasaremos de esta funcionalidad por ahora.

Borrar un Producto (POST /delete-producto/:id)

Cómo se mencionó arriba, se podría haber usado el verbo DELETE (haciendo override de método). Para hacer más simple el tutorial, se implementa en GET.

Debemos agregar los links para el eliminado en la lista de productos, es decir en el template de jade:

  • views/index.jade
h2 Tabla de Productos
table(border='1')
  tr
    th Producto
    th Descripción
    th Precio
    th  
  - if (productos)
    - each producto in productos
      tr
        td
          a(href="/producto/" + producto._id.toString()) #{producto.nombre}
        td #{producto.descripcion}
        td #{producto.precio}
        td
          a(href="/delete-producto/" + producto._id.toString()) Borrar

Node.js Express Mongoose Guia 7

Y la funcionalidad correspondiente en el controlador:

  • /controllers/producto.js
exports.remove = function (req, res, next) {
  var id = req.params.id

  Producto.findById(id, gotProduct)

  function gotProduct (err, producto) {
    if (err) {
      console.log(err)
      return next(err)
    }

    if (!producto) {
      return res.send('Invalid ID. (De algún otro lado la sacaste tú...)')
    }

    // Tenemos el producto, eliminemoslo
    producto.remove(onRemoved)
  }

  function onRemoved (err) {
    if (err) {
      console.log(err)
      return next(err)
    }

    return res.redirect('/')
  }
}

Nótese (con algo de humor por supuesto), como reaccionamos ante una id que no encontramos. Si bien asumimos que esta función es llamada dentro de la página de índice, es posible que los valores quieran ser ingresados directamente (ala REST). El desarrollador debe preveer esta conducta y crear los flujos adecuados.

Cosas que podemos agregar: Hacer una función js de cliente para que despliegue un confirmador (está seguro?) y enviar vía AJAX la llamada a borrar el producto; Podemos cerciorarnos además que quien de la orden esté dentro de una sesión; Podemos agregar un token contra “Cross Site Request Forgery”, entre otros.

Agregar un Producto (GET /nuevo-producto)

Finalmente, la funcionalidad de agregar productos.

La primera idea es que la función asociada a la ruta, exports.create, nos arroje un html con los campos en blanco:

  • /controllers/producto.js
exports.create = function (req, res, next) {
  return res.render('show_edit', {title: 'Ver Producto', producto: {}})
}

Eso fue sencillo. Quisieramos agregar un link a esta misma página en la página de inicio:

Node.js Express Mongoose Guia 8

  • /views/index.jade
p
  a(href='/nuevo-producto') Nuevo Producto

Node.js Express Mongoose Guia 10

Y la función de controlador exports.create debe ser modificada, crearemos un desvío según el metodo HTTP que ocupemos (GET o POST)

exports.create = function (req, res, next) {
  if (req.method === 'GET') {
    return res.render('show_edit', {title: 'Nuevo Producto', producto: {}})
  } else if (req.method === 'POST') {
    // Obtenemos las variables y las validamos
    var nombre      = req.body.nombre       || ''
    var descripcion = req.body.descripcion  || ''
    var precio      = req.body.precio       || ''

    // Validemos que nombre o descripcion no vengan vacíos
    if ((nombre=== '') || (descripcion === '')) {
      console.log('ERROR: Campos vacios')
      return res.send('Hay campos vacíos, revisar')
    }

    // Validemos que el precio sea número
    if (isNaN(precio)) {
      console.log('ERROR: Precio no es número')
      return res.send('Precio no es un número !!!!!')
    }

    // Creamos el documento y lo guardamos
    var producto = new Producto({
        nombre        : nombre
      , descripcion   : descripcion
      , precio        : precio
    })

    producto.save(onSaved)

    function onSaved (err) {
      if (err) {
        console.log(err)
        return next(err)
      }

      return res.redirect('/')
    }
  }  
}

Podemos hacer algunas pruebas:

Node.js Express Mongoose Guia 12

Node.js Express Mongoose Guia 13

Y eso sería todo por este tutorial. Insisto, se pueden hacer muchas cosas más, pero el objetivo es introducir al lector en estas tecnologías. Personalmente hubiese hecho algún trabajo para manejar los errores y devolver un error 500, desarrollo de usuarios y sesiones, más javascript de cliente y otros. Para el futuro. Muchas gracias.

Todo el código lo pueden encontrar en el repositorio: herman-mongoose-suggestion,

Autor de éste Artículo

Herman Junge, Chileno. Aspirante a Programador Zen. Ingeniero en Geekli.st, donde hace malabarismo en el stack NodeJS, MongoDB y Redis, entre otras tecnologías, alternando features tanto de front, como de back-end. En sus ratos libres reune energías para entender como las maquinas pueden aprender y ayudarnos en la recopilación y creación de conocimiento.

Twitter: @hermanjunge
Geekli.st: http://geekli.st/hermanjunge

Permalink

0

Oferta Laboral – Node.js en Mediasmart

mediasmart

Mediasmart es una empresa joven que soluciona problemas complejos de publicidad móvil. Es una compañía pequeña con grandes oportunidades de crecimiento. Empresa con gran respaldo y un gran lugar para trabajar con ventajas como el teletrabajo, con grandes profesionales y herramientas de vanguardia.
Mediasmart busca candidatos para ampliar su departamento de tecnología.  Las responsabilidades serán trabajar en nuestros productos manteniendo y expandiendo sus funcionalidades, mejorando su usabilidad a través de casos de prueba y, dependiendo de tus habilidades trabajar en nuestro SDK móvil o mejorar nuestra interfaz backbone.js

Capacidades

Mediasmart busca profesionales con experiencia en sistemas distribuidos basados en event model programming. Experiencia en Ruby EventMachine, Python Twisted/Tornado o Erlang. Lo ideal es contar con experiencia en Node.js. Si no tienes experiencia en ninguno de estos, como mínimo deberás tener experiencia en start ups, y disposición para programación creativa con contribuciones a la escena del “open source”.

  • Experiencia en JavaScript, de manera ideal Node.js. Coffescript es un extra
  • Experiencia en Event Driven Programming
  • Conocimiento en NoSQL. Conocimientos de administración, Membase o Couchbase es un extra
  • Conocimiento del protocolo y programación en Memcached
  • Experiencia en nginx para utilización y configuración. Experiencia en programacia de módulos en Lua para nginx es un extra
  • Experiencia de desarrollo de software en Objective C para iOS es un extra
  • Conocimiento practico de ambientes de desarrollo para Android. Contar con una aplicación escrita para Android es un extra
  • Experiencia de trabajo altamente efectivo en equipos geográficamente dispersos
  • Experiencia en el desarrollo de un Ad Server es un extra
  • Experiencia en entrega de software comercial para móviles

Otras Habilidades

  • Excelentes habilidades de comunicación tanto escritas como orales
  • Habilidades para buena organización y habilidades para trabajo efectivo en equipos
  • Fluidez en Ingles y Español
  • Fluidez en el manejo de programas de oficina ( Word, Excel, etc… ) y sistemas operativos modernos ( Mac OS X de preferencia )

Los candidatos por favor aplicar enviando enlaces de su cuenta GitHub o proyectos personales a el correo electrónico: info@mediasmart.es

Descargar oferta laboral: Link

Permalink

1

Cuando los desarrolladores .NET se deciden por Node.js

En el sitio Performance Zone encontramos un artículo que nos llama la atención y nos presenta una comparación entre la implementación de un servidor HTTP Asíncrono desarrollado con tecnología .NET y su equivalente desarrollado con tecnología Node.js

Veamos todas las herramientas necesarias aplicadas en cada implementación para cada una de estas tecnologías.

Este es un pequeño ejemplo .NET de un HttpHandler con manejo asincrónico y para su ejecución se utilizó EasyNetQ.

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Web;

namespace TestHttpHandler
{
    public class TimerHandler : IHttpAsyncHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            throw new InvalidOperationException("This handler cannot be called synchronously");
        }

        public bool IsReusable
        {
            get { return false; }
        }

        public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback callback, object state)
        {
            var taskCompletionSouce = new TaskCompletionSource<bool>(state);
            var task = taskCompletionSouce.Task;

            var timer = new Timer(timerState =>
            {
                context.Response.Write("OK");
                callback(task);
                taskCompletionSouce.SetResult(true);
            });
            timer.Change(1000, Timeout.Infinite);

            return task;
        }

        public void EndProcessRequest(IAsyncResult result)
        {
            // nothing to do
        }
    }
}

De lo que se puede ver nos lleva algún tiempo el poder desarrollar un servidor sin bloqueos lo cual es nativo en una servidor en Node.js

var http = require('http');

http.createServer(function (req, res) {
    setTimeout(function () {
        res.writeHead(200, { 'Content-Type': 'text/plain' });
        res.end('OK');
    }, 1000);
}).listen(1338);

console.log('LongRunningServer is at http://localhost:1338/');

Primero en Node.js la implementación necesita menos código y en tiempo de ejecución no es necesario de herramientas de terceros para poder ejecutarlo.