How should I go about structuring my express/mongoose application, so that I can use my schemas, models, routes and the functions that get called when those routes are hit?
There are more or less two axes to organize your code. Organize code based on the layer functionality of your modules (database, model, external interface) or by functionality/context they act on (users, orders). Most (MVC) applications use a functional organization schema which is easier to handle but does not reveal the purpose or intend of an application.
Beside organizing code functional layers should be as decoupled as possible.
The functional layers in your code are
The code base above seems to use a functional organization schema, which is fine. The use of a modules
directory does not really make sense to me and seems superfluous. So we have a schema somehow like this
|- server.js
|+ users
|- schema.js
|- routes.js
Now let's break some dependencies...
schema.js
The schema/model part of the code should not depend on the app that represents an interface of your application. This version of schema.js
exports a model and does not require an express app or a mongoose instance to be passed into some kind of factory function:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var UserSchema = Schema({
username: { type: String, required: true },
password: { type: String }
});
// Use UserSchema.statics to define static functions
UserSchema.statics.userlist = function(cb) {
this.find().limit( 20 ).exec( function( err, users )
{
if( err ) return cb( err );
cb(null, users);
});
};
module.exports = mongoose.model( 'User', UserSchema, 'users' );
Obviously this misses the app.send
functionality from the original file. This will be done in the routes.js
file. You may notice that we do not export /api/v1/users
anymore but /
instead. This makes the express app more flexible and the route self contained.
See this post for a article explaining express routers in detail.
var express = require('express');
var router = express.Router();
var users = require('./schema');
// get all users
router.get( '/', function(req, res, next) {
users.userlist(function(err, users) {
if (err) { return next(err); }
res.send(users);
});
});
// get one user
router.get( '/:id', ...);
// add one new user
router.post( '/', ...);
module.exports = router;
This code omits implementations for getting one user and creating new users because these should work quite similar to userlist
. The userlist
route now has a single responsibility to mediate between HTTP and your model.
The last part is the wiring/bootstrapping code in server.js
:
// setup
var express = require("express");
var app = express();
var mongoose = require("mongoose");
mongoose.connect( 'mydb' ); // Single connection instance does not need to be passed around!
// Mount the router under '/api/v1/users'
app.use('/api/v1/users', require('./users/routes'));
// listen
app.listen( 3000 );
As a result the model/schema code does not depend on the application interface code, the interface has a clear responsibility and the wiring code in server.js can decide which version of the routes to mounter under which URL path.