Express subroute router, without having to use full path in each route - node.js

I'm splitting out part of the overall API as a separate subroute, i.e. (code simplified for this question)
const mainRouter = express.Router();
const bananaRouter = express.Router();
const appleRouter = express.Router();
bananaRouter.get('/banana/hello', (req, res) => { /* ... */ });
mainRouter.all('/banana/*', bananaRouter);
mainRouter.all('/apple/*', appleRouter);
This works. But I don't want to specify the full '/banana/hello' in every single route, and would much prefer '/hello'.
How can I do that?

Use .use() with a path prefix (no wildcard) instead of .all(). .use() will automatically match a path prefix without a wildcard whereas .get() and .all() will not and this will then set up your router better. So, do this to register the router:
// send all /banana prefixed urls to the bananaRouter
mainRouter.use('/banana', bananaRouter);
Then, inside of that router, you don't use the /banana part of the path:
// will match /banana/hello
bananaRouter.get('/hello', (req, res) => { /* ... */ });
In general, you will use .use() for routers and other middleware, not .all().

Related

How do I get the full route path including the parameters from express or extend the request object to do so?

I have the following route in my express (version 4.17.1) API in a postTimecardCompany.js file:
const mongoose = require('mongoose');
const Timecard = require('./../models/timecard');
function postTimecardCompany(server) {
server.post('/api/v1/timecard/:userId', (req, res) => {
// Insert timecard data into the database
Timecard.create(data, (error, result) => {
// Check for errors
if (error) {
res.status(500).end();
return;
}
// Respond
res.status(200).send({timecardId: result._id});
});
});
}
module.exports = postTimecardCompany;
The route (among other routes) is loaded via the following mechanism by server.js file:
[
'postTimecardCompany',
'anotherRoute',
'someOtherRoute',
'andSoOn...'
].map((route) => {
require('./core/routes/' + route + '.js').call(null, server)
});
I have a middleware (in server.js file) where I check which route is being called.
server.use((req, res, next) => {
// If route is "/api/v1/timecard/:userId" do something
});
I have found various solutions which do nearly what I am looking for, but not exactly.
For example, if I post to the route with a data parameter userId value of "123f9b" then req.originalUrl gives an output of "/api/v1/timecard/123f9b."
What i'm looking to get is the original route path with the parameters in it so for a request of "/api/v1/timecard/123f9b" it would be: "/api/v1/timecard/:userId."
How do I get this functionality in express or extend express to get the original route path with parameters in the request object?
if you want to use from your approach, it's is impossible, after that your approach is not standard in express check the documentation, if you want get routes in a middleware you should try like this:
server.js
const express = require('express')
const server = express()
const postTimecardCompany = require('./routes/postTimecardCompany.js')// don't use map()
server.use("/",postTimecardCompany)//use the routes
server.listen(6565,()=>console.log(`Listening to PORT 6565`))
routes of postTimecardCompany.js
use Router of express and export router, and you can use middleware before each route you want, there are many ways to use middleware in routes, check the documentation
const express = require("express");
const router = express.Router();
const middleware = require('../middleware');//require middlewares
router.post("/api/v1/timecard/:userId", middleware,(req, res) => {
// Insert timecard data into the database
console.log(req.route.path);
});
module.exports = router;
middleware.js
module.exports = ((req, res, next) => {
console.log(req.route.path);
next()
});

Express - can't import routes

I have a following structure,
// app.js
const express = require("express");
const app = express();
app.get("/", require("./routes/index"));
app.get("/users", require("./routes/users"));
app.listen(3000);
// /routes/index.js
const express = require("express");
const router = express.Router();
router.get("/", (req, res) => res.send("index"));
module.exports = router;
// /routes/users.js
const express = require("express");
const router = express.Router();
router.get("/login", (req, res) => res.send("login"));
router.get("/register", (req, res) => res.send("register"));
module.exports = router;
If I use app.use(...) inside app.js then the routes work correctly but I want to use app.get since I want to block any other accessible method.
Right now the index route works fine but other routes does not work.
Working sandbox: https://codesandbox.io/s/cocky-cartwright-h82d2?fontsize=14&hidenavigation=1&theme=dark
The correct way to achieve what you're trying is to utilize the 'use' function.
You said you don't want to use the app.use because you want to block any other accessible method, but utilizing the 'use' function won't allow anything that wasn't declared on your routers, so you don't need to worry about that.
What app.use does is register a middleware function to your app on the specified route. So, the code below:
app.use("/users", require("./routes/users"));
Will make that every request that match the pattern '/users' will utilize the function you provided (in this case, the router inside the users.js file).
So, if someone sends a POST request to /users/register, per example, he will get a 404, because you never created a route to handle a post on that path.
And just to make it clear why the app.get don't work the way you did, let me give a brief explanation.
When you use the app.get function, it'll be expected that the request path matches exactly the one you provided. So, when you do the following:
app.get("/users", require("./routes/users"));
The server will be expecting a request that matches /users exactly. So, something like /users/example will not trigger the callback function.
The thing is, the function to handle the request in the code above is another router:
router.get("/login", (req, res) => res.send("login"));
router.get("/register", (req, res) => res.send("register"));
So, the router will expect that the request path matches /users/login or /users/register exactly. But, if a request path matches /users/register, it will not match /users, so nothing will be called.
The reason why your '/' path works right now is because your router is expecting the same path you used on the app.js file. So, a request to '/' will match both patterns.
you can't use router.get and pass a router
router.get expect to get a method (function).
you should use app.use or use controllers like that
// /controllers/index.js
exports.renderIndex = (req, res) => {
res.send("index")
}
and in app.js
// app.js
const express = require("express");
const app = express();
const indexController = require("./controllers/index");
app.get("/", indexController.renderIndex);
app.listen(3000);

