Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

node.js & express - global modules & best practices for application structure

I'm building a node.js app that is a REST api using express and mongoose for my mongodb. I've got the CRUD endpoints all setup now, but I was just wondering two things.

  1. How do I expand on this way of routes, specifically, how do I share modules between routes. I want each of my routes to go in a new file, but obviously only one database connection as you can see i've included mongoose at the top of people.js.

  2. Do I have to write out the schema of the model 3 times in my people.js? The first schema defines the model, then I list all the vars out in the createPerson and updatePerson functions. This feels like how I made php/mysql CRUD back in the day lol. For the update function, I've tried writing a loop to loop through "p" to auto detect what fields to update, but to no avail. Any tips or suggestions would be great.

Also, I'd love any opinions on the app as a whole, being new to node, it's hard to know that the way you are doing something is the most efficient or "best" practice. Thanks!

app.js

// Node Modules
var express     = require('express');
    app         = express();
    app.port    = 3000;



// Routes
var people      = require('./routes/people');

/*
var locations   = require('./routes/locations');
var menus       = require('./routes/menus');
var products    = require('./routes/products');
*/


// Node Configure
app.configure(function(){
  app.use(express.bodyParser());
  app.use(app.router);
});



// Start the server on port 3000
app.listen(app.port);



/*********
ENDPOINTS 
*********/

// People
app.get('/people', people.allPeople); // Return all people
app.post('/people', people.createPerson); // Create A Person
app.get('/people/:id', people.personById); // Return person by id
app.put('/people/:id', people.updatePerson); // Update a person by id
app.delete('/people/:id', people.deletePerson); // Delete a person by id

console.log('Server started on port ' + app.port);

people.js

//Database
var mongoose = require("mongoose");
mongoose.connect('mongodb://Shans-MacBook-Pro.local/lantern/');


// Schema
var Schema = mongoose.Schema;  
var Person = new Schema({  
    first_name: String,
    last_name: String,
    address: {
        unit: Number,
        address: String,
        zipcode: String,
        city: String,
        region: String,
        country: String
    },
    image: String, 
    job_title: String,
    created_at: { type: Date, default: Date.now },
    active_until: { type: Date, default: null },
    hourly_wage: Number,
    store_id: Number, // Inheirit store info
    employee_number: Number

});
var PersonModel = mongoose.model('Person', Person);  


// Return all people
exports.allPeople = function(req, res){
    return PersonModel.find(function (err, person) {
      if (!err) {
        return res.send(person);
      } else {
        return res.send(err);
      }
    });
}


// Create A Person
exports.createPerson = function(req, res){
    var person = new PersonModel({
        first_name: req.body.first_name,
        last_name: req.body.last_name,
        address: {
            unit: req.body.address.unit,
            address: req.body.address.address,
            zipcode: req.body.address.zipcode,
            city: req.body.address.city,
            region: req.body.address.region,
            country: req.body.address.country
        },
        image: req.body.image,
        job_title: req.body.job_title,
        hourly_wage: req.body.hourly_wage,
        store_id: req.body.location,
        employee_number: req.body.employee_number
    });

    person.save(function (err) {
        if (!err) {
            return res.send(person);
        } else {
            console.log(err);
            return res.send(404, { error: "Person was not created." });
        }
    });

    return res.send(person);
}


// Return person by id
exports.personById = function (req, res){
  return PersonModel.findById(req.params.id, function (err, person) {
    if (!err) {
        return res.send(person);
    } else {
        console.log(err);
        return res.send(404, { error: "That person doesn't exist." });
    }
  });
}


// Delete a person by id
exports.deletePerson = function (req, res){
  return PersonModel.findById(req.params.id, function (err, person) {
    return person.remove(function (err) {
      if (!err) {
          return res.send(person.id + " deleted");
      } else {
          console.log(err);
          return res.send(404, { error: "Person was not deleted." });
      }
    });
  });
}



// Update a person by id
exports.updatePerson = function(req, res){
    return PersonModel.findById(req.params.id, function(err, p){        
        if(!p){
            return res.send(err)
        } else {
            p.first_name = req.body.first_name;
            p.last_name = req.body.last_name;
            p.address.unit = req.body.address.unit;
            p.address.address = req.body.address.address;
            p.address.zipcode = req.body.address.zipcode;
            p.address.city = req.body.address.city;
            p.address.region = req.body.address.region;
            p.address.country = req.body.address.country;
            p.image = req.body.image;
            p.job_title = req.body.job_title;
            p.hourly_wage = req.body.hourly_wage;
            p.store_id = req.body.location;
            p.employee_number = req.body.employee_number;

            p.save(function(err){
                if(!err){
                    return res.send(p);
                } else {
                    console.log(err);
                    return res.send(404, { error: "Person was not updated." });
                }
            });
        }
    });
}
like image 446
Shan Robertson Avatar asked Sep 13 '13 15:09

