Tutorial:Node.js y MongoDB JSON REST API server con Mongoskin y Express.js

Actualización 3: La versión Expess 4 de este tutorial está disponible en Express.js 4, Node.js y MongoDB REST API Tutorial, y github.com/azat-co/rest-api-express (rama principal). Este tutorial funcionará con Express 3.x.

Actualización2 :"Mongoskin eliminó 'db.collection.id' y agregó algunos métodos actionById" de esta solicitud de extracción con estos cambios de código. Para usar el código en esta publicación, simplemente instale una versión anterior de Mongoskin (¿0.5.0?). El código en GitHub funcionará con Mongoskin 1.3.20.

Actualización2 :"Mongoskin eliminó 'db.collection.id' y agregó algunos métodos actionById" de esta solicitud de extracción con estos cambios de código. Para usar el código en esta publicación, simplemente instale una versión anterior de Mongoskin (¿0.5.0?)

Actualizar :use el código mejorado de este repositorio github.com/azat-co/rest-api-express (rama express3).

Nota: Este texto es parte de Express.js Guide:The Comprehensive Book on Express.js.

Este tutorial lo guiará a través de la prueba de escritura utilizando las bibliotecas Mocha y Super Agent y luego las usará en una forma de desarrollo basada en pruebas para construir un servidor API REST JSON gratuito de Node.js utilizando el marco Express.js y la biblioteca Mongoskin para MongoDB. En este servidor API REST, realizaremos crear, leer, actualizar y eliminar (CRUD) y aprovechar el concepto de middleware Express.js con app.param() y app.use() métodos.

Cobertura de prueba

Antes que nada, escribamos pruebas funcionales que realicen solicitudes HTTP a nuestro servidor REST API que pronto se creará. Si sabe cómo usar Mocha o simplemente quiere saltar directamente a la implementación de la aplicación Express.js, no dude en hacerlo. También puede usar los comandos de terminal CURL para realizar pruebas.

Suponiendo que ya tenemos instalados Node.js, NPM y MongoDB, creemos un nuevo carpeta (o si escribió las pruebas use esa carpeta):

mkdir rest-api
cd rest-api

Usaremos las bibliotecas Mocha, Expect.js y Super Agent. Para instalarlos, ejecute estos comandos desde la carpeta del proyecto:

$ npm install [email protected] --save-dev
$ npm install [email protected] --save-dev 
$ npm install [email protected] --save-dev

Ahora vamos a crear express.test.js archivo en la misma carpeta que tendrá seis suites:

  • Crear un nuevo objeto
  • Recuperar un objeto por su ID
  • Recuperación de toda la colección
  • Actualizar un objeto por su ID
  • Comprobar un objeto actualizado por su ID
  • Eliminar un objeto por su ID

Las solicitudes HTTP son muy sencillas con las funciones encadenadas de Super Agent que pondremos dentro de cada conjunto de pruebas. Aquí está el código fuente completo para express.test.js archivo:

var superagent = require('superagent')
var expect = require('expect.js')

describe('express rest api server', function(){
  var id

  it('post object', function(done){
    superagent.post('http://localhost:3000/collections/test')
      .send({ name: 'John'
        , email: '[email protected]'
      })
      .end(function(e,res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(res.body.length).to.eql(1)
        expect(res.body[0]._id.length).to.eql(24)
        id = res.body[0]._id
        done()
      })    
  })

  it('retrieves an object', function(done){
    superagent.get('http://localhost:3000/collections/test/'+id)
      .end(function(e, res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(typeof res.body).to.eql('object')
        expect(res.body._id.length).to.eql(24)        
        expect(res.body._id).to.eql(id)        
        done()
      })
  })

  it('retrieves a collection', function(done){
    superagent.get('http://localhost:3000/collections/test')
      .end(function(e, res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(res.body.length).to.be.above(0)
        expect(res.body.map(function (item){return item._id})).to.contain(id)        
        done()
      })
  })

  it('updates an object', function(done){
    superagent.put('http://localhost:3000/collections/test/'+id)
      .send({name: 'Peter'
        , email: '[email protected]'})
      .end(function(e, res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(typeof res.body).to.eql('object')
        expect(res.body.msg).to.eql('success')        
        done()
      })
  })

  it('checks an updated object', function(done){
    superagent.get('http://localhost:3000/collections/test/'+id)
      .end(function(e, res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(typeof res.body).to.eql('object')
        expect(res.body._id.length).to.eql(24)        
        expect(res.body._id).to.eql(id)        
        expect(res.body.name).to.eql('Peter')        
        done()
      })
  })    
  it('removes an object', function(done){
    superagent.del('http://localhost:3000/collections/test/'+id)
      .end(function(e, res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(typeof res.body).to.eql('object')
        expect(res.body.msg).to.eql('success')    
        done()
      })
  })      
})

