Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Structure of Express/Mongoose app

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).

like image 599
Ben Avatar asked Dec 08 '22 08:12

Ben


2 Answers

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

  • Models that abstract data and behavior in your application
  • Routes that constitute an external interface of your application. Routes are not the application!
  • Bootstrapping code (server.js) that is responsible to start and connect the parts of your application

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.

like image 135
saintedlama Avatar answered Dec 11 '22 08:12

saintedlama


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.

like image 38
Peter Lyons Avatar answered Dec 11 '22 07:12

Peter Lyons