Monthly Archives: Octubre 2015

Hacer una consulta MongoDB comprobando que un atributo array no esté vacío

En el ejemplo de obtener resultados aleatorios en una consulta de MongoDB y mongoose utilizaba un where un tanto especial. Este where es para comprobar que un atributo de tipo array que está contenido en el modelo tiene datos.

Primero definamos un ejemplo de modelo simple con el que vamos a trabajar. Lo detallo en formato JSON para no anclarnos a un entorno:

{
  name: "Nombre",
  code: "Código",
  screenshots: [],
  description: "Descripción"
}

Ahora con el modelo definido veamos como utilizarlo. Para ello debemos fijarnos en la sentencia where que vamos a utilizar.

{ screenshots: {$exists: true, $not: {$size: 0}} }

En este where, sobre screenshots realizamos 2 comprobaciones. La primera comprobación es utilizar "$exists: true" donde indicamos que la propiedad necesariamente debe existir. Recordemos en las bases de datos de documentos como MongoDB no necesariamente deben coincidir todos los modelos en una colección (tabla) de la base de datos.

Lo segundo que comprobamos es "$not" negamos que el tamaño del array sea 0 o lo que es lo mismo "$size: 0", de esta forma, le pedimos a MongoDB que nos de solo modelos que tengan la propiedad screenshots y que el tamaño de screenshots sea mayor de 0.

Ahora volvamos a ver el ejemplo que usamos con mongoose.

var where = { screenshots: {$exists: true, $not: {$size: 0}} };

mongooseGameModel.count().where(where).exec(function(err, gameCount) {
  var rand = Math.floor(Math.random() * gameCount);

  mongooseGameModel.findOne().where(where).skip(rand).populate('developer').exec(function(err, game) {
    res.locals.randomGame = game;
    next();
  });
});

En este ejemplo utilizando mongoose contra MongoDB obtenemos un juego aleatorio de un listado que cumpla la condición de que screenshots no esté vacío.

Obtener registros aleatorios con MongoDB

En una base de datos NoSQL como es MongoDB nos asaltan dudas constantemente. En este caso, ¿Cómo obtener un único registro aleatorio? A continuación os detallo como obtener este dato.

Para obtener un registro aleatorio debemos de realizar una consulta de un único elemento pero saltando los x primeros registros. De esta forma obtendremos el resultado. Nada mas fácil que verlo con un ejemplo. Para ello pondré 2 ejemplos uno directamente haciendo la consulta sobre MongoDB y otro utilizando ODM de Mongoose.

En MongoDB se haría de la siguiente forma.

db.games.find().limit(-1).skip( _rand() * db.games.count() )

Si tenemos una tabla games con un listado de juegos y queremos seleccionar un juego aleatorio, lo podríamos hacer así. Seleccionamos todos los juegos, establecemos el limit a -1 y saltamos un aleatorio.

Otro método sería utilizando mongoose, este ya solo como programación.

mongooseModel.count().exec(function(err, resultCount) {
  var rand = Math.floor(Math.random() *resultCount);
  mongooseModel.findOne().skip(rand).exec(function(err, result) {
    console.log(result);
  });
});

Como podemos ver, es un lo mismo que hacerlo directamente sobre MongoDB pero añadiendo algo de complejidad en el proceso por las llamadas asíncronas. En primer lugar deberemos obtener el número de resultados, una vez los tengamos, podemos calcular el aleatorio y obtener con findOne el resultado que queremos.

Ya para terminar un buen ejemplo de como utilizar este código, es utilizando where para filtrar los resultados, que deberemos aplicar tanto a la hora de obtener el contador como cuando buscamos el resultado, ya que si no lo aplicamos en conjunto, puede fallarnos todo el código.

var where = { screenshots: {$exists: true, $not: {$size: 0}} };

mongooseGameModel.count().where(where).exec(function(err, gameCount) {
  var rand = Math.floor(Math.random() * gameCount);

  mongooseGameModel.findOne().where(where).skip(rand).populate('developer').exec(function(err, game) {
    res.locals.randomGame = game;
    next();
  });
});

Montar un servidor para node.js usando nodeserver

Llevo tiempo trabajando con node.js y la verdad es que es el entorno donde mas cómodo me siento. El problema que tiene node.js es que cuando buscas como montar un servidor, no tienes ejemplos reales, solo como escuchar el puerto 80.

