8

I'm going all in and doing a project using only node. It's been a lot of fun, but sometimes I get a little lost in it and I want to try to gain understanding as I get confused so I build it correctly and don't get too overwhelmed. Anyway, here's the problem:

I have REST API that's using Express and mysql. I set up mysql:

app.js

//Variables, move these to env
var dbOptions = {
    host: config.db_config.host,
    user: config.db_config.user,
    password: config.db_config.password,
    port: config.db_config.port,
    database: config.db_config.database
};
app.use(myConnection(mysql, dbOptions, 'single'));

and then I include my routes, passing the routes the app and log middleware so I can use them in the route:

app.js cont.

var userRoute = require('./routes/users.js')(app,log);
app.use('/users', userRoute);

That actually was a little confusing, but now I get it, I have to pass them into module in order for the module to get the data.

Then in my route file I want to use an object so that I can use the same functionality in other routes, but in the user file in order to use the same connection pool that everything else is using, or at least to not have to set up the connection again I have to pass it the response and request? There's gotta be a better way to do that. It's really ugly that way. Here's the relevant part of

routes/users.js

var User = require('../controllers/User.js');

module.exports = (function(app,log) {
var userR = express.Router();

userR.post('/register', function(req,res){
        var email = req.body.email;
        var password = req.body.password;
        var firstName = req.body.first_name;
        var lastName = req.body.last_name;
        var userId;

        try {
            var query;
            var status = 200;
            var response = '';

            var newUser = {
                email: email,
                password:password,
                first_name: firstName,
                last_name: lastName,
                password: password
            };

            var user = new User(req,res);

            user.register(newUser);
...

};

controllers/User.js

module.exports = function User(req,res) {
this.id = 0;
    this.register = function(newUser){

        var _this = this;
        var deferred = q.defer();
req.getConnection(function(err,connection){
...

There must be a pattern I'm missing here. I should just be able to pass the app or something and have access to the req.getConnection etc.

Thanks.

Z2VvZ3Vp
  • 7,033
  • 6
  • 21
  • 35

1 Answers1

21

There's kind of a lot going on here, but I'll take a crack at this one.

My first recommendation would be to try to keep your routers pretty slim. It's not labeled, but I'm assuming the largest snippet of code you've provided is your router. There are lots of opinions of course, but if I were doing this here's what my router would look like. I'm assuming you're using Express 4x.

routes/users.js

var User = require('../controllers/user.js');
var userRouter = express.Router();
userRouter.post("/register", User.register);
module.exports = userRouter;

So what we've done here is eliminated the dependency on your app/log middleware.

This means your main file (what I usually call app.js), will look like this:

app.js

var userRouter = require('./routes/users.js');
app.use('/users', userRouter);

This is also where you can put any middleware you chose (logging, body-parsing, error-handling, etc).

In the later versions of Express the above code actually mounts our userRouter to "/users". In this case you will now have a "/users/register" route.

So now since we've pulled out some of the logic of the router, we have to put that somewhere. Typically a router will talk to a controller so let's take a look:

controllers/user.js

var User = require("../models/user.js")
var register = function(req, res, next){
    var email = req.body.email;
    var password = req.body.password;
    var firstName = req.body.first_name;
    var lastName = req.body.last_name;
    var userId;
    var params = {
            email: email,
            password:password,
            first_name: firstName,
            last_name: lastName,
            password: password
        };
    var newUser = new User(params);

    try {
       newUser.register();
        // do other things...
    }
};
module.exports = {register: register};

The first thing you'll notice is that I would have a UserModel file. By doing this, we've de-coupled our model object from this route. Let's say for instance we have a new register route (maybe one of them has an email + pw, the other registers through FB and we have to store different things). We should be able to use the same function (user.register in this case) specified in our model without having to change a whole bunch of things!

Now, here's what a UserModel might look like:

/models/user.js

var connection = require("../lib/connection.js");
var User = function(params){
   this.email = params.email;
   // ...etc
};

User.prototype.register = function(newUser){
    connection.getConnection(function(error, connection){
        //connection.doWhatever();
    });
};

module.exports = User;

Now we've finally gotten to the core of your question. At the top, you'll see we have a connection file. This is where we're going to put all of our DB-related logic like our connection pools.

/lib/connection.js

/*
This will be in some JSON config we'll say
var dbOptions = {
    host: config.db_config.host,
    user: config.db_config.user,
    password: config.db_config.password,
    port: config.db_config.port,
    database: config.db_config.database
};

*/
//This will depend on which version/module/db you're using, but here's what mine looks like
var MySQL = require("mysql");
var config = require("../config/db.json");
connectionPool = MySQL.createPool({host: config.db_config.host, ...});

var getConnection = function(done){
   connectionPool.getConnection(done);
};

module.exports = {getConnection: getConnection};

So, in conclusion, instead of passing your connection along with your request object, we now simply include our short connection module in whichever model file we're in, kindly ask for a connection, and do whatever processing we need to. Also note you'll have to release the connections back into the pool, but I'll leave that as an exercise for you :).

Also, I usually write CoffeeScript, so please excuse any small errors. Let me know if you have need for further clarification.

Cheers, Brennan

Brennan
  • 1,764
  • 1
  • 14
  • 17
  • 1
    Wow! What a detailed answer, thank you. I am still digesting this whole thing, but this makes it a lot clearer how the express apps should be structured. I think that the thing that was confusing me the most was that the node package 'myConnection' that I'm using extends the request object to give mysql functionality, which doesn't really make sense. Doesn't look like I need it at all. One initial question: Is it generally better practice to write the functions separate than the module export as you did? I've just been wrapping everything in the module.exports = { ... } Thanks again. – Z2VvZ3Vp Jan 30 '15 at 00:59
  • 2
    Ah, I wasn't sure about the `myConnection` bit. I've never seen that before. It makes more sense to me to do it this way, because not every request will need a connection to the database. I find that exporting everything at the bottom is cleaner for two reason. First, you can keep your code flatter, instead of immediately starting off one level deep. Secondly, assuming you're consistent, you'll know where to look in all of your files for the exports. This wouldn't be the case if you started to add helper methods you didn't want to export with the other way. – Brennan Jan 30 '15 at 16:13
  • 2
    I'm happy to answer any other questions, but if you found my answer helpful, don't forget to accept it! – Brennan Jan 30 '15 at 16:20
  • 1
    How does User.register have access to req,res,next without passing them in the function call from the route? – Z2VvZ3Vp Feb 02 '15 at 21:04
  • 2
    User.register is a variable containing a function with a signature of (req, res, next). What the router is doing is associating the function with the path. [Here's a simple example of what's happening](http://stackoverflow.com/questions/13286233/javascript-pass-function-as-parameter). – Brennan Feb 02 '15 at 21:33
  • 1
    Oh, right just like in normal js. Thanks. Sorry I was just getting lost in it there. Thanks for all the help, I owe you a drink :) – Z2VvZ3Vp Feb 02 '15 at 21:55
  • 1
    Not a problem. Happy to help! – Brennan Feb 02 '15 at 22:01