Fullstacking: Connecting NodeJS ↔ MongoDB

heymarkkop

Mark Kop

Posted on August 29, 2019

Fullstacking: Connecting NodeJS ↔ MongoDB

With React Native, NodeJS + KoaJS and MongoDB set up, we can start connecting them with each other.

KoaJS ↔ MongoDB

As I want to learn and practice Test-Driven Development, we'll be creating tests first and it seems that Mocha is recommended over Jest to test Mongoose (which we'll be using later).
We'll also be using supertest for integration testing.

yarn add mocha --dev
yarn add supertest --dev

Create a test file like this:

// server.test.js
const request = require('supertest');
const app = require('./koa');

describe('Server', () => {
  it('is running', done => {
    request(app)
      .get('/')
      .expect(200, done);
  });
});
Enter fullscreen mode Exit fullscreen mode

and change package.json as following:

// package.json
...
  "scripts": {
    ...
    "test": "mocha server.test.js --watch",
    ...
  },
...
Enter fullscreen mode Exit fullscreen mode

I've used mocha './server/*.test.js' --recursive --watch instead so it runs all test files inside server folder. We'll probably change this later.

Run yarn test and find that TypeError: app.address is not a function because app doesn't exist yet thus it's time to write the actual code

// server.js
const Koa = require('koa');

const app = new Koa();

app.use(async ctx => {
  ctx.body = "Hello World, I'm Koa";
});

module.exports = app.listen(3000, () =>
  console.log('Running on http://localhost:3000/'),
);
Enter fullscreen mode Exit fullscreen mode

Don't forget to module.exports it.
Now our first test is passing, but it can throw Uncaught Error: listen EADDRINUSE: address already in use :::3000 when trying to run the test again or --watching it.
We have to close the server after each test, so add this inside describe() block

// server.test.js
...
 afterEach(() => {
    app.close();
  });
...
Enter fullscreen mode Exit fullscreen mode

With Koa working and being tested, we can now try to read some information from our MongoDB instance.

Let's try to find our stampler by adding this test

// server.test.js
...
  it('finds our stampler', done => {
    request(app)
      .get('/')
      .expect(/Stampler/)
      .expect(200, done);
  });
Enter fullscreen mode Exit fullscreen mode

It'll return Error: expected body 'Hello World, I\'m Koa' to match /stampler/ because ctx.body is a plain text, not the data in our database.
To access it, we'll use Mongoose
yarn add mongoose or npm install mongoose

Create a Product.js to define a new schema

// Product.js
var mongoose = require('mongoose');

const ProductSchema = mongoose.Schema({
  title: String,
});

module.exports = mongoose.model('Product', ProductSchema);
Enter fullscreen mode Exit fullscreen mode

And use it as it follows

// server.js
const Koa = require('koa');
const mongoose = require('mongoose');
const Product = require('./Product');

const app = new Koa();

mongoose.connect('mongodb://127.0.0.1:27017/test', {useNewUrlParser: true});

app.use(async ctx => {
  //ctx.body = "Hello World, I'm Koa";
  ctx.body = await Product.find({});
});

module.exports = app.listen(3000, () =>
  console.log('Running on http://localhost:3000/'),
);

Enter fullscreen mode Exit fullscreen mode

Note that your MongoDB should be running or you'll get

MongoNetworkError: failed to connect to server [127.0.0.1:27017] on first connect [MongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017]
Enter fullscreen mode Exit fullscreen mode

You can check it by running mongo in the terminal and
sudo services mongodb start to start it.

It should work at first, however you might notice an error if --watching the test suite:
OverwriteModelError: Cannot overwrite 'Product' model once compiled.
To fix it, add this beforeEach like you did with afterEach, so it deletes the Product model before testing again.

// server.test.js
...
  beforeEach(async () => {
    const url = 'mongodb://127.0.0.1:27017/test';
    await mongoose.connect(url, {useNewUrlParser: true});
    delete mongoose.connection.models.Product;
  });
...
Enter fullscreen mode Exit fullscreen mode

While we're on it, let's try to add a new product, check if it exists and clean it after. Let's also separate some describes

// server.test.js
const request = require('supertest');
const mongoose = require('mongoose');
const app = require('./app');
const Product = require('./Product');

describe('Server', () => {
  describe('without acessing MongoDB', () => {
    afterEach(() => {
      app.close();
    });

    it('is successful', done => {
      request(app)
        .get('/')
        .expect(200, done);
    });

    it('finds our stampler', done => {
      request(app)
        .get('/')
        .expect(/Stampler/)
        .expect(200, done);
    });
  });

  describe('acessing MongoDB direcly', () => {
    afterEach(() => {
      Product.deleteOne({title: 'skate'}).exec();
    });

    beforeEach(async () => {
      const url = 'mongodb://127.0.0.1:27017/test';
      await mongoose.connect(url, {useNewUrlParser: true});
      delete mongoose.connection.models.Product;
    });

    it('creates and finds a skate', done => {
      const skate = new Product({title: 'skate'});
      skate.save();
      request(app)
        .get('/')
        .expect('Content-Type', /json/)
        .expect(/skate/)
        .expect(200, done);
    });
  });
});

Enter fullscreen mode Exit fullscreen mode

This probably isn't the optimal and most elegant way to test integration, but I'll leave the comments open for hints and suggestions

References

Hackernoon - API testing using SuperTest
Bits and Pieces- Build a Unit-Testing Suite with Mocha and Mongoose
Zellwk - Connecting Jest and Mongoose
SmoothTerminal - Build an API with Koa.js

💖 💪 🙅 🚩
heymarkkop
Mark Kop

Posted on August 29, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related