Shan Robertson


2 Answers

I have taken another approach here. Not saying it is the best, but let me explain.

  1. Each schema (and model) is in its own file (module)
  2. Each group of routes for a particular REST resource are in their own file (module)
  3. Each route module just requires the Mongoose model it needs (only 1)
  4. The main file (application entry point) just requires all route modules to register them.
  5. The Mongo connection is in the root file and is passed as parameter to whatever needs it.

I have two subfolders under my app root - routes and schemas.

The benefits of this approach are:

  • You only write the schema once.
  • You do not pollute your main app file with route registrations for 4-5 routes per REST resource (CRUD)
  • You only define the DB connection once

Here is how a particular schema file looks:

File: /schemas/theaterSchema.js

module.exports = function(db) {
        return db.model('Theater', TheaterSchema());
}

function TheaterSchema () {
        var Schema = require('mongoose').Schema;

        return new Schema({
            title: { type: String, required: true },
            description: { type: String, required: true },
            address: { type: String, required: true },
            latitude: { type: Number, required: false },
            longitude: { type: Number, required: false },
            phone: { type: String, required: false }
    });
}

Here is how a collection of routes for a particular resource looks:

File: /routes/theaters.js

module.exports = function (app, options) {

    var mongoose = options.mongoose;
    var Schema = options.mongoose.Schema;
    var db = options.db;

    var TheaterModel = require('../schemas/theaterSchema')(db);

    app.get('/api/theaters', function (req, res) {
            var qSkip = req.query.skip;
            var qTake = req.query.take;
            var qSort = req.query.sort;
            var qFilter = req.query.filter;
            return TheaterModel.find().sort(qSort).skip(qSkip).limit(qTake)
            .exec(function (err, theaters) {
                    // more code
            });
    });

    app.post('/api/theaters', function (req, res) {
      var theater;

      theater.save(function (err) {
        // more code
      });
      return res.send(theater);
    });

    app.get('/api/theaters/:id', function (req, res) {
      return TheaterModel.findById(req.params.id, function (err, theater) {
        // more code
      });
    });

    app.put('/api/theaters/:id', function (req, res) {
      return TheaterModel.findById(req.params.id, function (err, theater) {
        // more code
      });
    });

    app.delete('/api/theaters/:id', function (req, res) {
      return TheaterModel.findById(req.params.id, function (err, theater) {
        return theater.remove(function (err) {
          // more code
        });
      });
    });
};

And here is the root application file, which initialized the connection and registers all routes:

File: app.js

var application_root = __dirname,
        express = require('express'),
        path = require('path'),
        mongoose = require('mongoose'),
        http = require('http');

var app = express();

var dbProduction = mongoose.createConnection('mongodb://here_insert_the_mongo_connection_string');

app.configure(function () {
        app.use(express.bodyParser());
        app.use(express.methodOverride());
        app.use(app.router);
        app.use(express.static(path.join(application_root, "public")));
        app.use('/images/tmb', express.static(path.join(application_root, "images/tmb")));
        app.use('/images/plays', express.static(path.join(application_root, "images/plays")));
        app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.get('/api', function (req, res) {
        res.send('API is running');
});

var theatersApi = require('./routes/theaters')(app, { 'mongoose': mongoose, 'db': dbProduction });
// more code

app.listen(4242);

Hope this was helpful.

like image 78
Slavo Avatar answered Oct 13 '22 23:10

Slavo


I found this StackOverflow post very helpful:

File Structure of Mongoose & NodeJS Project

The trick is to put your schema into models directory. Then, in any route, you can require('../models').whatever.

Also, I generally start the mongoose db connection in app.js, and only start the Express server once the connection is up:

mongoose.connect('mongodb://localhost/whateverdb')
mongoose.connection.on('error', function(err) {
  console.log("Error while connecting to MongoDB:  " + err);
  process.exit();
});
mongoose.connection.on('connected', function(err) {
  console.log('mongoose is now connected');
  // start app here
  http.createServer(app).listen(app.get('port'), function(){
    console.log('Express server listening on port ' + app.get('port'));
  });

});
like image 20
Harry Pehkonen Avatar answered Oct 14 '22 00:10

Harry Pehkonen