En el siguiente post les enseño los pasos para crear un proyecto esqueleto con CRUD de un modelo conectado a MongoDB. No introduciré demasiado en cómo funciona NodeJS como tal, existen varias páginas que ayudan en este aspecto. La motivación es porque la generación automática de CRUD de un modelo en NodeJS no es tan intuitivo para nuevos usuarios en esta plataforma.

Las siguientes semanas haré un post con un proyecto más avanzado. Todo el código de esta entrada pueden encontrarla en: https://github.com/arturoverbel/nodejs_skeleton.

¿Qué es NodeJS?

Es un entorno de servidor de código abierto que corre el lenguaje de Javascript. Es gratis, corre en varias plataformas como Windows, Linux, Mac, etc.

A diferencia de otros servidores, NodeJS utiliza programación asincrónica. Esto quiere decir que cuando a NodeJS le llega una solicitud, esta pueda ir procesando (buscar el archivo, leerlo y devolverla al cliente), mientra espera otras solicitudes. Otros servidores como los de PHP y ASP esperan hasta que la solicitud termine para estar disponible para otra solicitud.

Al usar el mismo lenguaje en el cliente como en el servidor, se utiliza el mismo paradigma. NodeJS es una programación orienta a eventos. Lo que ha resultado muy fácil para aplicaciones escalables.

NPM

Traduce (Node Package Manager) y es el gestor de paquete de Javascript para NodeJS. Gracias a este programa tenemos acceso a todas las librerías a un comando de distancia. La instalación puede ser de manera global en el computador, o local en la carpeta del proyecto NodeJS. También nos ayuda administrar la información de nuestra aplicación.

Instalación

Las siguientes instrucciones son para instalar NodeJS en Linux.

$ cd /tmp
$ wget http://nodejs.org/dist/v6.3.1/node-v6.3.1-linux-x64.tar.gz
$ tar xvfz node-v6.3.1-linux-x64.tar.gz
$ mkdir -p /usr/local/nodejs
$ mv node-v6.3.1-linux-x64/* /usr/local/nodejs
$ export PATH=$PATH:/usr/local/nodejs/bin

En este punto podemos escribir en la consola node y que nos aparezca el terminal REPL, el cual es un entorno para ingresar comando y arrojar resultados. Así como la consola de Windows, o el shell de linux.

$ node
>

Desde aquí se pueden realizar varias tareas, ciclos y hasta consultas a la base de datos con el lenguaje Javascript ( Vaya poder ). También podemos ejecutar un script de javascript con este comando. Pero en este tutorial haremos caso omiso a todos estos detalles y pasaremos a la instalación de la base de datos y entornos para montar nuestra aplicación web.

Mongo

Pasamos la siguiente linea de comando:

$ echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list 
$ sudo apt-get update
$ sudo apt-get install mongodb-10gen = 2.2.3

Así como el servicio de mysql podemos apagar, encender y ver su estado con:

$ sudo service mongodb stop
$ sudo service mongodb start
$ sudo service mongodb status

Iniciar proyecto

Instamos la librería express de modo global en nuestra máquina.

$ npm install express-generator -g

Luego pasamos el comando:

$ express --view=twig project

Este comando genera una carpeta con todo lo necesario para iniciar nuestro proyecto los cuales son:

  • app.js: Es el archivo principal y aquí se inicializa todas las variables y los entornos. También se definen los archivos que interactuan entre ellos.
  • /bin/www: Se definen aspectos avanzados de la apliación
  • package.json: El archivo donde se define la informació  básica del app y las librerías que utiliza. Necesario para un proyecto de NodeJS.
  • /public: Carpeta con archivos públicos en la web
  • /routes: Carpeta con archivos donde se definen todas las rutas del app
  • /views: Carpeta con los archivos de vista

En la vista los archivos son formato TWIG como se definió en el comando anterior. Otros populares son EJS.

Instalamos la librería de mongo para que nuestra app se conecta con nuestra base de datos:

$ npm install mongoose

En este punto podemos ver nuestro proyecto corriendo con:

$ DEBUG=project:* npm start

Imagen 1. Página por defecto de express

Configuración con MongoDB

En app.js se configura la conexión con Mongo

var mongoose = require('mongoose');

var mongoDB = 'mongodb://127.0.0.1:27017/mydb';

//BD
mongoose.connect(mongoDB);
mongoose.Promise = global.Promise;
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'MongoDB connection error:'));

Escribir el CRUD

Lo primero es establecer los modelos. Creamos la carpeta /models y escribimos nuestro modelo Product.js:

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var ProductSchema = new Schema({
    name: {type: String, required: true, max: 100},
    price: {type: Number, required: true},
    created_at: { type: Date, default: Date.now }
});

module.exports = mongoose.model('Product', ProductSchema);

Para este modelo entonces necesitaremos un controlador para gestionar todas als acciones. Creamos la carpeta /controllers y agregamos el archivo ProductController.js, en este archivo agregaremos todas las acciones que corresponde al modelo Product.

Controlador

Listar

var mongoose = require('mongoose');
var Product = require("../models/Product");

var productController = {};

productController.list = function(req, res){
    
    Product.find({}).exec(function(err, products){
        if( err ){ console.log('Error: ', err); return; }
        console.log("The INDEX");
        res.render('../views/product/index', {products: products} );
        
    });
    
};

/*
 * Other actions
 */

module.exports = productController;

Mostrar

productController.show = function(req, res){
    Product.findOne({_id: req.params.id}).exec(function(err, product){
        if( err ){ console.log('Error: ', err); return; }
        
        res.render('../views/product/show', {product: product} );
    });
    
};

