How to structure a multiple post endpoints with node / express.js - node.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)

Related

how can I use router.all('path', Router Object') with Express?

I want to use router.all('path', RouterObject) for all method with right path to route to RouterObject.
const express = require("express");
const signup = require("./signup");
const router = express.Router();
router.all("/signup", signup);
router.use((_req, res) => res.status(404).send(`<h1>Page Not Found</h1>`));
./signup.js
const express = require('express');
const router = express.Router();
const controller = require('../controllers/signup');
router.get('/', res.send("OK");
module.exports = router;
but It doesn't work as what I was expected.
It shows 404 Page, not 'hello', when I request with 'GET /signup'.
I want to use the signup module with same path name for '/signup' (not '/signup/something')
I tried router.use("/signup", signup);, It works, but I don't want pass the request with path like path '/signup/something' to the signup module.
How can I fix the problem using router.all() or is there any better way?

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);

Prefix all requests in JSON Server with middleware

I'm using the JSON Server package (json-server) from https://github.com/typicode/json-server. I'd like to make the server prefix all requests with /api/v2. The documentation even gives an example how to do this with the following:
server.use('/api', router)
However I don't want to setup my own server instance but extend the default when running json-server.
Can I somehow use the above statement in a middleware?
Since the router that json-server returns to you is an Express router.
First define all of your /v1, /v2, etc in its own routes file as show below:
// api-routes.js
const express = require('express')
const jsonServer = require('json-server')
const router = express.Router()
const server = jsonServer.create()
const middlewares = jsonServer.defaults()
const v1Router = jsonServer.router('db-v1.json')
const v2Router = jsonServer.router('db-v2.json')
router.use('/v1', v1Router)
router.use('/v2', v2Router)
module.exports = router
Then mount your API router onto /api like so:
const express = require('express')
const apiRoutes = require('./api-routes')
const app = express()
app.use('/api', apiRoutes)
// ...
Should now have /api/v1 and /api/v2. Untested code above, but should give an idea what you need to do.

Node express param issue

we are using Node.js + Express as back-end for an Angular2 app. One of the modules of the back-end is responsible for the information gathering about products, based on barcode, which is a param for the Express router. Currently, we're facing an issue when adding a GET request route that does not use the barcode param.
The router file looks as follows:
'use strict';
const express = require('express');
const router = express.Router();
const prodCtrl = require('./controller');
const auth = require('../middlewares/authenticationBearer');
module.exports.init = function(app) {
router.param('barcode', prodCtrl.findById);
router.get('/products/:barcode', auth.bearer(), prodCtrl.getProduct);
router.post('/products/article', auth.bearer(), prodCtrl.getProductData);
router.get('/products/csv', auth.bearer(), prodCtrl.csv);
return router;
}
The problem is the /products/csv route. In its current position, calling the route gives a 404 error seemingly because the param barcode is expected, as the function prodCtrl.findById is called first. By moving this route before the /products/:barcode route like so:
'use strict';
const express = require('express');
const router = express.Router();
const prodCtrl = require('./controller');
const auth = require('../middlewares/authenticationBearer');
module.exports.init = function(app) {
router.param('barcode', prodCtrl.findById);
router.get('/products/csv', auth.bearer(), prodCtrl.csv);
router.get('/products/:barcode', auth.bearer(), prodCtrl.getProduct);
router.post('/products/article', auth.bearer(), prodCtrl.getProductData);
return router;
}
the code works fine. Is this a bug in Express? Or am I missing something here?
You should put '/products/:barcode' route after '/products/article' because it blocks the request to get to the article (the barcode param catch any suffix)

Express router param - chaining with URL

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'));

Resources