Para mis webs uso un servidor de desarrollo propio llamado nodeserver que podemos encontrar tanto en github como en npm. Este servidor tiene varias ventajas fundamentales:

  • Al ser un desarrollo propio, me permite fácilmente ir añadiendo nuevas funcionalidades según las necesito.
  • Tiene soporte para proxy reverso, por ejemplo tener las webs en un servidor interno sin acceso público y traer las conexiones.
  • Soporte de gestión de procesos para node.js, por lo que los .js de node.js que vamos a arrancar como servidores, podemos arrancarlos automáticamente desde el servidor, así como pararlos o reiniciarlos.
  • Tiene soporte para páginas estáticas en html, con un listado de mime types en desarrollo. Esto nos permite por ejemplo, crear un sistema cookieless para un CDN sin un backend, solo configurando el servidor.
  • Soporte para páginas en PHP. Aunque aún no soporta elementos como mod_rewrite, en un futuro lo tendrá, por lo que en el típico VPS para gestionar pequeños proyectos, podemos prescindir de un NGINX o Apache y utilizar nodeserver para gestionar nuestras webs.

Vamos a ver como se instala y configura este servidor. Para esto, primero debemos tener instalado en nuestro sistema node.js, cualquier versión o io.js. Este servidor funciona desde la versión 0.10 de node.js, aunque lo mas recomendable es utilizar las últimas versiones, 4.0 o superiores.

Con nodejs ya instaldo, debemos instalar por npm nodeserver

$ sudo npm install nodeserver -g

De momento nodeserver solo cuenta con script de instalación para centos pero se irá ampliando a otros sistemas, esto no quiere decir que no se pueda utilizar, no se instalará como demonio del sistema.

$ sudo nodeserver install centos

Una vez instalado el script, que no es obligatorio, deberemos configurar el servidor. Para ello crearemos un json de configuración en /etc/nodeserver/nodeserver.config.

{
 "nodeserver": {
  "admin": {
   "active": [true|false],
   "port": admin-port-number,
   "user": "user-for-admin",
   "password": "hash-password"
  }
 },
 "sites": [{
  "name": "website name",
  "type": "node|cgi",
  "bindings": [
   "example.com:80",
   "www.example.com:80",
   "otherbindings:8080",
  ],
  "port": "internal port number for the project, do not repeat it. Only for node"
  "script": "absolute script for server.js for node or doc_root for php (cgi)",
  "security": {
   "certs": {
    "key": "/absolutepath/keycert.key",
    "cert": "/absolutepath/cert.cert",
    "ca": ["/absolutepath/ca.cert"]
   },
   "bindings": [
    "securehostforhttps.com:443",
    "www.securehostforhttps.com:443"
   ]
  },
 }, {
  "name": "php website",
  "type": "cgi",
  "bindings": [
   "myphpsite.com:80"
  ],
  "script": "/var/www/phpsite"
 }, {
  "name": "standar nodejs site",
  "type": "node",
  "bindings": [
   "standarnodejs.com:80"
  ],
  "port": "10001",
  "script": "/var/www/nodejs1/server.js"
 }, {
  "name": "secure nodejs site",
  "type": "node",
  "bindings": [
   "securenodejs.com:80"
  ],
  "port": "10002",
  "security": {
   "certs": {
    "key": "/absolutepath/keycert.key",
    "cert": "/absolutepath/cert.cert",
    "ca": ["/absolutepath/ca.cert"]
   },
   "bindings": [
    "securenodejs.com:443"
   ]
  },
  "script": "/var/www/nodejs2/server.js"
 }]
}

El json de configuración se divide en 2 keys: una de administración y otra de sitios web. La parte de administración indicamos si queremos activarlo o no, el puerto de escucha via web, usuario y contraseña. La contraseña viene en formato hash y podemos generar una utilizando el comando:

$ nodeserver password micontraseña

El resultado deberemos utilizarlo en la configuración de password.

Por otro lado tenemos los sites. Los sites tienen como configuración un nombre, un tipo cgi para estáticosphp o node para webs en node.js. Un array de bindings indicará dominio y puerto que vamos a utilizar de escucha. En los bindings podemos incluir patrones regulares, por ejemplo, para cuando tenemos subdominios que queremos gestionar desde un mismo backend. Un ejemplo sería ".+\.midominio.com:80" para capturar todos los subdominios de midominio.com en el puerto 80. El atributo port solo se utilizar en node.js y es un puerto interno de escucha para servidores node.js. El atributo script utiliza para node.js la ruta del script de arranque del proyecto o el document_root para webs en php o estáticas.

Una vez todo configurado, ya podemos arrancar y probar nuestro servidor.

$ sudo nodeserver start

Os animo a probarlo y a comentar vuestras experiencias, mejoras, etc. Esto es un ejemplo, de como prepararlo en un entorno real, pero se puede configurar nodeserver como servidor de desarrollo, tener una escucha con el parametro start-loop en lugar de start.