Hierarchical routing with plain Express.js - node.js

I’m implementing a RESTful API using Node and Express. When it comes to routing, currently it looks like this:
var cat = new CatModel();
var dog = new DogModel();
app.route('/cats').get(cat.index);
app.route('/cats/:id').get(cat.show).post(cat.new).put(cat.update);
app.route('/dogs').get(dog.index);
app.route('/dogs/:id').get(dog.show).post(dog.new).put(dog.update);
I don’t like this for two reasons:
Both cat and dog models are instantiated whether I need them or not.
I have to repeat /cats and /dogs for every path schema
I’d love to have something like this (not working, of course):
app.route('/cats', function(req, res)
{
var cat = new CatModel();
this.route('/').get(cat.index);
this.route('/:id').get(cat.show).post(cat.new).put(cat.update);
});
app.route('/dogs', function(req, res)
{
var dog = new DogModel();
this.route('/').get(dog.index);
this.route('/:id').get(dog.show).post(dog.new).put(dog.update);
});
Is there a clean way in modern Express without any further modules (like express-namespace)? I could go for separate routers for each model and assigning them with app.use('/cats', catRouter). However, what if I have more than one hierarchy level like '/tools/hammers/:id'? I would then have routers within routers within routers, which seems like overkill to me.

I would then have routers within routers within routers, which seems like overkill to me.
Perhaps, but that is the built-in method of prefixing -- to app.use() a Router().
var cats = express.Router();
app.use('/cats', cats);
cats.route('/').get(cat.index);
cats.route('/:id').get(cat.show).post(cat.new).put(cat.update);
// ...
And, to have one Router .use() another to define multiple depths:
var tools = express.Router();
app.use('/tools', tools);
var hammers = express.Router();
tools.use('/hammers', hammers);
// effectively: '/tools/hammers/:id'
hammers.route('/:id').get(...);
Though, to be closer to your 2nd snippet, you can define a custom method:
var express = require('express');
express.application.prefix = express.Router.prefix = function (path, configure) {
var router = express.Router();
this.use(path, router);
configure(router);
return router;
};
var app = express();
app.prefix('/cats', function (cats) {
cats.route('/').get(cat.index);
cats.route('/:id').get(cat.show).post(cat.new).put(cat.update);
});
app.prefix('/dogs', ...);
app.prefix('/tools', function (tools) {
tools.prefix('/hammers', function (hammers) {
hammers.route('/:id').get(...);
});
});

Check out the new Router in Express 4. It sounds exactly what you're looking for.

Related

What is the difference between the two calls to express()

