Express router param - chaining with URL - node.js

I have a full CRUD API defined in Express, and I'd like to remove the duplication of base and use the snazzy route function, but I fear it's not possible.
Current:
var router = express.Router();
var base = '/api/foo/bar/';
router.get(base, this.list);
router.get(base + ':id', this.read);
router.post(base, this.create);
router.put(base + :id', this.update);
router.del(base + :id', this.del);
Desired:
var router = express.Router();
router.route('/api/foo/bar')
.get(this.list)
.get(':id', this.read)
.post(this.create)
.put(':id', this.update)
.del(':id', this.del)
The problem is that the verb functions (get, post, put, del) do not accept a string as their first parameter.
Is there a similar way to achieve this?

Important: Using this technique will work, but we aware that as of Express 4.3.2, all subroutes defined on the nested router will not have access to req.params defined outside of it, nor param middleware. It's completely quarantined. This, however, is subject to change in a later 4X version. See https://github.com/visionmedia/express/issues/2151 for more (up to date) info.
How about this instead:
// api.js
var router = express.Router();
router
.route('/')
.get(this.list)
.post(this.create);
router
.route('/:id')
.get(this.read)
.put(this.update)
.del(this.del);
module.exports = router;
// app.js / main.js
app.use('/api/foo/bar', require('./api'));
or if you want to chain all of them at once:
// api.js
var router = express.Router();
router
.get('/', this.list)
.get('/:id', this.read)
.post('/', this.create)
.put('/:id', this.update)
.del('/:id', this.del);
module.exports = router;
// app.js / main.js
app.use('/api/foo/bar', require('./api'));

Related

How to structure a multiple post endpoints with node / express.js

How can I successfully configure my routes to access multiple methods on my controller accordingly to the endpoint and parameters passed to the URL?
When accessing /companies/:companyId/createCheques I would like to call the method createCheques in cheques controller but it is still calling createCheque.
I tried adding the line below to routes/cheques.js but it did not work.
router.route('/:companyId/createCheques').post(createCheques)
// routes/companies.js
const express = require('express')
const {
getCompanies,
getCompany,
deleteCompany,
createCompany,
updateCompany,
} = require('../controllers/companies')
// Include other resource routers
const userRouter = require('./users')
const chequeRouter = require('./cheques')
const redeemRouter = require('./redeems')
const router = express.Router({ mergeParams: true })
// Re-route into another resources routers
router.use('/:companyId/users', userRouter)
router.use('/:companyId/cheques', chequeRouter)
router.use('/:companyId/createCheques', chequeRouter)
router.use('/:companyId/redeems', redeemRouter)
router
.route('/')
.get(getCompanies)
.post(createCompany)
router
.route('/:id')
.get(getCompany)
.put(updateCompany)
.delete(deleteCompany)
module.exports = router;
// routes/cheques.js
const express = require('express')
const {
getCheques,
getCheque,
deleteCheque,
createCheque,
createCheques,
updateCheque
} = require('../controllers/cheques')
// when more than 1 url param is possible to the same route, mergeParams must to be set to true
const router = express.Router({ mergeParams: true })
// Advanced results
const Cheque = require('../models/Cheque')
const advancedResults = require('../middleware/advancedResults')
router
.route('/')
.get(advancedResults(Cheque, 'cheques'), getCheques)
.post(createCheque)
.post(createCheques)
router
.route('/:id')
.get(getCheque)
.put(updateCheque)
.delete(deleteCheque)
module.exports = router;
The problem you have is that you are defining two POST controllers for the exact same route.
If you want to call both controllers createCheque and createCheques, when a POST is done to :companyId/createCheques/, you only need to add next() on the last line of createCheque, just like a middleware. See https://expressjs.com/es/4x/api.html#router
If you only want to call one controller, then you need to create a separate route for the other controller on your routes/cheques.js file.
router
.route('/cheques') //here the complete path would be companyId/createCheques/cheques
.post(createCheques)

Express Router - different routes with same suffixes

I have 2 routes:
/api/system/list - list my systems
/api/system/:systemId/books/list - list books of current system
And different file for each API:
systemAPI.js:
const list = router.get('/list', Validate(Validation.list), listHandler)
return {
routes: [list]
}
bookAPI.js:
const list = router.get('/list', Validate(Validation.list), listHandler)
return {
routes: [list]
}
Finally, use the above routes:
express.use('/api/system', systemAPI.routes)
express.use('/api/system/:systemId/book', bookAPI.routes)
The problem is, when I'm entering list-books API (/api/system/:systemId/books/list), its actually calling the list-systems API (/api/system/list)
Update: Solved!
I had 2 main problems:
routes order (in app.use(..))
use different instance of Express.Router() on each API
Refer the answer below for more information.
Try reversing the order of the routes and this will probably solve your problem:
express.use('/api/system/:systemId/book', bookAPI.routes);
express.use('/api/system', systemAPI.routes);
The reason is that express evaluates routes 'greedily' and it will even resolve to partial matches. So, it is important to keep the more specific routes first before the more general ones.
Also, I think you are using the express router wrong according to the documentation the systemAPI:
const express = require('express');
const router = express.Router();
router.get('/list', Validate(Validation.list), listHandler)
module.exports = router;
The bookAPI route:
const express = require('express');
const router = express.Router();
router.get('/list', Validate(Validation.list), listHandler)
module.exports = router;
Finally import the routers and use them:
const express = require('express');
const bookRouter = require('./bookAPI'); //set the file paths based on your file structure
const systemRouter = require('./systemAPI');
const app = express();
app.use('/api/system/:systemId/book', bookRouter);
app.use('/api/system', systemRouter);

express.Router() vs express() in express

As mentioned in express routing guide and this answer, we can create "mini-app" and use it from the main app. However I saw a code where it uses app instead of router in the module
app.js
var express = require('express');
var userRoutes = require('./routes/user');
var app = express();
app.use('/user', userRoutes);
module.exports = app;
routes/user.js
var express = require('express');
var app = express(); // not express.Router() !!
app.get('/:name', function(req, res) {
var userName = req.params.name;
res.render('user.jade', {
userName: userName
});
});
module.exports = app;
I assumed the correct usage in routes/user.js should be
router = express.Router()
instead of
app = express()
but app = express() also works! what are the differences and why router = express.Router() is better?
When you are working with a server where there are many routes, it can be confusing to leave them in a Main file together. The let router = express.Router() option works differently than let app = express().
While the app returns an app object, router will return a small app fragment, similar to the app, where you will use logic to call them later on the Main.
The most important, about your question, is that a router, which is isolated, will not interfere with others in the application, being a single environment.
https://expressjs.com/en/api.html#router
A router object is an isolated instance of middleware and routes. You can think of it as a “mini-application,” capable only of performing middleware and routing functions. Every Express application has a built-in app router.
A router behaves like middleware itself, so you can use it as an argument to app.use() or as the argument to another router’s use() method.

Express JS Router Middleware Abstraction

I have developed an API using Node.js + Express JS and i use token-based authentication.
I used two differents routers in this api, userRoute (/USER) and postRoute (/POST). The postRoute can be used within authentication but userRoute needs the token.
To solve that i use a router middleware for userRoute but it interferes with portRoute
This is the code:
...
var postRoute = express.Router();
var userRoute = express.Router();
// Route middleware to verify a token
userRoute.use(function(req, res, next) {
security.checkToken(req,res, next);
});
userRoute.route('/users')
.get(userCtrl.findAllUsers)
.post(userCtrl.addUser);
postRoute.route('/posts')
.get(userCtrl.findAllPosts)
.post(userCtrl.addPost);
app.use(userRoute);
app.use(postRoute);
...
If i try to access '/posts' the servers checks the token and does not let me in. I know if i change the order of app.use it works, but i dont understand why works in this way if i am using "Router Middleware".
Someone knows?
This happens because if the express router implementation, you will be able to understand it quite easily if you have a look at it. Here is the path: node_modules/express/lib/router/index.js. Every time that you call the Router(), like in your case
var postRoute = express.Router();
var userRoute = express.Router();
this function will be invoked:
var proto = module.exports = function(options) { ... }
and it is true that there is a different router instance that is being returned every time. The difference is in the way that the use is registering the middlewares. As you see the use it is registered against the proto.use
proto.use = function use(fn) { ... }
this means that the middleware that you register there they will be registered for every instance of the router that you define.

express route wildcard both sides

I want each route containing the phrase Privacy to be rendered the same.
So I used http://forbeslindesay.github.io/express-route-tester to make sure:
/(.*)Privacy(.*)
Should work.
However - the following code still gets me 404 on valid examples:
/Privacy
/aaaPrivacy
/aaaPrivacyaa
var express = require('express');
var router = express.Router();
router.get('/(.*)Privacy(.*)', function(req, res) {
res.render('privacy', {'isPrivacy': true});
});
module.exports = router;

Resources