Introducción a Node.js, parte 13: CRUD con Mongoose

La palabra CRUD significa Create(Crear), Read(Leer), Update(Modificar), Delete(Eliminar); y se refiere a las operaciones que podemos realizar en los registros de una base de datos, en el caso de MongoDB, en los documentos. En la publicación pasada, aprendimos cómo conectar Node.js a una base de datos MongoDB, en esta ocasión vamos a ver como insertar, modificar, eliminar y recuperar documentos.

Recordar siempre tener el servicio de MongoDB encendido.

Yo en mi caso usaré el proyecto de la publicación pasada, «conexion_mongodb», pero si querés crear un nuevo proyecto, adelante.

Ahora vamos a ir a nuestro archivo app.js y vamos a importar el módulo de mongoose, si es que no está:

var mongoose = require('mongoose');

Y vamos a conectarlo a MongoDB, como hicimos la última vez:

mongoose.connect('mongodb://localhost/primer_base', function(error){
   if(error){
      throw error; 
   }else{
      console.log('Conectado a MongoDB');
   }
});

 

Creando nuestro Modelo

Al igual que las tablas en una base de datos SQL, en Mongoose también se trabaja con el concepto de Modelo. Si no sabés qué es un modelo, es una clase, que se encargará de realizar consultas, en un tipo de documento específico. En nuestro caso vamos a crear un modelo para guardar personajes. Así que vamos a agregar las siguientes líneas:

var PersonajeSchema = mongoose.Schema({
   nombre: {type: String, required: true},
   apellido: {type: String, required: true},
   biografia: {type: String, required: true}
});
var PersonajeModel = mongoose.model('Personaje', PersonajeSchema);

En primer lugar creamos el esquema de nuestro modelo, mediante el método de mongooseSchema(). Aquí vamos a definir los atributos que tendrá el esquema. Uno para guardar el nombre, otro para el apellido y un último para la biografía. Los tres serán de tipo String y obligatorios:

var PersonajeSchema = mongoose.Schema({
   nombre: {type: String, required: true},
   apellido: {type: String, required: true},
   biografia: {type: String, required: true}
});

Finalmente con el método model(), crearemos un modelo con el nombre ‘Personaje’, y le pasamos el esquema, la estructura del modelo:

var PersonajeModel = mongoose.model('Personaje', PersonajeSchema);

CRUD

Para disponer de las acciones para nuestros documentos de tipo Personaje, vamos a crear primero dentro de la carpeta routes, un nuevo módulo al que llamaremos personajes.js. Y las vistas, así que vamos también a la carpeta views, y dentro creamos un directorio con el nombre: personajes, y dentro tres vistas: index.jadeshow.jade y save.jade.

Vamos a editar nuestro módulo personajes.js, de la siguiente forma:

var Personaje;
exports.setModel = function(modelo){
   //
};
exports.index = function(req, res){
   //
};
exports.create = function(req, res){
   //
};
exports.store = function(req, res){
   //
};
exports.show = function(req, res){
   //
};
exports.edit = function(req, res){
   //
};
exports.update = function(req, res){
   //
};
exports.destroy = function(req, res){
   //
};

En primer lugar tendremos una variable Personaje que representará al modelo dentro de ese módulo:

var Personaje;

Luego tendremos un método para setear esa variable:

exports.setModel = function(modelo){
   //
};

Así que vamos a reemplazar ese método de esta manera:

exports.setModel = function(modelo){
   Personaje = modelo;
};

Con este método definiremos el modelo, que estará disponible dentro del resto del módulo, como dijimos antes. Para ello vamos a llamar a este método y vamos a pasarle dicho modelo.

Así que vamos a modificar el archivo app.js y reemplazamos lo anterior por el siguiente código:

var mongoose = require('mongoose');
var personajes = require('./routes/personajes');
mongoose.connect('mongodb://localhost/primer_base', function(error){
   if(error){
      throw error; 
   }else{
      console.log('Conectado a MongoDB');
   }
});
var PersonajeSchema = mongoose.Schema({
   nombre: {type: String, required: true},
   apellido: {type: String, required: true},
   biografia: {type: String, required: true}
});
var PersonajeModel = mongoose.model('Personaje', PersonajeSchema);
personajes.setModel(PersonajeModel);