catch plural route in expressjs

in expressjs, I use routing like below;
app.use('/game', require("./routes/game"));
in the file /routes/game.js
const express = require('express');
var router = express.Router();
router.get("s", function (req, res, next) {
res.send("GAME LIST");
})
router.get("/:gameurl", function (req, res, next) {
res.send(`GAME: ${req.params.gameurl}`);
})
module.exports = router;
I'd like to catch both /games and /game/wow
How can I manage to handle both routes separately?
If you want both /games and /game to go to your router, but not any other top level paths, then there are a number of ways to specify it. You can see them described here in the doc. For example, you could use a regex or pass multiple strings. In this case, I'll show you the multiple strings:
app.use(["/game", "/games"], require("./routes/game"));
For the route path, you can pass a single string, a path pattern (an Express subset of regex), a regex, or an array that contains any combination of these.
If you want to be able to tell the difference between /game and /games in your router, then you will have to examine req.originalUrl to see which one caused it to go to your router which seems to me to kind of defeat part of the purpose of routing in the first place.
Thus, sending two separate top level paths to the same router and routing them differently inside the router is not a design that works well with Express. Personally, I'd either change my path design so this doesn't happen or use two routers as that fits better with the router mechanics.
you can do something like this
app.use('/', require("./routes/game"));
in the file /routes/game.js
const express = require('express');
var router = express.Router();
const base = '/game';
router.get(`${base}s`, function (req, res, next) {
res.send("GAME LIST");
})
router.get(`${base}/:gameurl`, function (req, res, next) {
res.send(`GAME: ${req.params.gameurl}`);
})
module.exports = router;

How to customise get method with different queries in Express.js with Node.js?

I'm currently building a REST API using Node.js with Express.js and I'm quite new to this technology. The following code shows the get method to a list of councils stored in MongoDB.
const { Council } = require('../mongoose-models/council');
const express = require('express');
const router = express.Router();
router.get('/', async (req, res) => {
const query = req.query;
const councilsList = await Council.find(query);
if (!councilsList) return res.status(404).send('No councils found.');
res.send(councilsList);
});
module.exports = router;
From my previous experience when developing REST API using java, I can customise different queries by implementing different methods with their own paths. For example:
#Path("findByCouncilName/{councilName}")
#Path("findCouncilsNotInMyArea/{longitude}/{latitude}")
And within each method, I can then write different logics. However, in Express.js, it seems that I have to implement all these different logics into one block. It seems not flexible and how can actually implement it? Furthermore, does the query must be same as the key name in MongoDB? What if I want to filter the results based on a specified index element in a nested array in a document?
For your routes:
#Path("findByCouncilName/{councilName}")
#Path("findCouncilsNotInMyArea/{longitude}/{latitude}")
If you are to implement them in express, you can split them into different blocks actually.
Instead of listening to '/' and try to handle everything inside, you can try this.
const express = require('express');
const router = express.Router();
router.get('/findByCouncilName/:councilName', async (req, res) => {
const councilName = req.params.councilName;
// Your logic goes here
res.send();
});
router.get('/findCouncilsNotInMyArea/:longitude/:latitude', async (req, res) => {
const longitude = req.params.longitude;
const latitude = req.params.latitude;
// Your logic goes here
res.send();
});
module.exports = router;
You can use it like lets say:
router.get('/:councilName', async (req, res) => {
Then use the parameter in the route with :
req.params.councilName
Express doc is your friend
https://expressjs.com/en/guide/routing.html
Here is everything you should know about express routing.
You can specify individual logic for every pat-method pair, and again, use general as needed.
You need to be aware of path order in which Express resolves them, eg. first path to match will will be executed.

Node.js Express nested resources

i'd like to nest resources like the below eg.
/// fileA.js
app.use('/dashboards/:dashboard_id/teams', teams);
[...]
///teams.js
router.get('/', function(req, res, next) {
[...]
}
but I can't get the req.params["dashboard_id"] parameter in teams because it seems it is already substituted with the parameter value.
I've tried to baypass the problem by calling an intermediate function and pass somewhere the parameter but I can't figure out where ...
Do you have an answer?
Thanks,
Franco
You may try this solution:
//declare a function that will pass primary router's params to the request
var passPrimaryParams = function(req, res, next) {
req.primaryParams = req.params;
next();
}
/// fileA.js
app.use('/dashboards/:dashboard_id/teams', passPrimaryParams);
app.use('/dashboards/:dashboard_id/teams', teams);
///teams.js
router.get('/', function(req, res, next) {
var dashboardId = req.primaryParams['dashboard_id']; //should work now
//here you may also use req.params -- current router's params
}
Using Express 4.0.0 or above at the time of this writing:
To make the router understand nested resources with variables you will need create and bind a new router using app.use for every base path.
//creates a new router
var dashboardRouter = express.router();
//bind your route
dashboardRouter.get("/:dashboard_id/teams", teams);
//bind to application router
app.use('/dashboards', dashboardRouter);
This way Express will see the first part of the path and go to the /dashboards route, which has the :dashboard_id/teams path.
You can use the mergeParams option here
// fileA.js
app.use('/dashboards/:dashboard_id/teams', teams);
// teams.js
const router = express.Router({ mergeParams: true })
router.get('/', function(req, res, next) {
// you will have access to req.params.dashboard_id here
}
You can also see this answer: Rest with Express.js nested router

Resources