Node.js: Persistiendo en Mongo

Actualmente estamos persistiendo la lista de noticias en un array guardado dentro de una variable global a nuestra instancia de node.js. Esta solución es solo temporal, necesitamos poder persistir la lista de noticias en algún medio persistente (que sobreviva al ciclo de vida de nuestro servidor node).

Vamos a usar MongoDB como base de datos. Mongo es una base de datos documental open-source.

Instalando MongoDB

Mongo correrá como un proceso separado de nuestro servidor node.js. Para instalarlo podemos seguir las siguientes instrucciones:

https://docs.mongodb.com/manual/administration/install-community/

Una vez instalado arrancaremos la base de datos ejecutando

$ mongod --dbpath mongo_data

Utilizando Mongoose

Mongoose es un cliente de mongodb que provee servicios de más alto nivel que los provistos por el driver original de mongo. Entre sus features podemos contar la capacidad de definir schemas para cada colección de objetos que tengamos en mongo (podemos ver a mongoose como un object-mapper entre objetos javascript y la estructura json que mongo persiste).

Utilizaremos npm para instalar el modulo de mongoose.

$ npm install mongoose --save

Nos conectaremos a mongo por medio del siguiente código:

index.js

1
2
3
4
5
...
import mongoose from 'mongoose'
mongoose.connect('mongodb://localhost/news')
...
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Una vez abierta la conexión con mongo deberemos proceder a configurar nuestro modelo por medio de uno o varios Schema. Un schema define la estructura que un objeto persistido en mongoose tendrá.

A continuación definimos schema y modelo para nuestro objeto noticia:

index.js

1
2
3
4
5
6
7
8
const postSchema = new mongoose.Schema({
title: String,
content: String,
upvotes: { type: Number, default: 0 },
createdAt: { type: Date, default: Date.now }
})
const Post = mongoose.model('Post', postSchema)
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Desde este punto podemos modificar el código que teníamos para nuestras rutas de forma que utilice mongo (en lugar de una variable global) como medio persistente.

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...
app.get('/noticias', (req, res) => {
Post.find((err, noticias) => {
res.json(noticias)
})
})
app.post('/noticias', (req, res) => {
const noticia = new Post(req.body)
Post.save((err, noticias) => {
res.sendStatus(200)
})
})
...
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Asincronicidad

Post.find es la función encargada de recuperar noticias desde mongo. Como muchas otras funciones en el ecosistema javascript/node.js funciona de forma asincrónica por lo que el flujo de control no se bloquea esperando que las noticias sean recuperadas desde mongo sino que continua. Una de las formas para poder hacer uso del resultado de la busqueda es especificar una función de callback la cuál será invocada con el mismo una vez que la operación asincrónica haya concluído. Dicha función de callback recibirá en este caso dos parametros:

  • err - cuyo valor será distinto de null solamente si ocurrió algún error en la ejecución de la query.
  • noticias - con el resultado de la query

Para que la implementación de nuestras rutas esté completa deberíamos manejar de alguna forma el escenario en el cuál err no es nulo. Una buena forma de hacerlo es propagando dicho error para que express pueda manejarlo eventualmente de forma genérica (esto se hace utilizando la función next que es provista a todas las rutas de express)

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
app.get('/noticias', (req, res, next) => {
Post.find((err, noticias) => {
if (err) {
return next(err)
}
res.json(noticias)
})
})
app.post('/noticias', (req, res, next) => {
const noticia = new Post(req.body)
noticia.save((err, noticia) => {
if (err) {
return next(err)
}
res.sendStatus(200)
})
})
...
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Si utilizamos curl para probar nuestras rutas notaremos que las noticias son persistidas apropiadamente en mongo. Para inspeccionar el contenido de mongo recomendamos Robomongo

GitHub

Como referencia dejamos el estado del proyecto en GitHub

Callbacks vs Promises

Una mejor forma de manejar la asincrónicidad es hacer uso de Promises. Una Promise es un objeto que representa el resultado de una computación futura. Las funciones de mongoose como find o save retornan Promise.

Una Promise entiende fundamentalmente dos mensajes: then y catch. Ambos mensajes reciben una función por parametro la cuál será invocada asincrónicamente cuando la Promise se resuelva a un valor o cuando ocurra un error, respectivamente.

El código de las rutas anteriores, utilizando promises, se traduce a:

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
app.get('/noticias', (req, res, next) => {
const promise = Post.find()
promise.then(noticias => res.json(noticias))
.catch(err => next(err))
})
app.post('/noticias', (req, res, next) => {
const noticia = new Post(req.body)
const promise = noticia.save()
promise.then(noticia => res.sendStatus(200))
.catch(err => next(err))
})
...
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

o su versión más compacta:

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...
app.get('/noticias', (req, res, next) => {
Post.find()
.then(noticias => res.json(noticias))
.catch(next)
})
app.post('/noticias', (req, res, next) => {
const noticia = new Post(req.body)
noticia.save()
.then(noticia => res.sendStatus(200))
.catch(next)
})
...
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

En el resto de tutorial preferiremos utilizar Promise por sobre sus alternativas a base de callbacks.

results matching ""

    No results matching ""