Y luego agregaremos siete acciones con los otros métodos del módulo personajes:

app.get('/personajes', personajes.index);
app.get('/personajes/create', personajes.create);
app.post('/personajes', personajes.store);
app.get('/personajes/:id', personajes.show);
app.get('/personajes/:id/edit', personajes.edit);
app.put('/personajes/:id', personajes.update);
app.delete('/personajes/:id', personajes.destroy);

Verbos HTTP

Como se ve en las anteriores líneas, hemos utilizado dos métodos nuevos: put() y delete(). Las acciones get() son para mostrar registros, post() para insertar nuevos registros, put() para modificar y delete() para eliminar.

En mi caso yo creé dentro del módulo personajes, métodos para las distintas acciones: index() (GET) nos mostrará una lista con los personajes creados. create() (GET) cargará un formulario para insertar un nuevo personaje, y store() (POST) será justamente la acción que reciba esos datos y hará creará un nuevo documento. show() (GET) nos mostrará un personaje específico. edit() (GET) será un formulario para editar los datos de un personaje en concreto, mientras que update() (PUT) será la acción que reciba esos nuevos valores para modificar el personaje. Y finalmente destroy() (DELETE) para eliminar un personaje.

Listar registros

Vamos a empezar con la acción index() de nuestro módulo personajes.js. Aquí vamos a mostrar la lista con todos los personajes registrados. Así que modificamos esa función con el siguiente código:

exports.index = function(req, res){
   Personaje.find({}, function(error, personajes){
      if(error){
         res.send('Ha surgido un error.');
      }else{
         res.render('personajes/index', {
            personajes: personajes
         });
      }
   })
};

Como se ve en el ejemplo, llamamos al método find() del modelo Personaje.

Este recibirá como primer parámetro las condiciones de la búsqueda, como aquí queremos recuperar todos los personajes, sin filtros, entonces ingresamos un json vacío. También podríamos por ejemplo buscar todos los personajes que cumplan una característica:

{apellido: 'Simpson'}

Con esto nos traerá sólo aquellos personajes de apellido ‘Simpson’.

El segundo parámetro es un callback, que recibirá dos parámetros. El error, si ha habido alguno al intentar realizar la consula, y un array con los documentos encontrados. Entonces vamos a preguntar si hay algún error, y de no haberlo cargaremos la vista index.jade, a la cual le pasamos ese array:

if(error){
   res.send('Ha surgido un error.');
}else{
   res.render('personajes/index', {
      personajes: personajes
   });
}

Ahora, podemos continuar editando justamente esa vista. Así que editamos index.jade con el siguiente código:

extends ../layout
block content
  h1 Personajes
  p
    a(href="/personajes/create") Crear nuevo personaje
  -if(personajes.length > 0)
    table
      thead
        tr
          th Nombre
          th Apellido
          th
          th
          th
      tbody
        -each item in personajes
          tr
            td #{item.nombre}
            td #{item.apellido}
            td Ver
            td Editar
            td Eliminar
  -else
    p No existen personajes creados

En esta vista preguntamos si existen registros, mediante la propiedad length del array personajes:

-if(personajes.length > 0)

De haber registros encontrados dibujamos una tabla con los personajes:

Por el momento las enlaces a «ver», «modificar» y «eliminar» registros no hará nada, pero de eso nos encargaremos más adelante. Sin embargo sí habrá un enlace que nos va a redireccionar a la acción create(). Vamos ahí.

Agregar  registros

Las acciones de create() y store() que he definido, servirán para mostrar el formulario para agregar un nuevo registro, y la segunda será el que reciba esos datos del formulario e inserte el registro. El primero será una acción de tipo GET y el segundo POST.

La acción create() tendrá el siguiente código:

exports.create = function(req, res){
   res.render('personajes/save', {
      put: false,
      action: '/personajes/',
      personaje: new Personaje({
         nombre: '',
         apellido: '',
         biografia: ''
      })
   });
};

