9

In an attempt to modularize a large existing Node+Express+Mongoose application into multiple mountable apps, each developed as a separate NPM package, we're wondering whether sharing a single Mongoose instance between them is a good idea?

Let's say we have a suite of NPM packages each containing client-side assets, Mongoose models, and a REST-API implemented with Express. They do share a few common traits but are essentially to be considered separate reusable artefacts. A host application, also Express-based, mounts these under various root URIs:

var discussions = require('discussions'),
    tickets     = require('tickets'),
    events      = require('events'),
    express     = require('express'),
    app         = express();

var environment = { ...see below... };

...

app.use('/events-api', events(environment));
app.use('/tickets-api', tickets(environment));
app.use('/discussions-api', discussions(environment));

Now, since the events, tickets and discussions apps (separate NPM packages pulled in via the host package.json) use Mongoose, as do the host application itself, we figured we would pass in the host Mongoose instance through some kind of environment object that also include other stuff that the host wants to share with the mounted apps.

Do you see any obvious flaws with this approach? The mounted apps in this case would not specify Mongoose as a dependency in their respective package.json, and they would not require('mongoose') as normally done but instead get the Mongoose instance from the host which is responsible for connecting it to MongoDB.

If this is a bad idea and you suggest each sub-app declare a dependency towards Mongoose on their own, each NPM package would get their own copy of Mongoose and would each have to connect to MongoDB, right?

Some background info:

  • We really do want to include the apps in a host application, running in a single process, rather that having multiple Node instances. The host contains middleware for authentication and other things.
  • We do want to have the apps as separately developed NPM packages included as versioned dependencies of the various host applications that we build, rather than just copying their source to a host application.
  • We realize that reusing the same Mongoose instance between multiple mounted apps will have them share the same model namespace.

Edit: To clarify the package structure after all has been npm installed:

host/
  assets/
  models/
  routes/
  node_modules/
    express/ ...
    mongoose/ ...
    events/
      assets/ ...
      models/ ...
      routes/ ...
    tickets/
      assets/ ...
      models/ ...
      routes/ ...
    discussions/
      assets/ ...
      models/ ...
      routes/ ...

That is, the events, tickets, and discussions apps do not include Mongoose (or Express) of their own but are designed to rely on an always-present host application that suppliesd those dependencies.

We're assuming here that an NPM package like tickets cannot simply require stuff from the parent, right?

Greg
  • 313
  • 3
  • 8

3 Answers3

7

If you want to reuse your Mongoose package between other NPM packages, the best way to do it is to install the shared package at the top level app and then use it to initialize the other NPM packages.

In the top level:

var db = require('myMongooseDb'),
    events = require('events')(db),
    ...

Then your events package just needs to export a function that takes the db as a parameter.

Bill
  • 25,119
  • 8
  • 94
  • 125
  • Thanks a lot for your answer, but doesn't that mean that the core issue is just pushed one level of indirection? If each of our "mountable app" NPM package includes this new "DB" package as a dependency, they will all at some point have a nested Mongoose instance each in their respective `node_modules` which all need to be connected to MongoDB? My current assumption being that it is not enough for the root/host application to declare a dependency towards this DB package that its other NPM packages can `require`. Sorry if I misinterpreted you. – Greg Oct 05 '12 at 22:39
  • Oh sorry, I was confused. I thought you were just creating reusable modules, not actual NPM packages that you were installing. NPM is really for sharing packages between projects, not creating modules within a single project. What are you hoping to gain that using regular modules don't solve? It would seem to me that you will be duplicating lots of things going down the NPM path. – Bill Oct 05 '12 at 22:49
  • Added a small clarification to my question. The rationale for using NPM packages is that they are easily declared as dependencies of the host, in this case, with simple versioning so that multiple hosts can use different versions of the same sub-app directly from its `package.json`. The actual NPM packages have their own Git repos and development life-cycles, but I guess we could use Git sub-modules instead of managing them through the host `package.json`? Or is there another smart way of reusing modules? – Greg Oct 05 '12 at 22:56
  • Oh and one more thing, these NPM packages __will__ be shared among several applications that we develop, not just a single application. We will use them as building blocks for various client projects. – Greg Oct 05 '12 at 23:00
  • Thanks, that pretty much confirms the way we're doing it right now except with your valid suggestion that we encapsulate Mongoose in some Host API. Upvoted, but I'll keep the question open a little bit longer in case any brilliant alternative pops in, then I'll accept it. Thanks for your input! – Greg Oct 05 '12 at 23:15
  • it sounds like you want a local npm repository for installing packages from. http://stackoverflow.com/questions/7575627/can-you-host-a-private-repository-for-your-organization-to-use-with-npm – chovy Oct 06 '12 at 00:09
  • @chovy We install them directly from Git now, via npm, which is a little easier than setting up a private repo. – Greg Oct 06 '12 at 08:45
  • @Greg I've searching for alternatives for a month (or a little bit longer) but there's no good solutions except that one. I've decided to pass an `app` object to each submodule installed via npm and extend it there. Actually I also want async require of my submodules so it's more harded than just `require('submodule')(CommonObject);` but still it's the only way to do it right. – Alex Yaroshevich Nov 10 '13 at 15:19
3

I suggest you have a look at https://github.com/jaredhanson/node-parent-require, a recently published package which solved this issue for me.

The node-parent-require Readme file on the Github project page provides a detailed walkthrough using mongoose.

Basically, you need to dig in your submodule and replace this:

mongoose = require("mongoose");

... with this:

try {
  var mongoose = require('mongoose');
} catch (_) {
  // workaround when `npm link`'ed for development
  var prequire = require('parent-require')
    , mongoose = prequire('mongoose');
}

Don't forget to add mongoose as a peerDependency in your submodule's package.json. For example:

"peerDependencies": {
  "mongoose": "3.x"
}

You may also want to read http://blog.nodejs.org/2013/02/07/peer-dependencies/ first.

jbmusso
  • 3,436
  • 1
  • 25
  • 36
  • Great articles, and solution worked great for me. I just put the try..catch wrapper code in own file, and all packages reference it - thanks! – electblake Feb 24 '15 at 00:08
  • How would I use this in 2017 with ES6 / Typescript imports? "import" can not be wrapped in a try/catch block... – Matthias Max Dec 11 '17 at 09:50
0
const mongoose = require('mongoose-global')();

You can just require one global mongoose instance and use it anywhere.

https://www.npmjs.com/package/mongoose-global

Do Async
  • 4,081
  • 1
  • 22
  • 25