Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unsure of how to approach Data Access Object/Layer in an Express / MongoDB Application

I have an Express application running with MongoDB. I want to separate out my database access from the server level. However, to get the result of a database call I can only seem to do one of two things:

Pass Res as an argument

//server.js
...
var dbApi = require('../data/db-api.js');
...
app.get('/api/user', dbApi.getUsers(function (data) {
  res.send(data);
}));
...

//db-api.js
...
getUsers: function (callback) {
  MongoClient.connect(url, function (err, db) {
  if (err) {
    throw err;
  }

  db.collection(collections.Users)
    .find({})
    .toArray(function (error, documents) {
      db.close();
      callback(documents);
    });
  });
}
...

Assume Express req/res paradigm in db-api.js

//server.js
...
var dbApi = require('../data/db-api.js');
...
app.get('/api/user', dbApi.getUsers);
...

//db-api.js
...
getUsers: function (req, res) {
  MongoClient.connect(url, function (err, db) {
  if (err) {
    throw err;
  }

  db.collection(collections.Users)
    .find({})
    .toArray(function (error, documents) {
      db.close();
      res.send(documents);
    });
  });
}
...

However, I feel both of these approaches add implicit dependencies I would prefer to avoid. I would prefer to call dbApi independently in server.js such that it returns a result set I can manipulate before returning, i.e.:

//server.js
...
var dbApi = require('../data/db-api.js');
...
app.get('/api/user', function (req, res) {
  var result = dbApi.getUsers();
  //do stuff with result as necessary
  res.send(result);
});
...

//db-api.js
getUsers: function () {
  MongoClient.connect(url, function (err, db) {
  if (err) {
    throw err;
  }

  db.collection(collections.Users)
    .find({})
    .toArray(function (error, documents) {
      db.close();
      return documents;
    });
  });
}

But this last one doesn't seem to want to work, as the documents aren't returned to the server level (result is undefined). I know this is because I'm trying to do something synchronously that is inherently asynchronous.

So, I suppose, what I'm looking for is any advice on best practices regarding app architecture as it pertains to separating out the data access layer.

like image 617
Thomas Preston Avatar asked Apr 14 '15 01:04

Thomas Preston


2 Answers

Bit late to the party, but in my opinion the split should be that the Express handler deals with the HTTP request, and your other method deals with the database.

Expanding your original script and using callbacks:

//server.js
...
var dbApi = require('../data/db-api.js');
...
app.get('/api/user', (req, res) => {
  try {
    dbApi.getUsers((documents) => res.send(documents))
  } catch (error) {
    // or something along those lines
    res.status(500).send({ error: error.message });
  }
});
...

//db-api.js
...
getUsers: function () {
  MongoClient.connect(url, function (err, db) {
  if (err) {
    throw err;
  }

  db.collection(collections.Users)
    .find({})
    .toArray(function (error, documents) {
      db.close();
      if (error) {
        throw error;
      }
      return documents;
    });
  });
}

Sam H. is right as well, I'd promisify/async this so it becomes more intuitive, the current versions of the Mongodb client on node will return a promise if you don't supply a callback:

//server.js
...
const dbApi = require('../data/db-api.js');
...
app.get('/api/user', async (req, res) => {
  try {
    const documents = await dbApi.getUsers();
    res.send(documents)
  } catch (error) {
    // or something along those lines
    res.status(500).send({ error: error.message });
  }
});
...

//db-api.js
...
getUsers: async function () {
  const db = await MongoClient.connect(url);
  const collection = await db.collection(collections.Users);
  const query = await collection.find({});
  const documents = await query.toArray();
  await db.close();
  return documents;
}

Or with Promises:

//server.js
...
const dbApi = require('../data/db-api.js');
...
app.get('/api/user', (req, res) => {
  dbApi.getUsers()
    .then(documents => res.send(documents))
    .catch(error => res.status(500).send({ error: error.message })
});
...

//db-api.js
...
getUsers: function () {
  return MongoClient.connect(url)
    .then(db => Promise.all([
      db,
      db.collection(collections.Users).find({}).toArray()
    ]))
    .then(([db, documents]) => Promise.all([documents, db.close()])
    .then(([documents]) => documents)
}
like image 147
Trolleymusic Avatar answered Oct 23 '22 16:10

Trolleymusic


Well, you can use a promisified version of the mongo client, return the promise of that value, and use async/await. See, for example, this answer.

like image 40
Sam H. Avatar answered Oct 23 '22 16:10

Sam H.