Get middleware mount point from request in Express - javascript

I have an Express application with a router, here is the example of the router:
const router = require('express-promise-router')();
const users = require('./users');
const validate = require('./validate');
router.get('/users', users.list);
router.get('/users/:id', users.get);
// other routes here
module.exports = router;
Now I want to add a middleware that validates each query, like that (this is not the working example, it's just to show the idea of what I want to accomplish):
const schemas = {
'/users': 'some validation schema',
'/users/:id': 'another validation'
}
module.exports = (req, res, next) => {
const url = req.originalUrl; // This is where I'm stuck.
if (!schemas[url]) {
// throw new error that validation failed
}
// validating somehow
if (!req.validate(schemas[url])) {
// throw new error that validation failed
}
return next();
}
And for this, I need to get the middlelware mount folder (like '/users/:id' for '/users/557'). I've tried to use req.originalUrl, but it returns the full URL path instead of the mount folder.
How can I achieve this? And if there's no way, how can I write my validation middleware another way to make it work?

Inside req.route you will get the path of API.
Check this screenshot

Related

Possible to use router.param() inside a nested router in express.js?

I'm working on a code challenge and trying to challenge myself to make something that works as well as something that is well organized.
I have a router set up and functioning. This router in turn uses an additional router. All is working so far. Now I am trying to run router.param() to preform some logic in the child router using the parameter that comes from the parent router. I have included {mergeParams: true} when creating the child router.
I would like to run additional logic using the parameter for all http methods in the child router, but don't seem to be able to. Can I not use router.param() for this purpose? I also seem to not have access to req.minion in the child router (which was attached using router.param() in the parent).
Alternatively is there a way to carry over logic from the router.param() in the parent router to the child router?
Parent router
const {getFromDatabaseById, getAllFromDatabase} = require('./db');
const express = require('express');
const workRouter = require('./workRouter.js');
const minionsRouter = express.Router();
minionsRouter.use('/:minionID/work', workRouter);
minionsRouter.param('minionId', (req, res, next, minionId) => {
const minion = getFromDatabaseById('minions', minionId);
if(minion){
req.minion = minion;
next();
}else{
res.status(404).send();
}
});
minionsRouter.get('/', (req, res, next) => {
res.send(getAllFromDatabase('minions'));
});
minionsRouter.get('/:minionId', (req, res, next) => {
res.send(req.minion);
});
//Some additional http methods here
module.exports = minionsRouter;
Child router
const {getAllFromDatabase} = require('./db');
const express = require('express');
const workRouter = express.Router({mergeParams: true});
workRouter.param('minionId', (req, res, next, minionId) => {
const allWork = getAllFromDatabase('work');
const minionWork = allWork.filter(work => work.minionId === minionId);
console.log(minionWork);
if(minionWork){
req.minionWork = minionWork;
next();
}else{
res.status(404).send();
}
});
workRouter.get('/', (req, res, next) => {
res.send(req.minionWork);
});
//Some additional http methods here
module.exports = workRouter;
The get methods in the parent router work perfectly, and the get method in the child router works if I move the logic in the router.param() call to it, but as is it does not seem to attach minionWork to req. If router.param() won't work for this, how can I avoid repeating that code for all http methods in the child router? Thanks!

add json data to middleware request

I am new to express and node, I working on a task where I want to add some json data to middleware request, here is the approach I am following:
In my middleware I want to add some details to request like current date and time and then extract the URL path. Here is the code I came up with:
var express = require('express');
var app = express();
module.exports = app.use('/some-url', function(req, res, next) {
let logdetails = {
id: "some-id",
datetime: Date.now(),
path: '/path-in-url'
}
req.logdetails= logdetails;
next();
} );
I am using module.exports so it this function is exported. But it is giving errors. Also what is the correct way to access the URL path, for example if URL is /users/john then I want to extract /john and my middleware should be applied only when URL starts with /users.
Also what is the correct way to access the URL path, for example if
URL is /users/john then I want to extract /john
If your request url is /users/john then req.path will give you the path of the request url, i.e. /users/john. To extract john, you can use named route parameters and define your route as /users/:name. After using named route parameter in your route, if your request url is /users/john, req.params.name will give you the route parameter, i.e. john. For more details, take a look at req.params on express docs
and my middleware should be applied only when URL starts with /users
following middleware will only run when request path is /users
app.use('/users', (req, res, next) => {
...
}
If you want the middleware then just export the middleware function. Not the Whole app.use part. I think app.use returns an Express object. And :param in your url will make sure that you can access that parameter. You can check out more about path patterns here
middleware.js
module.exports = function(req, res, next) {
let logdetails = {
id: "some-id",
datetime: Date.now(),
path: '/path-in-url'
}
req.logdetails= logdetails;
next();
}
Your other main.js file:
const express = require('express');
const app = express();
const middleWare = require('./middleware')
app.use('/users/:name', middleWare, (req, res)=>{
//you can access logDetails
// and the name
const name = req.params.name;//This will be john for /users/john
const logDetails = req.logdetails;
})
req.originalUrl returns the url path like shown below
// GET /search?q=something
console.dir(req.originalUrl)
// => '/search?q=something'
source: https://expressjs.com/en/api.html
Similarly, get the URL path and split the string accordingly and apply the condition you need.
Here if your originalPath is /users/john then
const result = "/users/john".split("/")
would result in ["", "users", "john"]
Check if the result[1]==="users" and write your condition in the middleware.
You don't have to export the middle ware.
middleware.js
module.exports = {
handler: (req, res, next) => {
const userName = req.params.name;
// add payload to request
next();
}
}
app.js
middleware = require("middleware");
app.use('/users/:name', middleware.handler);
Concerning URL path access you could get access using request params, e.g
app.use('/users/:name', (req, res, next) => {
const userName = req.params.name;
// add payload to request
next();
});
1. Register your specific URL - `/users/:name`, where `:name` is dynamic params e.g *john*
2. Grab params in the callback

Expressjs middleware static typed path and path with param conflict

I'm about to implement REST endpoints for authenticated users and non-authenticated users in expressjs. My current understanding of REST has led me to following design pattern:
User resource
Token user:
/users/self GET, PATCH
Non-token user:
/users POST
/users/:user_id GET
Profile image resource
Token user:
/users/self/profile-images POST
/users/self/profile-images/:profile_image_id PUT, DELETE
Non-token user:
/users/:user_id/profile-images GET
I'm struggling to figure out how to use this pattern without having :user_id parameter become self, i.e {user_id: 'self'}. I would want them to act as two isolated path types without interference, one strict and one dynamic. Is this possible? If so, how?
A code example of my current implementation looks like following:
// instPrivateUserRestRoutes.js (token-user)
router.use('/users/self', [
instAccountRestRoutes(restControllers),
instAuthenticationSessionRestRoutes(restControllers),
instPrivateProfileImageRestRoutes(restControllers)
])
// instPublicUserRestRoutes.js (non-token user)
router.use('/users/:user_id', [
instPublicProfileImageRestRoutes(restControllers)
])
// instRestApp.js (mount point for top resources)
router.use(instPrivateUserRestRoutes(restControllers))
router.use(instPublicUserRestRoutes(restControllers))
What you could do it to create a conditional routing middleware. The factory dunction takes as first argument a callback method that determines which router should be use, and as second argument a list of routers, and returns a new middle war that conditionally uses one of the routes.
function conditionalRouting(cond, routes) {
return function (req, res, next) {
try{
// get the index for the router that should be used
var idx = cond(req, res)
// call this router and pass req, res and next to it
routes[idx](req, res, next)
} catch(err) {
// in the case an error occurs in on of the steps "throw" that error
next(err)
}
}
}
You could then use it that way:
app.use(
'/users/:user_id',
conditionalRouting(
(req, _) => req.params.user_id === 'self' ? 0:1,
[routerForSelf, routerForUser]
))
An other way would be to handle this case explicitly with a middle ware that triggers a not found error:
function forceNotFound(req, res, next) {
var error = new Error('resource not found')
error.status = 404
next(error)
}
And add this as your last middleware
router.use('/users/self', [
instAccountRestRoutes(restControllers),
instAuthenticationSessionRestRoutes(restControllers),
instPrivateProfileImageRestRoutes(restControllers),
forceNotFound
])
This way it is clear that express should stop at that point.
This will look different to what the standard Cannot GET /the/path would look like, but you normally don't want to display those default error messages anyway.
If you want to have the same kind of message then you could write:
var parseUrl = require('parseurl')
var encodeUrl = require('encodeurl')
app.use((req, res, next) => {
var msg = 'Cannot ' + req.method + ' ' + encodeUrl(parseUrl.original(req).pathname)
var error = new Error(msg)
error.status = 404
next(error)
})
If you only want to handled numeric ids you could use this:
router.use('/users/:user_id(\\d+)', [
instPublicProfileImageRestRoutes(restControllers)
])
That way instPublicProfileImageRestRoutes would only be called if user_id is numberic.

using React router with Next JS route

i have simple question , i am new in Next.JS
we have a project and my web application manage routes in BackEnd with Next JS
now my problem is here , i want use React-Router-dom in one section
forexample before im working with Laravel and React
in Laravel I set My Route like This
Route::get('/reactPage/*' ...)
and then use Clien route with react
but i dont know how handle this with Next JS
( more details => for example i want user click to some link after that user see a page with some link inside of them , if user click that link , react-router-dom handle route and no any request send to Server )
I would recommend using the Next router. You do need to create a custom server in order to overload the default Next routing, but its a trivial task:
// server.js
const { createServer } = require('http');
const next = require('next');
const routes = require('./routes');
const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handler = routes.getRequestHandler(app);
app.prepare().then(() => {
createServer(handler).listen(port, err => {
if (err) {
throw err;
}
console.log(`> Ready on http://localhost:${port}`);
});
});
Then you can define routes, which I do in the routes.js file:
// routes.js
const nextRoutes = require('next-routes');
const routes = (module.exports = nextRoutes());
routes
.add('landing', '/')
.add('profile', '/profile', 'profile');

Express - Conditional Routing

At my website.com/v2/bridge/:locationId/process endpoint, the incoming req.body looks like this:
{
choice: 'a',
data: [
{
...
},
...
]
}
I want to access a particular route depending on what the value of req.body.choice is. If req.body.choice === 'a' then I want to go on to website.com/v2/bridge/:locationId/process/choiceA with the same req being passed on.
I don't know what middleware I need to use to accomplish that. I don't know if that is even possible.
My extremely simplified routes:
// website.com/v2/bridge
const proc = require('./process');
router.use('/:locationId/process', proc);
module.exports = router;
// website.com/v2/bridge/56/process
router.use(function (req, res, next) {
// ?????????????????????????
next();
});
const choiceA = require('./choice-a');
const choiceB = require('./choice-b');
router.use('/choice-a', choiceA);
router.use('/choice-b', choiceB);
module.exports = router;
// website.com/v2/bridge/56/process/choice-a
router.post('/', function (req, res) {
res.send('I got here.');
return;
});
module.exports = router;
What middleware function do I need to include to conditionally route my request? I am trying to avoid one giant function with if statements that process different things according to the value of req.body.choice.
This will be little trickier for you...give it a try
router.use(function (req, res, next) {
req.path = "/" + "choice-"+req.body.choice
req.url = "/" + "choice-"+req.body.choice
next();
});
now it'will do the request to the end point you want
As part of finding the answer for the same question, I came across with this question, which didn't settle my mind by messing with the req.url, so here's how I got it done (I know it's a long delay, but better late than never):
When you're dealing with routers, and you want to make a condition to decide whether to use, you can do it with two ways (according to expressjs doc), and let's learn them by examples
1. Skipping between routes
function skipThisRouteMiddleware (req, res, next) {
if (true === isAmazingValidation) {
return next('route');
}
return next();
}
router.get('/user/:id',
skipThisRouteMiddleware,
getUserV1 // TBD - your old route
)
router.get('/user/:id',
getUserV2 // TBD - your new route
)
In the case above, when you have two routes, and you want to conditionally pick one of them, it can be done by specifying a middleware that makes the validation for the first route only, and when needed, it triggers next('route') which skip to the next matching route, please note that you must specify METHOD and not generally app.use()
2. Skipping between routers
// routers/index.js
const mainRouter = express.Router();
mainRouter.use(oldRouter);
mainRouter.use(newRouter);
// routers/old.js
const oldRouter = express.Router();
function canUpgradeToNewRouter (req, res, next) {
if (true === isAmazingValidation) {
return next('router'); // now it's 'router' and not 'route'
}
return next();
}
oldRouter.use(canUpgradeToNewRouter);
// routers/new.js
const newRouter = express.Router();
newRouter.use(...);
In this case, you have two different routers, and you want to conditionally pick one of them. for this case, you'll have to create a parent router (mainRouter) and two nested routers (oldRouter, newRouter).
The trick here is that the oldRouter tries to run a middleware validation that tries to "upgrade" the requester to the new shiny router, if the condition is true, it will skip the whole oldRouter, and pass the stick to the parent router mainRouter to continue matching routes to this request (the magic - next('router')), which eventually pick the upcoming newRouter
In both methods, we let the first route to make the logic and choose between itself and the others, that's a different perception (a bit)

Categories