I have 2 require('express) calls.
First:
const express = require('express');
const app = express();
Second:
const Router = require('express');
const router = new Router();
What is the difference, why in the first we call a function, and in the second we create an object, if the methods are the same in both (use, get, post, etc)?
I think your question missed something. Your second example shows this:
const Router = require('express');
... but I think you meant to do this:
const Router = require('express').Router;
... regardless, the following should help you better understand.
In express, you can think of Routers as little mini applications... lightweight express apps... which have their own routing. In fact, the main "express" object is itself a Router. For example, you might have a bunch of endpoints for managing users:
// ./routes/user-routes.js
const userRoutes = new express.Router();
userRoutes.get('/', getAllUsers);
userRoutes.get('/:userId', getUserById);
userRoutes.post('/', createUser);
userRoutes.put('/:id', updateUser);
userRoutes.delete('/:id', removeUser);
Notice how none of the urls have anything like /users/ inside them. This is important because this little mini app can now be "mounted" (for lack of better terms) in a larger express app like follows:
const express = require('espress');
const userRoutes = require('./routes/user-routes');
const app = express();
app.use('/path/to/users', userRoutes);
Notice how the userRoutes were "mounted" on the /path/to/users such that all user requests will happen to the following URLs:
GET /path/to/users - get all users
GET /path/to/users/1234 - get user with id "1234"
... you get the point
This is mostly a convenient way to think about your app as a bunch of smaller mini apps which are orchestrated together.
Your second call is incorrect, you are just calling (requiring) express which is similar to your first call.
I never did const router = new Router();, so I'm not sure what that accomplish.
I generally do-
const router = require('express').Router();
router.get();
Even though with your first call you can do
app.get() and app.post()
According to express explanation
express.Router class is used to create modular, mountable route handlers. A Router instance is a complete middleware and routing system
Read more about it here
GeekforGeeks explains express.Router() very well

Can I pass variable to required file?

In express, I'm trying to move my minification to a requierd file:
app.js:
var app = express();
var minify = require("./minify.js");
In that file I try to set my template engine.
minify.js:
var app = express();
app.engine('html', mustacheExpress());
Later when I try to use to use the rendering engine in app.js, I get the error that no template-engine is set. It works if I run it all in the same file. I think the problem is that I declare the app-variable twice. How can I pass the app-variable into minify.js?
The problem is that you define new app variable, and you currently instantiate brand new express instance by calling express().
What you need to do is start using functions so that you can pass params (there are other methods too, but this is one that will work for you):
// app.js
var app = express();
var minify = require('./minify'); // don't include .js!
minify(app); // CALL the function that minify.js exports, passing params
// minify.js
module.exports = function(app) {
// because app comes as a parameter, it's the very same you've created in app.js
app.engine('html', mustacheExpress());
}
Again, there are many different methods and maybe proper approaches, depending on what you want to do, but this will do the job in your case. Read more about NodeJS and it's require system.
You can pass 'app' from app.js to your minify by using function in your module like Andrey said. You can do it like this too for example :
minify.js
module.exports = {
setAppEngine : function(app) {
app.engine( [...] );
}
}
And calling it like this in your app.js:
app.js
var app = express();
var minify = require("./minify.js").setAppEngine(app);
This solution is very useful because you can set and call others methods in minify.js. For example, you can do with the same code in minify.js:
app.js
var app = express();
var minify = require("./minify.js");
minify.setAppEngine(app);

Two way communication between routers within express app

I have an express app that has a router for different sections of my application, each contained within individual files. At the end of each file I export the router object like so.
var express = require("express");
var router = express.Router();
//routing handlers
module.exports = router;
However my problem is that I am trying to implement a feature were a user is allowed to edit a post that could be displayed on the front page, therefore in order to have the most current version of the user's post I need to be able to know when the user edits the post to make the necessary changes.
I have two modules one that handles dispatching the user's posts call this module B and another that handles editing call this module A. I need to be able to have module A include handler function and an array from module B, but I also need module B to be able to be notified when to make changes to the its array that module A requires.
I have tried
module A
var express = require('express');
var EventEmitter = require('events').EventEmitter;
var evt = new EventEmitter();
var router = express.Router();
var modB = require('moduleB');
router.evt = evt;
module.exports = router;
Module B
var express = require('express');
var router = express.Router();
var modA = require('moduleA').evt;
modA.on('myEvent',handler);
var myArray = [....];
router.myArray = myArray;
module.exports = router;
This gives me an undefined for modA and throws an error. I suspect it might be the order the modules are included but anyhow I would like to obtain some feedback since I sense that this might not even be good practice.
I think you are running into a common scenario for someone just starting out with express. A lot of people stick everything into routes/controllers when really the route should be very simple and just extract the data needed to figure out what the request is doing and then pass it to a service for most of the processing.
The solution is to create a Service and put the bulk of your logic and common code there, then you can wire up ModA and ModB to use the Service as needed.
EDIT with an example(not working but should give you a good starting point):
Shared Service
var EventEmitter = require('events').EventEmitter;
var evt = new EventEmitter();
module.exports = {
saveData: function(data) {
// do some saving stuff then trigger the event
evt.emit('myEvent', data);
},
onDataChange: function(handler) {
evt.on('myEvent', handler);
}
};
Module A
var service = require('SharedService.js');
// listen for events
service.onDataChange(function(e, data) {
// do something with the data
});
Module B
var service = require('SharedService.js');
// save some data which will cause Module A's listener to fire
service.saveData(data);
This example above hides the implementation of EventEmitter which may or may not be desirable. Another way you could do it would be to have SharedService extend EventEmitter, then your Modules could listen/emit directly on the service.

Express.js: Use Routers inside Routers

I have such router:
exports.read = (req,res) ->
// do stuff with DB
res.status(200).send
data:data
Now how can I use this router inside another router, call it exports.wrapper? I wnat to avoid having to rewrite my DB requests again and again. Is this approach that I have in mind recommended?
I would not recommend attempting to wrap routers inside each other.
It's recommended in Express 4 to use Express's router object like so:
// router.js
var myRouter = express.Router();
myRouter.route('/read', myController.readMethod);
Then in your controller you would handle the request and end with the result call:
// myController.js
exports.readMethod = function(req, res) {
var data = readFromDB(req.params);
res.send('read method renders', data);
}
exports.readMethod2 = function(req, res) {
var data = readFromDB(req.params);
res.send('read method 2 renders', data);
}
function readFromDB(params) {
// make a call to the DB (maybe via a model)
// return some data
}
Hope that helps
edit
Additionally, I would recommend wrapping your DB calls in a model, to abstract them away from your router or controller logic. For a reference of a well organised Express App that uses MVC checkout this Yeoman generator - https://github.com/ngenerio/generator-express-simple
second edit
In my very brief example externalising the readFromDB method to a model makes this function moot, if all it's doing is getting data from the DB put it in a model.

Unit testing express routers

I know this has been discussed a couple of times. Anyway, I feel like all the provided solutions don't (perfectly) fit to my requirement(s). I have the following code:
router.js:
------------------
var Router = function(app, resourceName, controller) {
//Create
app.post('/api/' + resourceName, function(req, res) {
console.log('Incoming request: ' + resourceName + ' (POST)');
controller.create(req, res);
});
};
module.exports = Router;
As you can see this is a very "generic" router. It can be instantiated for example in the server like this:
var app = express();
var userController = ...
var userRouter = new Router(app, 'Users', userController);
So I don't have to write a file per resource but I just have one generic router.
I would like to test my generic router but I see some problems:
How to "inject" the app? I could create an instance of Express (var app = express();) but I think a mock would be better (as this is a unit test, not an integration test!). What's the best way to get an appropriate mock?
What exactly should I test? As far as I see my router itself (without integration) isn't doing anything else but console output (not worth to test) and a call of a function (controller.create(req, res);). How should I test if this function is called? Or is there anything else to test?
You should probably make a stub implementation of app.
What you want to test is that the constructor registers listeners on specified routes + HTTP methods. I would advise putting Sinon.js stubs into your app stub, and then in your tests check that they are called with expected arguments.
I would use jasmine.createSpyObj to mock app (and maybe controller as well).
I think you just need to test that app.post gets called with the arguments '/api/' + resourceName and controller.create, because you aren't testing that express.post works correctly or not.
Here's how I'd do those two things specifically.
I'd modify router.js a little bit to make this easier:
var Router = function(app, resourceName, controller) {
app.post('/api/' + resourceName, controller.create.bind(controller))
}
module.exports = Router;
And then the test would look like this:
describe("Router", function() {
it("should route /api to controller.create", function() {
router = require('./router');
app = jasmine.createSpyObj('application', ['post']);
controller = jasmine.createSpyObj('controller', ['create']);
router(app, 'foo', controller);
expect(app.post).toHaveBeenCalledWith('/api/foo', jasmine.any(Function));
});
});
This isn't a perfect test because it isn't actually checking that controller.create specifically is getting called. That gets a little more complicated because of the .bind() stuff.
describe("Router", function() {
it("should route /api to controller.create", function() {
router = require('./router');
app = jasmine.createSpyObj('application', ['post']);
controller = jasmine.createSpyObj('controller', ['create']);
controller.create = jasmine.createSpyObj('binder', ['bind']);
controller.create.bind.and.returnValue('bar');
router(app, 'foo', controller);
expect(controller.create.bind).toHaveBeenCalledWith(controller);
expect(app.post).toHaveBeenCalledWith('/api/foo', controller.create.bind(controller));
});
});

Resources