Para ejecutar las pruebas podemos usar el $ mocha express.test.js comando.

Dependencias

En este tutorial utilizaremos Mongoskin, una biblioteca MongoDB que es una mejor alternativa al controlador MongoDB nativo antiguo y sencillo para Node.js. Además, Mongoskin es más liviano que Mongoose y no tiene esquema. Para obtener más información, consulte el resumen de comparación de Mongoskin.

Express.js es un contenedor para los objetos del módulo HTTP principal de Node.js. El marco Express.js se basa en el middleware Connect y proporciona mucha comodidad. Algunas personas comparan el marco con Sinatra de Ruby en términos de cómo es configurable y sin opiniones.

[Nota al margen]

Leer publicaciones de blog es bueno, pero ver cursos en video es aún mejor porque son más atractivos.

Muchos desarrolladores se quejaron de la falta de material de video de calidad asequible en Node. Es una distracción ver videos de YouTube y una locura pagar $ 500 por un curso de video de Node.

Visite Node University, que tiene cursos de video GRATUITOS en Node:node.university.

[Fin de la nota al margen]

Si ha creado un rest-api carpeta en la sección anterior Cobertura de prueba , simplemente ejecute estos comandos para instalar módulos para la aplicación:

npm install [email protected] --save
npm install [email protected] --save

Implementación

Lo primero es lo primero, así que definamos nuestras dependencias:

var express = require('express')
  , mongoskin = require('mongoskin')

Después de la versión 3.x, Express agiliza la instanciación de su instancia de aplicación, de manera que esta línea nos dará un objeto de servidor:

var app = express()

Para extraer parámetros del cuerpo de las solicitudes, usaremos bodyParser() middleware que se parece más a una declaración de configuración:

app.use(express.bodyParser())

El middleware (en esta y otras formas) es un patrón poderoso y conveniente en Express.js y Connect para organizar y reutilizar el código.

Al igual que con el bodyParser() método que nos salva de los obstáculos de analizar un objeto de cuerpo de solicitud HTTP, Mongoskin hace posible conectarse a la base de datos MongoDB en una línea de código sin esfuerzo:

var db = mongoskin.db('localhost:27017/test', {safe:true});

Nota:Si desea conectarse a una base de datos remota, por ejemplo, una instancia de MongoHQ, sustituya la cadena con su nombre de usuario, contraseña, host y valores de puerto. Este es el formato de la cadena URI:mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]

El app.param() El método es otro middleware de Express.js. Básicamente dice "haga algo cada vez que haya este valor en el patrón de URL del controlador de solicitudes ”. En nuestro caso, seleccionamos una colección en particular cuando el patrón de solicitud contiene una picadura collectionName con el prefijo de dos puntos (lo verás más adelante en las rutas):

app.param('collectionName', function(req, res, next, collectionName){
  req.collection = db.collection(collectionName)
  return next()
})

Simplemente para que sea fácil de usar, pongamos una ruta raíz con un mensaje:

app.get('/', function(req, res) {
  res.send('please select a collection, e.g., /collections/messages')
})

Ahora comienza el verdadero trabajo, así es como recuperamos una lista de elementos ordenados por _id y que tiene un límite de 10:

app.get('/collections/:collectionName', function(req, res) {
  req.collection.find({},{limit:10, sort: [['_id',-1]]}).toArray(function(e, results){
    if (e) return next(e)
    res.send(results)
  })
})

¿Has notado un :collectionName cadena en el parámetro de patrón de URL? Este y el anterior app.param() el middleware es lo que nos da el req.collection objeto que apunta a una colección específica en nuestra base de datos.

El punto final de creación de objetos es un poco más fácil de comprender, ya que simplemente pasamos toda la carga útil a MongoDB (método también conocido como API REST JSON gratuita):

app.post('/collections/:collectionName', function(req, res) {
  req.collection.insert(req.body, {}, function(e, results){
    if (e) return next(e)
    res.send(results)
  })
})

Las funciones de recuperación de un solo objeto son más rápidas que find() , pero usan una interfaz diferente (devuelven un objeto directamente en lugar de un cursor), así que tenga esto en cuenta. Además, estamos extrayendo el ID de :id parte del camino con req.params.id Magia de Express.js:

app.get('/collections/:collectionName/:id', function(req, res) {
  req.collection.findOne({_id: req.collection.id(req.params.id)}, function(e, result){
    if (e) return next(e)
    res.send(result)
  })
})

