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?
server.js
// setup
var express = require("express");
var app = express();
var mongoose = require("mongoose");
app.db = mongoose.connect( 'mydb' ) );
// this is the bit I am not sure about
var UserSchema = require( './modules/users/schema' )( app, mongoose );
var routes = require( './modules/users/routes' )( app, mongoose, UserSchema );
// listen
app.listen( 3000 );
modules/users/schema.js
exports = module.exports = function( app, mongoose )
{
var UserSchema = mongoose.Schema(
{
username: { type: String, required: true },
password: { type: String }
});
var UserModel = mongoose.model( 'User', UserSchema, 'users' );
// it looks like this function cannot be accessed
exports.userlist = function( db )
{
return function( req, res )
{
UserModel.find().limit( 20 ).exec( function( err, users )
{
if( err ) return console.error( err );
res.send( users );
});
};
};
}
modules/users/routes.js
function setup( app, mongoose, UserSchema )
{
var db = mongoose.connection;
// get all users
app.get( '/api/v1/users', UserSchema.userlist( db) ); // this function cannot be accessed
// get one user
app.get( '/api/v1/users/:id', UserSchema.userone( db ) );
// add one new user
app.post( '/api/v1/users', UserSchema.addone( db ) );
}
// exports
module.exports = setup;
PS: The error I get is app.get( '/api/v1/users', UserSchema.userlist( db ) );
TypeError: Cannot call method 'userlist' of undefined
(routes.js).
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.
Take a look at my express_code_structure example repo for my recommendations on a module filesystem organization.
However, your code samples above are major MVC violations. Do not pass the app
instance to your model. Do not have your model have any awareness of req
objects or HTTP services whatsoever. Model: data structure, integrity, persistence, business logic, and nothing else. Routes should be defined in entirely separate .js files from your models.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With