3 - Mongoose

We will now add Mongoose which is like a Object mapper for MongoDB.

npm install --save mongoose

We will then create a folder for our models

mkdir src/models

And lets go ahead and create our first model: User.js

import mongoose from 'mongoose'
const Schema = mongoose.Schema

const schema = Schema({
  name: { type: String },
  created_at: { type: Date, default: Date.now }
})

const User = mongoose.model('User', schema)

export default User

Testing with mockgoose

Lets create a test for it. We will use

  • mockgoose: which will mock mongodb using an in-memory representation of the database
  • chai-as-promised: to make it easier to test promises (since mongoose methods will return promises)

Run:

npm install mockgoose chai-as-promised --save-dev

And we need to setup some global configuration for all tests so go ahead and create a test/setup.js file

// chai
import chai from 'chai'
import chaiAsPromised from 'chai-as-promised'
chai.use(chaiAsPromised)

// mongoose
import mongoose from 'mongoose'
mongoose.Promise = global.Promise

And change the test script in package.json to include a "--require" of that file

"test": "export NODE_PATH=./ && mocha --require test/setup.js --compilers js:babel-register --recursive",

And now lets create a new folder for our tests

mkdir test/models
vi test/models/User_spec.js

And include the following content

import mongoose from 'mongoose'
import mockgoose from 'mockgoose'
import { expect } from 'chai'

import User from '../../src/models/User'

describe("User", ()=> {

  before(done => {
    mockgoose(mongoose).then(() => {
      mongoose.connect('mongodb://example.com/TestingDB', done)
    })
  })

  after(done => {
    mockgoose.reset(done)
  })

  it("should be retrieved after saved", () => {
    const user = new User({ name: 'pepito' })

    return user.save()
        .then(() => User.find()
            .then(users => {
              expect(users).not.to.be.equal(undefined)
              expect(users.length).to.be.equal(1)
              expect(users[0]).to.be.have.property('name', 'pepito')
            })
        )
  })

})

Now run the tests again

Integration with Webapp

Now we will integrate our expressjs webapp with mongoose and provide and endpoint to retrieve users.

We will change the setup of server.js extending the idea that we have modeled for setting up express with the src/config/express.js. So we will consider that there could be many modules there that export a function(app):Promise[void] and perform some kind of initialization.

Lets edit server.js

import express from 'express'

import expressConfig from './config/express'
import mongoConfig from './config/express'

const app = express()

const setups = [expressConfig, mongoConfig]
setups.forEach(setup =>  setup(app))

// ...  routes below

Then create a new file src/config/mongoose.js

// mongoose
import mongoose from 'mongoose'

mongoose.Promise = global.Promise

export * from '../models/all'

export default function (app) {
  mongoose.connect('mongodb://localhost/myappdatabase')
}

The connection URL will be hardcoded for the moment.

Then in order to make it easier to load all the mongoose "models", lets create a file in src/models/all.js

// list of all models
export * from './User'

We will be adding similar lines there for each new model.

Organizing routes

As our webapp will probably have many routes, lets organize them into separated files and a folder

mkdir src/routes
vi src/routes/index.js

And lets move our routes from server.js into routes/index.js

import express from 'express'
const router = express.Router()

router.get('/', (req, res) => {
  res.status(200).send('hello')
})

router.post('/', (req, res) => {
  const message = `hello ${req.body.to}!`
  res.json({ status: 'ok', message: message })
})

export default router

And update server.js removing the routes and replacing them with this lines

import index from './routes/index'

app.use('/', index)

Run the tests to make sure everything is working.

Now lets create a new file

vi src/routes/users.js

With content

import express from 'express'
import User from '../models/User'

const router = express.Router()

router.get('/', (req, res) => {
  User.find().then(users => {
    res.json(users)
  })
})

export default router

And include it in server.js as well.

And create a new test test/integration/users_spec.js

import request from 'supertest'
import mongoose from 'mongoose'
import mockgoose from 'mockgoose'
import { expect } from 'chai'
import User from '../../src/models/User'

import appP from '../../src/server'

appP.then(app => {

  describe('Users API', () => {

    before(done => {
      mockgoose(mongoose).then(() => {
        done()
      })
    })

    after(done => {
      mockgoose.reset(done)
    })

    it('GET /users gives a list of users containing one user previously saved', (done) => {
      const wayne = new User({
        _id: '58220c92bdacd314f0c220c4',
        name: 'Wayne',
        created_at : new Date(2017, 10,10)
      })

      wayne.save().then(() => {

        request(app)
            .get('/users')
            .expect(200, [ {
              __v: 0,
              _id: '58220c92bdacd314f0c220c4',
              name: 'Wayne',
              created_at : '2017-11-10T03:00:00.000Z'
            } ], done)

      })
    })

  })

})

Here we face the fact that the express app needs to be returned by server.js not before mongoose completly sets up (which is done by /config/mongoose). But the connect() method is asynchronous. That means that the test will start running with the app, but before mongoose connects causing errors.

So we need to refactor our server.js file so that it returns a Promise[app] that eventually once, all configs gets resolver will resolve to the app.

Change config/mongoose.js

// mongoose
import mongoose from 'mongoose'

mongoose.Promise = global.Promise

export * from '../models/all'

export const CONNECTION_STRING_URL = 'mongodb://localhost/myappdatabase'

export default function (app) {
  return mongoose.connect(CONNECTION_STRING_URL)
}

Notice that it returns the connect return value (a promise)

Then the new version of server.js should use promises

import express from 'express'

import expressConfig from './config/express'
import mongoConfig from './config/express'

import index from './routes/index'
import users from './routes/users'

const app = express()

const setups = [expressConfig, mongoConfig]
const startUp = setups.map(setup =>  setup(app))

const appPromise = Promise.all(startUp).then(() => {
  app.use('/', index)
  app.use('/users', users)
  return app
})

export default appPromise

And we need to refactor all of our tests that use the app, to be done after the promise

Basically the test needs to be surrounded by

import request from 'supertest'

import appP from '../../src/server'

appP.then(app => {

   // ... test code here

})

And that's it. Run the tests.

results matching ""

    No results matching ""