El controlador de solicitudes PUT se vuelve más interesante porque update() no devuelve el objeto aumentado, sino que nos devuelve un recuento de los objetos afectados.

También {$set:req.body} es un operador especial de MongoDB (los operadores tienden a comenzar con un signo de dólar) que establece valores.

El segundo {safe:true, multi:false} El parámetro es un objeto con opciones que le dicen a MongoDB que espere la ejecución antes de ejecutar la función de devolución de llamada y que procese solo un (primer) elemento.

app.put('/collections/:collectionName/:id', function(req, res) {
  req.collection.update({_id: req.collection.id(req.params.id)}, {$set:req.body}, {safe:true, multi:false}, function(e, result){
    if (e) return next(e)
    res.send((result===1)?{msg:'success'}:{msg:'error'})
  })
})

Finalmente, el método DELETE que también genera un mensaje JSON personalizado:

app.del('/collections/:collectionName/:id', function(req, res) {
  req.collection.remove({_id: req.collection.id(req.params.id)}, function(e, result){
    if (e) return next(e)
    res.send((result===1)?{msg:'success'}:{msg:'error'})
  })
})

Nota:El delete es un operador en JavaScript, por lo que Express.js usa app.del en su lugar.

La última línea que realmente inicia el servidor en el puerto 3000 en este caso:

app.listen(3000)

En caso de que algo no funcione del todo bien, aquí está el código completo de express.js archivo:

var express = require('express')
  , mongoskin = require('mongoskin')

var app = express()
app.use(express.bodyParser())

var db = mongoskin.db('localhost:27017/test', {safe:true});

app.param('collectionName', function(req, res, next, collectionName){
  req.collection = db.collection(collectionName)
  return next()
})
app.get('/', function(req, res) {
  res.send('please select a collection, e.g., /collections/messages')
})

app.get('/collections/:collectionName', function(req, res) {
  req.collection.find({},{limit:10, sort: [['_id',-1]]}).toArray(function(e, results){
    if (e) return next(e)
    res.send(results)
  })
})

app.post('/collections/:collectionName', function(req, res) {
  req.collection.insert(req.body, {}, function(e, results){
    if (e) return next(e)
    res.send(results)
  })
})

app.get('/collections/:collectionName/:id', function(req, res) {
  req.collection.findOne({_id: req.collection.id(req.params.id)}, function(e, result){
    if (e) return next(e)
    res.send(result)
  })
})
app.put('/collections/:collectionName/:id', function(req, res) {
  req.collection.update({_id: req.collection.id(req.params.id)}, {$set:req.body}, {safe:true, multi:false}, function(e, result){
    if (e) return next(e)
    res.send((result===1)?{msg:'success'}:{msg:'error'})
  })
})
app.del('/collections/:collectionName/:id', function(req, res) {
  req.collection.remove({_id: req.collection.id(req.params.id)}, function(e, result){
    if (e) return next(e)
    res.send((result===1)?{msg:'success'}:{msg:'error'})
  })
})

app.listen(3000)

Sal de tu editor y ejecuta esto en tu terminal:

$ node express.js

Y en otra ventana (sin cerrar la primera):

$ mocha express.test.js

Si realmente no te gusta Mocha y/o BDD, CURL siempre está ahí para ti. :-)

Por ejemplo, datos CURL para realizar una solicitud POST:

$ curl -d "" http://localhost:3000

Las solicitudes GET también funcionan en el navegador, por ejemplo, http://localhost:3000/test.

En este tutorial, nuestras pruebas son más largas que el propio código de la aplicación, por lo que abandonar el desarrollo basado en pruebas puede ser tentador, pero créame los buenos hábitos de TDD le ahorrarán horas y horas durante cualquier desarrollo serio cuando la complejidad de las aplicaciones en las que trabaja es grande.

Conclusión

Las bibliotecas Express.js y Mongoskin son excelentes cuando necesita crear un servidor API REST simple en unas pocas líneas de código. Más adelante, si necesita expandir las bibliotecas, también brindan una forma de configurar y organizar su código.

Las bases de datos NoSQL como MongoDB son buenas en las API REST gratuitas donde no tenemos que definir esquemas y podemos arrojar cualquier dato y se guardará.

El código completo de los archivos de prueba y de aplicación:https://gist.github.com/azat-co/6075685.

Si desea obtener más información sobre Express.js y otras bibliotecas de JavaScript, consulte la serie de tutoriales Introducción a Express.js.

Nota :En este ejemplo, estoy usando un estilo sin punto y coma. Los puntos y comas en JavaScript son absolutamente opcionales excepto en dos casos:en el bucle for y antes de una expresión/declaración que comienza con paréntesis (por ejemplo, Expresión de función invocada inmediatamente).