4

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@gmail.com',
                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.

Community
  • 1
  • 1
kl02
  • 580
  • 6
  • 24
  • try `app.use('/persons/*', persons);` – laggingreflex Dec 17 '15 at 04:57
  • No error, but still 404'd. :( – kl02 Dec 17 '15 at 04:58
  • I see below that you had typo error, that solved the problem? else i will look into it and figure it out. – NarendraSoni Dec 17 '15 at 05:08
  • Nope, the typo was from me editing once I copied over, and not the issue in the original. I wish it were that easy! When I add the router.use 'something is happening' part from http://stackoverflow.com/questions/23923365/how-to-separate-routes-on-node-js-and-express-4 question, that does work! It still won't read the rest, but that at least tells me the file's getting loaded, I suppose. – kl02 Dec 17 '15 at 05:22

3 Answers3

6

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.

Crogo
  • 483
  • 4
  • 8
  • I had to re-add mongoose so it'd recognize the schema for Persons, but the rest I kept like your example. I don't get any errors in the server console, but the browser says it's a 404, and the page is blank. I really do appreciate the explanation, but I have no idea why it won't work, or any idea of possible culprits (a conflict somewhere? a dependency? I don't know). Thanks, though! – kl02 Dec 17 '15 at 23:16
  • Just tried this setup in a dummy projet and it works fine for me. I assume you have your middlewares send some data ? If this persists then try `console.log` in some parts of your app / middlewares to see where your request is routed to. – Crogo Dec 18 '15 at 10:04
  • I have no doubt your version works, I just don't know why I can't get it to work. I do have my server.js console.logged within an inch of its life, which is how I knew that doing app.get in server.js and router.use in the /routes file worked, but I got errors if I tried to then hit route.anything-else. That's why I suspect it must be conflict, or a quirk introduced by something earlier in server.js. Will edit question to include that, so you can see. – kl02 Dec 18 '15 at 20:52
1

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!

kl02
  • 580
  • 6
  • 24
0

instead of

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

do:

persons.put('/persons/:user_id',function (req, res, next) {
  // etc
});
Nir Levy
  • 12,750
  • 3
  • 21
  • 38
  • I'd tried it both ways, actually, since I saw both patterns in the various other answers. What seemed to do the trick was changing `app.get` in server.js to `router.get` and then it suddenly all worked. I don't know why, since I didn't see that pattern in any answer, but it works, so... :) – kl02 Dec 17 '15 at 06:06