En esta acción vamos a cargar la vista save.jade. Vale aclarar que esa vista será usada también como formulario para modificar el registro, por la acción edit(), por tanto debemos pasar una serie de parámetros para diferenciar a ambos.

En primer lugar yo le indique una propiedad put, igualada a false, ya que este formulario será enviado por POST, a diferencia del de edición que enviará el formulario por PUT. Ya veremos esto. También le indique la acción, a dónde debe ir ese formulario. Y un objeto personaje, con sus propiedad nombreapellido y biografia, como valores vacíos. Esto será porque ese formulario recibirá en sus campos valores, y como se está intentando insertar un registro nuevo, entonces los campos tendrá que estar vacíos.

Ahora vamos a editar el archivo save.jade con el siguiente código:

extends ../layout
block content
  h1 Guardar personaje
  form(method="post", action="#{action}")
    -if(put)
      input(type="hidden", name="_method", value="PUT")
    label Nombre
    br
    input(type="text", name="nombre", required="required", value="#{personaje.nombre}")
    br
    label Apellido
    br
    input(type="text", name="apellido", required="required", value="#{personaje.apellido}")
    br
    label Biografía
    br
    textarea(name="biografia", cols="50", rows="5", required="required") #{personaje.biografia}
    br
    input(type="submit", value="Guardar")
    a(href="/personajes") Cancelar

Aquí tenemos un formulario normal, con campos para ingresar, el nombre, apellido y biografía del personaje.

Con respecto a la línea:

-if(put)
  input(type="hidden", name="_method", value="PUT")

Por el momento vamos a omitirla, ya la veremos cuando tengamos que editar.

Ahora vamos a seguir yendo a nuestra acción store() que es donde recibirá los datos y guardará el documento:

exports.store = function(req, res){
   var personaje = new Personaje({
      nombre: req.body.nombre,
      apellido: req.body.apellido,
      biografia: req.body.biografia
   });
   personaje.save(function(error, documento){
      if(error){
         res.send('Error al intentar guardar el personaje.');
      }else{ 
         res.redirect('/personajes');
      }
   });
};

Aquí simplemente creamos un json Personaje, con los valores enviados por el usuario:

var personaje = new Personaje({
   nombre: req.body.nombre,
   apellido: req.body.apellido,
   biografia: req.body.biografia
});

Y finalmente lo guardamos con el método save(). El cual recibirá como parámetro un callback, que a su vez tendrá dos parámetros. Uno con el error, si es que hubo, y otro con el documento que se acaba de insertar. Finalmente si todo salió bien redireccionamos a la página de la lista de personajes.

Mostrar un registro 

Vamos a entrar nuevamente a nuestra vista index.jade, y vamos a modificar la siguiente línea:

td Ver

Por:

td 
  a(href="/personajes/#{item._id}") Ver

La cual nos permitirá acceder al id de ese registro.

Así que ahora editaremos la acción show(), con el siguiente código:

exports.show = function(req, res){
   Personaje.findById(req.params.id, function(error, documento){
      if(error){
         res.send('Error al intentar ver el personaje.');
      }else{
         res.render('personajes/show', {
            personaje: documento
         });
      }
   });
};

Mediante el método findById(), podremos recuperar un documento por su id. El primer parámetro será justamente el id. Mientras que el segundo será un callback, en donde el primer parámetro será un error, si hubo alguno al intentar recuperar el registro. El segundo parámetro es un objeto con los datos del registro.

Si todo ha salido bien cargaremos la vista show.jade, y le pasaremos el objeto con los datos del personaje.

Así que ahora podemos modificar la vista show.jade con el siguiente código:

extends ../layout
block content
  h1 #{personaje.nombre} #{personaje.apellido}
  div(style='white-space:pre;') #{personaje.biografia}
  br
  a(href="/personajes") Volver atrás

Modificar registros

Vamos a abrir nuevamente la vista index.jade y vamos a reemplazar la línea:

td Editar

Por:

td 
  a(href="/personajes/#{item._id}/edit") Editar

De este modo le agregamos un enlace al formulario para editar el registro.

Ahora vamos a crear nuestra acción edit() con el siguiente código:

exports.edit = function(req, res){
   Personaje.findById(req.params.id, function(error, documento){
      if(error){
         res.send('Error al intentar ver el personaje.');
      }else{
         res.render('personajes/save', {
            put: true,
            action: '/personajes/' + req.params.id,
            personaje: documento
         });
      }
   });
};

Primero recuperamos el registro con el método findById(), una vez recuperado, lo pasamos a la vista save.jade, la misma que usamos en el método create(). Sin embargo acá, además además de agregar una url diferente en la acción a enviar el formulario (con el id), en este caso la propiedad que le pasamos, put, será igual a true. Esto, es porque en este caso, este formulario enviará una petición de tipo PUT al servidor.

Si nos fijamos bien, dentro de la vista save.jade, tenemos una línea:

-if(put)
  input(type="hidden", name="_method", value="PUT")
Esto se debe a que la mayoría de los formularios son incapaces de enviar peticiones PUT y DELETE. Para resolver esto debemos enviar una variable con el nombre _method, y el valor «PUT». De esta forma la aplicación comprenderá que se trata de dichas peticiones.
Ahora, debemos escribir el código de la acción update(), la cuál será la que recibe la petición por PUT:
exports.update = function(req, res){
   Personaje.findById(req.params.id, function(error, documento){
      if(error){
         res.send('Error al intentar modificar el personaje.');
      }else{
         var personaje = documento;
         personaje.nombre = req.body.nombre;
         personaje.apellido = req.body.apellido;
         personaje.biografia = req.body.biografia;
         personaje.save(function(error, documento){
            if(error){
               res.send('Error al intentar guardar el personaje.');
            }else{ 
               res.redirect('/personajes');
            }
         });
      }
   });
};
Aquí el proceso es muy sencillo, recuperamos el registro, y de encontrarlo, seteamos los nuevos valores, lo guardamos, y redirigimos a la página con todos los personajes.

Eliminar registros

Vamos a modificar por última vez el archivo index.jade, donde la línea:

td Eliminar

Por:

td
  form(method="post", action="/personajes/#{item._id}")
    input(type="hidden", name="_method", value="DELETE")
    a(href="javascript:void(0);", onclick="if(confirm('¿Está seguro que desea eliminar este registro?')){this.parentNode.submit();}") Eliminar

Como dijimos antes, los formularios son capaces de enviar sólo peticiones GET y POST, por tanto debemos disfrazar esta petición agregando un campo oculto con el nombre «_method» y en este caso el valor «DELETE». También tendremos un enlace con funcionalidad Javascript, que al ser pulsado preguntará si está seguro que desea eliminar el registro, y si el usuario pulsa «Aceptar» en el confirm(), enviará la petición DELETE para eliminar el documento.

Así que vamos a editar la acción que nos falta, destroy():

exports.destroy = function(req, res){
   Personaje.remove({_id: req.params.id}, function(error){
      if(error){
         res.send('Error al intentar eliminar el personaje.');
      }else{ 
         res.redirect('/personajes');
      }
   });
};

Aquí simplemente eliminamos mediante el método remove(), el cual recibirá como primer parámetro el id del documento a eliminar, y como segundo un callback, que tendrá como parámetro un error, si lo hubo, como siempre.

Descargar ejemplo

Anterior: Introducción a Node.js, parte 12: Conexión con MongoDB

Siguiente: Introducción a Node.js, parte 14: Conectar Node.js con MySQL


6 Respuestas a “Introducción a Node.js, parte 13: CRUD con Mongoose”

  1. Muy buenas Ferchu, a pesar de haber seguido el tuto al pie de la letra, me da un error al eliminar o actualizar los datos, pues el archivo app.js no entiende el app.put y el app.delete.

    Si cambio estos valores por post me elimina o actualiza bien, con lo cual deduzco que se debe a que lo está enviando por post a pesar de haber puesto al formulario el name=»_method» y el value=»DELETE». Sabes que puedo hacer?

    1. Hacé una cosa, para comprobar que no es el campo de formulario, imprimí por pantalla el valor de _method con:

      req.body._method

      Tal vez ese dato no esté llegando bien, y por eso entrá por POST y no por PUT o DELETE.

      Saludos!