Seleccionar página

NeDBHace unos días nos encontrábamos analizando la mejor solución para guardar una pequeña cantidad de datos que teníamos manejar para un crawler. Básicamente necesitábamos almacenar las solicitudes de trabajos y las páginas visitadas con alguna información adicional. Sinceramente, nos daba alguna pereza instalar un sistema de bases de datos para este proyecto, ya que queríamos que fuera extremadamente sencillo y ligero. Tras estudiar varias opciones, descubrimos NeDB, una base de datos escrita completamente en Javascript, sin ningún tipo de dependencia binaria, y que expone un interfaz de programación muy similar a MongoDB. Nos sorprendió ver la velocidad a la que se mueve (no es un MongoDB, pero es bastante rápida). Nos impresionó cuando vimos que funciona tanto en NodeJS y en Chrome, Firefox, Safari e IE9+.

Si ya se tiene conocimientos de MongoDB su uso es realmente sencillo, por el contrario, si se carece de experiencia previa con este motor de base de datos o con bases de datos NoSQL es posible que resulte un poco extraño al principio. Vamos a ver paso a paso las principales características que nos ofrece este sencillo, pero útil paquete:

Instalación

La forma más sencilla de instalarlo es por medio de NPM con la siguiente instrucción:

npm install nedb --save

No es un paquete muy grande y sus dependencias son bastante “razonables”.

Hay unas cuantas decenas de paquetes que extienden, complementan o interaccionan con NeDB. Os recomendamos realizar algunas búsquedas en NPM. Da muestra de la extensión de su uso y da bastante confianza sobre su estabilidad.

Crear un datastore

Los datastore son el equivalente a las colecciones en MongoDB. Si sólo necesitamos un datastore en memoria, su creación es extremadamente sencilla:

var Datastore = require('nedb'),
    db = new Datastore();

Esta es una solución sencilla, pero muy probablemente querremos que se gestione la persistencia de estos datos en un fichero y de esta forma poder recuperarlos posteriormente. Para ello sólo tenemos que pasar algunas opciones a la hora de crear el datastore:

var Datastore = require('nedb'),
    db = new Datastore({filename: __dirname + '/data/example.dat', autoload: true});

Se pueden crear tantos datastore como necesitemos. A partir de aquí los utilizaremos de una forma muy similar a las colecciones de MongoDB.

Insertar documentos

Para insertar documentos de forma individual podemos llamar a insert de esta forma:

db.insert({n: (0 | (Math.random() * 10000)), now: new Date()}, function(err, record) {
    if (err) {
        console.error(err);
        return;
    }
    console.log(record);
    // El resultado debe ser similar a este
    // { n: 9522,
    //   now: Sun Nov 01 2015 21:42:29 GMT+0100 (Hora estándar romance),
    // _id: 'Opc90LgGqYSjMtQ4' }
});

Algunas advertencias:

  • Hay un campo especial denominado _id que de no pasarlo se genera automáticamente y no puede repetirse en un datastore.
  • No es posible crear un documento con una clave que contenga los caracteres ‘$’ o ‘.’. Hay algunos ejemplos en Internet sobre cómo utilizar caracteres alternativos a estos que están reservados

Podemos utilizar el mismo método para insertar varios documentos, simplemente tenemos que pasarlos dentro de una matriz:

var docs = [
    {n: 1, name: 'one'},
    {n: 2, name: 'two'},
    {n: 3, name: 'three'},
    {n: 4, name: 'four'},
    {n: 5, name: 'five'}
];
db.insert(docs, function(err, record) {
    if (err) {
        console.error(err);
        process.exit(0);
    }
    console.log(record);
});

Consultar documentos

Para obtener los documentos almacenados en el datastore utilizaremos el método find pasando como parámetro algún criterio y obtendremos todos aquellos que lo cumplan:

db.find({n: 3}, function(err, record) {
    if (err) {
        console.error(err);
        process.exit(0);
    }
    console.log(record);
});

Si queremos obtener todos los documentos sólo tenemos que pasar un objeto vacío y, por lo tanto, todos los documentos cumplen esta condición:

db.find({}, function(err, record) {
    if (err) {
        console.error(err);
        process.exit(0);
    }
    console.log(record);
});