Crear

productController.create = function(req, res){
    res.render('../views/product/create');
};

productController.save = function(req, res){
    var product = new Product( req.body );
    
    product.save(function(err){
        if( err ){ console.log('Error: ', err); return; }
        
        console.log("Successfully created a product. :)");
        res.redirect("/products/show/"+product._id);
        
    });
};

Editar

productController.edit = function(req, res) {
  Product.findOne({_id: req.params.id}).exec(function (err, product) {
    if (err) { console.log("Error:", err); return; }
    
    res.render("../views/product/edit", {product: product});
    
  });
};

productController.update = function(req, res){
    Product.findByIdAndUpdate( req.params.id, {$set: {
        name: req.body.name,
        price: req.body.price
    }}, { new: true },
    function( err, product){
        if( err ){ 
            console.log('Error: ', err); 
            res.render('../views/product/edit', {product: req.body} );
        }
        
        console.log( product );
        
        res.redirect('/products/show/' + product._id);
        
    });
};

Borrar

productController.delete = function(req, res){
    
    Product.remove({_id: req.params.id}, function(err){
        if( err ){ console.log('Error: ', err); return; }
        
        console.log("Product deleted!");
        res.redirect("/products");
    });
    
};

Vistas

Las vistas se realizan en formato TWIG para este ejemplo. Creamos la carpeta /views/product/ dentro de views y agregamos los siguientes archivos:

Crear

<!DOCTYPE html>
<html>
    <head>
        <title>Create Product</title>
    </head>
    <body>
        <div class="container">
            <h3><a href="/products">Product List</a></h3>
            <h1>Create New Product</h1>
            <form action="/products/save" method="post">
                <table>
                    <tbody>
                        <tr>
                            <td>Name</td>
                            <td><input type="text" name="name" /></td>
                        </tr>
                        <tr>
                            <td>Price</td>
                            <td><input type="text" name="price" /></td>
                        </tr>
                        <tr>
                            <td colspan="2"><input type="submit" value="Save" /></td>
                        </tr>
                    </tbody>
                </table>
            </form>
        </div>
    </body>
</html>

Imagen 2. Crear el modelo

Editar

<!DOCTYPE html>
<html>
    <head>
        <title>Edit Product</title>
    </head>
    <body>
        <div class="container">
            <h3><a href="/products">Product List</a></h3>
            <h1>Edit Product</h1>
            <form action="/products/update/{{ product._id }}" method="post">
                <table>
                    <tbody>
                        <tr>
                            <td>Name</td>
                            <td><input type="text" name="name" value="{{ product.name }}" /></td>
                        </tr>
                        <tr>
                            <td>Price</td>
                            <td><input type="text" name="price" value="{{ product.price }}" /></td>
                        </tr>
                        <tr>
                            <td colspan="2"><button type="submit">Update</button></td>
                        </tr>
                    </tbody>
                </table>
            </form>
        </div>
    </body>
</html>

Imagen 3. Editar modelo

Listar

<!DOCTYPE html>
<html>
    <head>
        <title>Product List</title>
    </head>
    <body>
        <div class="container">
        <h3><a href="/products/create">Create Product</a></h3>
        <h1>Product List</h1>
            {% if products is not empty %}
                <table>
                    <thead>
                        <tr>
                            <th>ID</th>
                            <th>Employee Name</th>
                            <th>Position</th>
                        </tr>
                    </thead>
                    <tbody>
                        {% for product in products %}
                        <tr>
                            <td><a href="/products/show/{{ product._id }}">{{ product._id }}</a></td>
                            <td><a href="/products/show/{{ product._id }}">{{ product.name }}</a></td>
                            <td>{{ product.price }}</td>
                        </tr>
                        {% endfor %}
                    </tbody>
                </table>
            {% else %}
                <div>No products found.</div>
            {% endif %}
        </div>
    </body>
</html>

Imagen 4. Listar modelo

Mostrar

<!DOCTYPE html>
<html>
    <head>
        <title>Product Detail</title>
    </head>
    <body>
        <div class="container">
            <h3><a href="/products">Product List</a></h3>
            <h1>Product Detail</h1>
            <table>
                <tbody>
                    <tr>
                        <td>Name</td>
                        <td>{{ product.name }}</td>
                    </tr>
                    <tr>
                        <td>Address</td>
                        <td>{{ product.price }}</td>
                    </tr>
                </tbody>
            </table>
            <h3><a href="/products/edit/{{ product._id }}">EDIT</a></h3>
            <form action="/products/delete/{{ product._id }}" method="post">
                <button type="submit">DELETE</button>
            </form>
        </div>
    </body>
</html>

Imagen 5. Mostrar modelo

Rutas

Creamos el archivo product.js dentro de la carpeta /routes:

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

var product = require('../controllers/ProductController.js');

router.get('/', product.list);
router.get('/show/:id', product.show);
router.get('/create', product.create);
router.post('/save', product.save);
router.get('/edit/:id', product.edit);
router.post('/update/:id', product.update);
router.post('/delete/:id', product.delete);

module.exports = router;

y añadir la siguiente linea al archivo app.js en la raíz de la aplicación:

var products = require('./routes/products');

Con esto terminamos la aplicación esqueleto con CRUD de un modelo en NodeJS.

Anexo

El código fuente se puede encontrar en mi cuenta de Github con el enlace: https://github.com/arturoverbel/nodejs_skeleton.

Fuentes