kl02
kl02

Reputation: 582

Breaking out express routes into separate files

I have tried every answer I've found on s/o, and I'm sure I must be missing something. What doesn't error on me instead gives me a 404. I tried answers from Organize routes in Node.js, strongloop's route-separation pattern, the answers from How to include route handlers in multiple files in Express?, hit similar errors as in Router.use requires middleware function? but none of those answers worked, either. The answer for Unable to Split Routes into Separate Files in Express 4.0 doesn't error, but also 404s. It seems like each answer has a different syntax and style, and maybe it's that I'm mixing and matching incorrectly?

Right now my /routes/persons.js has this pattern:

    var express = require('express');
    var persons = express.Router();

    persons.route('/persons/:user_id')
        .put(function (req, res, next) {
            // etc
    });

    module.exports = persons;       

In my server.js file, I've got:

    var persons = require('./routes/persons');
    app.use('/persons', persons);

This combination doesn't throw errors, but it also doesn't do anything. I've tried adding the endpoint to server.js lines:

    var persons = require('./routes/persons');
    app.get('/persons/:user_id', persons.addpersons);

and stripping persons.js down to just export functions:

    exports.addpersons = function (req, res, next) {
            var list = req.body;
            // etc
    }

Plus variations like wrapping the whole person.js file in module.exports = function(), sticking module.exports = router at the end, using app instead of router, etc.

What am I overlooking? Should I be adding some other middleware, rearranging how I call the endpoint, using app, or sticking with router.route? What are the most likely culprits when there's no error but the endpoint is still 404'ing?

many thanks in advance!

============= EDITED TO INCLUDE SERVER.JS =============

Since it's clear something is set wrong, somewhere, here's my server.js file:

        var express = require('express');
        var app = express();
        var methodOverride = require('method-override');
        var mongoose = require('mongoose');
        var bodyParser = require('body-parser');
        var router = express.Router();
        var jwt    = require('jsonwebtoken');
        var config = require('./config');
        var nodemailer = require('nodemailer');
        var bcrypt = require('bcrypt-nodejs');
        var crypto = require('crypto');
        var async = require('async');

        var transporter = nodemailer.createTransport({
            service: 'gmail',
            auth: {
                user: '[email protected]',
                pass: 'password'
            }
        });

        // I don't know if both are necessary, used multiple conflicting tutorials
        app.use(require('express-session')({
            secret: 'secret',
            resave: false,
            saveUninitialized: false
        }));
        app.set('superSecret', config.secret);

        var Schema = mongoose.Schema,
            Person = require('./models/person.js'),
            User = require('./models/user.js'),
            Event = require('./models/event.js');

        var port = process.env.PORT || 8080;
        mongoose.connect(config.database);

        app.use(bodyParser.json());
        app.use(bodyParser.json({ type: 'application/vnd.api+json' }));
        app.use(bodyParser.urlencoded({ extended: true }));
        app.use(methodOverride('X-HTTP-Method-Override'));
        app.use(express.static(__dirname + '/public'));

        // routes go here

        app.use('/api', router);
        app.listen(port);
        console.log('gogogo port ' + port);

I have no idea where else I might look for why including routes requires such a break in the usual pattern. My config files? My procfile? Those are the only other files sitting on the server, not counting /models and /routes.

Upvotes: 4

Views: 3817

Answers (3)

kl02
kl02

Reputation: 582

I FIGURED IT OUT!

Of course, this might be the totally wrong way to go about it (pls tell me if so) but it WORKS.

in my server.js file, I have:

    var persons = require('./routes/persons');
    router.get('/persons/:user_id', persons);
    router.post('/persons/:user_id', persons);

and my persons.js file now looks like this:

    var mongoose = require('mongoose');
    var express = require('express');
    var router = express.Router();

    var Schema = mongoose.Schema,
        Person = require('../models/person.js');

    router.post('/persons/:user_id', function (req, res) {
        var potatoBag = req.body;
        Person.collection.insert(potatoBag, function onInsert(err, potatoBag) {
            if (err) {
                return res.json(err);
            } else {
                res.status(200).end();
            }
        });
    });

    router.get('/persons/:user_id', function(req, res) {
        var id = req.params.user_id;
        Person.find({'user_id':id},function(err, person) {
            if (err)
                return res.json(err);
            res.send(person);
        });
    });

    module.exports = router;

This seems like more overhead than most of the examples, but maybe it's because of a) using router.route and b) using imported schemas? I also had (req, res, next) in there, and it threw fits until I removed the next pieces. Probably still a bit awkward, but hey, it's working. Thanks for the help, everyone!

Upvotes: 1

Crogo
Crogo

Reputation: 493

The key here is to understand what app.use() does to your req object (in particular to req.path), how app.get() and friends are different, and how Express wraps path-to-regexp (its internal path matching module) to handle routes.

1) app.use(path, middleware) mounts the middleware. Inside the mounted middleware/router, req.path is relative to the mount path. Only the beginning of the request path needs to match, so /foo will work for requests at /foo (relative path will be /), /foo/bar (relative path is /bar), etc.

app.use(function (req, res, next) {
    console.log('Main: %s %s', req.method, req.path);
    next();
});

app.use('/foo', function (req, res) {
    console.log('In /foo: %s %s', req.method, req.path);
    res.send('Got there');
});

Try running the setup above, navigate to localhost/foo and see the following logs:

Main: GET /foo
In /foo: GET /

2) app.get(path, middleware), app.post(path, middleware) etc. do not mount the target middlewares, so req.path is preserved. req.path must match the whole pattern you defined your route with, so /foo will only work for /foo requests.

app.use(function (req, res, next) {
    console.log('Main: %s %s', req.method, req.path);
    next();
});

app.get('/foo', function (req, res) {
    console.log('In /foo: %s %s', req.method, req.path);
    res.send('Got there');
});

Navigate to localhost/foo and see :

Main: GET /foo
In /foo: GET /foo

3) app.route(path), as explained in the Express docs, is just a convenience to define multiple app.get(middleware), app.post(middleware) etc. sharing the same path.


Now in your case, here is a working setup:

main

var persons = require('./routes/persons');
app.use('/persons', persons);

routes/persons.js

var router = require('express').Router();
router.route('/:user_id')
  .post(function (req, res) {
    // handle potato data
  })
  .get(function (req, res) {
    // get and send potato data
  });

module.exports = router;

This is convenient as you only have to set the /persons entry point once in your main file, so you can easily update it later on if needed (you could also import that path value from a config file, from your router object or whatever, Node is pretty flexible in this regard). The persons router itself takes care of its business controllers, regardless of where it is exactly mounted at.

Upvotes: 7

Nir Levy
Nir Levy

Reputation: 12953

instead of

persons.route('/persons/:user_id')
    .put(function (req, res, next) {
        // etc
});

do:

persons.put('/persons/:user_id',function (req, res, next) {
  // etc
});

Upvotes: 0

Related Questions