Hay bastantes opciones para realizar búsquedas en subdocumentos, dentro de matrices en los documentos, por expresiones regulares, etc. Consulta la documentación de NeDB o, incluso la de MongoDB, para tener una referencia completa de lo que se puede llegar a hacer. Aquí, a modo de resumen, te indicamos sólo algunas de ellas:

  • $or y $and, realizar comparaciones lógicas que utilizan de esta forma { $or: [query1, query2, ...] }.
  • $not, invierte el resultado de una consulta { $not: query }
  • $where, permite ejecutar una función de esta forma { $where: function () {} } el objeto es this, el retorno debe ser true o false
  • $lt, $lte: menor que, menor o igual que {n: {$lt: 3}}
  • $gt, $gte: mayor que, mayor o igual que {n {$gt: 3}}
  • $in: el valor debe estar dentro de la matriz
  • $ne, $nin: no igual, no es un miembro de
  • $exists: comprueba la existencia de una propiedad en el objeto
  • $regex: comprueba si la cadena coincide con la expresión regular (no acepta opciones, como MongoDB)
  • $size: comprueba el tamaño de una matriz
db.find({n: {$lt:3}}, function(err, records) {
    if (err) {
        console.error(err);
        process.exit(0);
    }
    console.log(records);
});

Configurar los resultados

Obtener un sólo resultado

Si queremos asegurarnos que sólo vamos a obtener un resultado podemos utilizar findOne de esta forma:

db.findOne({n: 3}, function(err, record) {
    if (err) {
        console.error(err);
        process.exit(0);
    }
    console.log(record);
});

Ordenar

Si queremos obtener los valores con un determinado orden podemos utilizar los métodos sort y exec de la siguiente forma:

db.find({n: {$lt:3}}).sort({name:-1}).exec(function(err, record) {
    if (err) {
        console.error(err);
        process.exit(0);
    }
    console.log(record);
});

Paginar

También podemos paginar los resultados, saltándonos algunos valores (skip) y obteniendo un determinado número de resultados (limit):

db.find({}).sort({name:1}).skip(1).limit(2).exec(function(err, record) {
    if (err) {
        console.error(err);
        process.exit(0);
    }
    console.log(record);
});

Proyección

Otra característica que tenemos a nuestra disposición es definir que parte de los documentos queremos obtener, limitando de esta forma el tamaño de los documentos que vamos utilizar:

db.find({}, {name: 1}, function(err, record) {
    if (err) {
        console.error(err);
        process.exit(0);
    }
    console.log(record);
});

Contar

Cuando sólo queremos saber cuántos documentos se obtendrían con un determinado filtro utilizaremos:

db.count({n: {$lt:3}},function(err, record) {
    if (err) {
        console.error(err);
        process.exit(0);
    }
    console.log(record);
});

Actualizar los documentos

Para actualizar un documento tenemos que utilizar el método update de la siguiente forma:

db.update({n: 3}, {$set: {nombre: 'tres'}}, {}, function(err, num) {
    if (err) {
        console.error(err);
        return;
    }
    console.log(num);
    db.find({n: 3}, function(err, result) {
        if (err) {
            console.error(err);
            return;
        }
        console.log(result);
    });
});

Como podemos observar se ha utilizado la cláusula $set para indicar que se quiere actualizar el documento con este nuevo valor. También se pueden utilizar:

  • $unset para eliminar elementos
  • $inc para incrementar el valor de una propiedad del documento
  • $push, $pop, $addToSet, $pull y $each para trabajar con las matrices del documento.

En este sencillo ejemplo no hemos pasado ninguna opción y por eso podemos ver como tercer parámetro un objeto vacio {}, pero podemos utilizar estas opciones para determinar con mayor precesión el comportamiento de update:

  • {multi: true} (por defecto es false) permite podificar múltiples documentos
  • {upsert: true} (por defecto es false) añade un nuevo documento si no se encuentra el documento. En este caso la función callback recibe un tercer parámetro con los nuevos documentos

Borrar documentos

Para eliminar documentos del datastore utilizaremos el método remove:

db.remove({n: {$gte:  4}}, {multi: true}, function(err, num) {
    if (err) {
        console.error(err);
        return;
    }
    console.log(num);
    db.find({}, function(err, result) {
        if (err) {
            console.error(err);
            return;
        }
        console.log(result);
    });
});

En este caso la única opción que podemos pasar como tercer parámetro es {multi: true} que indica que se borren todos los documentos que coincidan con el filtro que hemos pasado. Si no se pasa este parámetro sólo se borrar el primer documento que coincida con el filtro.

Optimizaciones y algunas caracaterísticas internas

Antes de concluir tres aspectos que debemos tener en cuenta:

Índices

Es posible definir índices, lo cual hará que los resultados se obtengan más rápidamente. Para ello podemos utilizar ensureIndex con tres opciones:

  • fieldName (requerido): nombre de la propiedad sobre la que vamos a crear el índice
  • unique (opcional, por defecto false): fuerza que los valores del índice sean únicos y por lo tanto dará un error al intentar crear un valor duplicado
  • sparse (optional, por defecto false): no indexa los documentos que no tenga la propiedad que indexamos
  • db.ensureIndex({fieldName: 'name', unique: true}, function(err, num) {
        if (err) {
            console.error(err);
            return;
        }
        console.log(num);
    });
    

    Los índices sólo es necesario crearlos una vez si hemos definido el datastore con persistencia. Si por algún motivo queremos eliminar un índice creado, utilizaremos removeIndex pasando como parámetro el nombre del campo sobre el que hemos creado el índice.

    Formato del fichero de datos

    El formato interno que utilizar NeDB es bastante curioso, sólo se añaden valores y cualquier borrado o modificación se anexa al fichero que contiene los valores en texto plano con formato JSON separados en líneas. Es posible encriptar el contenido por medio de los eventos afterSerialization y beforeDeserialization, aunque su uso sobrepasa esta introducción.

    Compactar el fichero de datos

    Al ser un fichero en el que no se modifican los valores, sólo se van añadiendo, es posible que crezca rápidamente. Para controlar su tamaño podemos compactarlo por medio de db.persistence.compactDatafile().

    Si queremos que esta compactación se ejecute cada un determinado periodo de tiempo podemos utilizar db.persistence.setAutocompactionInterval(interval). Este intervalo se puede detener con db.persistence.stopAutocompaction().

    Conclusión

    Claramente NeDB no es un motor de base de datos, es una base de datos que podemos incrustar (empotrar) en nuestro código para labores auxiliares y por ello resulta muy interesante. Además, su compatibilidad con MongoDB nos hace más sencillo migrar posteriormente desde NeDB a ese motor si los requisitos de rendimiento, seguridad o volumen así nos lo exigen.

    Como decíamos al principio, existen numerosas extensiones y paquetes que complementan a NeDB, por lo que podemos utilizarlo desde para almacenar sesiones en Express o Connect, para integrarlo con Sails.js, y así un largo etcétera.

    NeDB es una buena herramienta para tener a mano y utilizarla en cuanto necesitemos almacenar información en nuestra aplicación y no queremos o no podemos utilizar un sistema de base de datos como tal.

Novedades

HTTP2 para programadores. Enviar mensajes del servidor al cliente con Server Sent Event (sin WebSockets)

HTTP2 para programadores. Enviar mensajes del servidor al cliente con Server Sent Event (sin WebSockets)

En esta charla, organizada por MadridJS, Pablo Almunia nos muestra cómo la mayoría de nosotros cuando oímos hablar por primera vez de HTTP2 nos ilusionamos con las posibilidades que presumiblemente se abrían para el desarrollo de soluciones web avanzadas y cómo muchos nos sentimos defraudados con lo que realmente se podía implementar.

En esta charla podemos ver cómo funciona el HTTP2, que debemos tener en cuenta en el servidor para hace uso de este protocolo y, sobre todo, cómo podemos enviar información desde el servidor al cliente de forma efectiva y fácil. Veremos con detenimiento cómo por medio de los Server-Sent Events (SSE) podemos recibir en el cliente datos enviados desde el servidor sin utilizar websocket, simplificando enormemente la construcción de aplicaciones con comunicación bidireccional.

Observables en Javascript con Proxies

Observables en Javascript con Proxies

En esta charla, organizada por MadridJS, Pablo Almunia nos habla de la observación reactiva de objetos en Javascript por medio de Proxy. Se describe paso a paso cómo funcionan los Proxies y en que casos pueden ser nuestro mejor aliado. Veremos que no hay que tenerles miedo, son bastante sencillos de utilizar, y nos ofrecen una gran abanico de posibilidades.

Aplicaciones JAMStack, SEO friendly y escalables con NextJS

Aplicaciones JAMStack, SEO friendly y escalables con NextJS

En esta charla de Madrid JS, Rafael Ventura nos describe las funcionalidades clave de NextJS, nos muestra en vivo cómo desarrollar una completa aplicación JAMStack con Server Side Rendering (SSR) y Static Site Generation (SSG) y termina mostrando como publicar esta aplicación en Vercel.

Stencil JS: mejora el Time To Market de tu producto, por Rubén Aguilera

Stencil JS: mejora el Time To Market de tu producto, por Rubén Aguilera

En esta charla Rubén Aguilera nos cuenta los problemas que tienen muchas empresas a la hora de sacar productos accesibles, vistosos y usables en el Time To Market que requiere Negocio y cómo podemos minimizar este tiempo gracias al DevUI con StencilJS para adecuar una aplicación de Angular a las exigencias del mercado en tiempo record.