I'll start with some context:
I have a RESTful API Server that includes routes for managing users; e.g.
PUT .../users/:id/profile
As part of our authentication flow we verify the user's identity and compare the id in the url with the id retrieved from our IDP.
What I'm trying to do is implement a 'me' replacement approach; where a user, instead of providing their id in the url, can simply provide 'me' instead, and the id is reassigned with id retrieved from the IDP based on their authentication. I've seen Google do this for some of their APIs. E.g.
.../users/me/profile
So far I've been attempting a simple replacement with the assumption that a modified req.params will propagate forward:
req.params.id = req.params.id === 'me'
? session.id
: req.params.id
This wasn't working so I did a bit of reading up on it and apparently req.params gets built with each middleware function (when .use() is called), so reassigning req.params.id doesn't propagate throughout the app as I had hoped.
Currently all our business logic pulls the id from req.params so I was wondering if there's a way to get the above behaviour to work to avoid refactoring all my business logic?
I understand that req.params is built from parsing the url; so would modifying the url be an option?; to replace 'me' with the id; so that req.params is populated as intended? If so, how could this be achieved?
Or, if you have a nicer alternative to getting the above 'me' replacement behaviour to work then I'm all ears!
Global middleware (that you add using app.use() or router.use()) is typically not even aware of any route handlers that declare parameters, so req.params usually isn't even populated.
A solution would be to "inline" the modifying middleware:
app.put('/users/:id/profile', middleware, ...)
But that would require rewriting all your route handlers.
As you already point out correctly, you can modify the URL:
app.use(function(req, res, next) {
if (req.url === '/users/me/profile') {
req.url = '/users/1234/profile';
}
next();
});
app.put('/users/:id/profile', ...)
Instead of having to match each URL specifically, you can perform substring matches, or use a list of all URL's that may contain an :id parameter (and therefore, may contain the me identifier).
I just realized that this should work (using req.param()):
router.param('id', function(req, res, next, id) {
if (id === 'me') {
req.params.id = '1234';
}
next();
});
This should be added to routers that declare parameters, either in route handlers directly, or in mountpoints.
A convoluted example:
let childRouter = express.Router({ mergeParams : true });
childRouter.put('/profile', ...);
let parentRouter = express.Router({ mergeParams : true });
parentRouter.use('/:id', childRouter);
parentRouter.param('id', function(req, res, next, id) {
if (id === 'me') {
req.params.id = '1234';
}
next();
});
app.use('/users', parentRouter);
Because parentRouter declares a mountpoint /:id, it should get the resolving function.
Related
I have a middleware function that takes a query string value and turns it into
I am using Express 4 and Node.js. I have a route defined for /:module/:page.aspx and am using req.params and req.query values across a number of middleware functions.
app.get('/:module/:page.aspx', catchEncodedQuery, doSomeStuff, doOtherStuff);
I have one middleware function that appears first, which catches certain requests which have a query string value that can be transformed into a new req.url that I set for use further on in the middleware chain. Basically, I want to proceed as though the un-encoded form of this query string value was the request URL the whole time.
function catchEncodedQuery(req, res, next) {
if (
// req meets some specific criteria
) {
req.url = myUnencodeFunction(req.query.url_str);
next();
} else {
next();
}
};
But I would also like req.params and req.query to reflect this new URL. I could just parse the new URL myself and reassign these values to req.params and req.query, but I'm wondering if there's a better way to do this that leverages Express's existing URL-parsing functionality.
I have an isomorphic react app and I would like to somehow pass state between express middleware.
I have the following express route that handles form submission:
export const createPaymentHandler = async (req: Request, res: Response, next: NextFunction) => {
const { field } = req.body;
if (!paymentType) {
res.locals.syncErrors = { field: 'some error.' };
next();
return;
}
try {
const { redirectUrl } = await makeRequest<CreatePaymentRequest, CreatePaymentResponse>({
body: { paymentType },
method: HttpMethod.POST
});
res.redirect(redirectUrl);
} catch (err) {
error(err);
res.locals.serverError = true;
next();
}
};
The next middleware is handling the rendering.
At the moment I am using res.locals, is there a better way or a recognised pattern?
IMO your question is more about passing some data to the next middleware. Since the rendering logic is handled by the next middleware, the express route shouldn't be concerned by how the data is being used. Your approach looks fine.
res.locals is the recommended way of passing data to the next middleware. From the docs:
This property is useful for exposing request-level information such as the request path name, authenticated user, user settings, and so on.
Also, since the variables added will be scoped to the current request, thus the data will only be available for the current request's lifecycle. Perhaps you can set a convention of adding a state key on the res.locals to store all your state variables, but the current approach would also work fine.
res.locals is a standard way to pass data to the next middleware in the scope of the current request. Since your use case is around the current request, it makes sense to do so.
At the same time, the standard way to handle errors is to pass the error to the next middleware.
next(err);
Then you can handle the error scenario from the error handler. However, for an isomorphic react app, this would make things harder. So if you decide to go down that path, I would suggest you to use a custom error like PaymentError by extending Error. This would make even more sense since you are already using Typescript.
However, when you actually think about this scenario, when the error is not a request error, from the point of view of the react app, it is a special state/property of rendering. Thus I suggest the following hybrid approach.
If the error is of high priority, that is, if the error should stop rendering the expected content and fallback to a special page, use the next(err) approach.
If the error should just be part of the state report, then use the res.locals approach.
Because your handler is async, you need to pass the err into next, like so:
next(err);
In order for your middleware to process the error, instead of it being picked up by the default error handler, you need to have four parameters:
app.use((err, req, res, next) => {
// handle the error
})
It's also worth noting that error handlers need to be specified after other middleware. For your case, it might make sense to use a normal "success" middleware alongside an error handler, rather than combining the two into one middleware.
Finally, keep in mind that passing err as a parameter is specific to error handlers. If you just want to pass some data into your next middleware, you would do that by modifying the req:
req.x = 'some data'
next()
Then, the next middleware's req parameter will have the data you set.
Further reading: https://expressjs.com/en/guide/using-middleware.html#middleware.error-handling
If it's passing lightweight information to the next middleware for rendering purposes then applying res.locals is fine. However, you might want to look into custom error-handling for general errors, such as internal error.
Consider the following error handling
function notFoundHandler(req, res, next) {
res.status(404).render('notFoundPage', {
error: '404 - not found'
});
}
function badRequestHandler(err, req, res, next) {
res.status(400).render('badRequestPage', {
error: 'Bad request'
});
}
function errorHandler(err, req, res, next) {
res.status(err.status || 500).render('errorPage', {
error: 'Internal server error'
});
}
app.use(notFoundHandler);
app.use(badRequestHandler);
app.use(errorHandler);
Now instead of passing error details to the next middleware you would simple let it flow to the error handlers, e.g.
export const createPaymentHandler = async (req: Request, res: Response, next:
NextFunction) => {
const { field } = req.body;
if (!paymentType) {
res.status(400);
return next(); // This will hit the Bad Request handler
}
try {
const { redirectUrl } = await makeRequest < CreatePaymentRequest, CreatePaymentResponse > ({
body: { paymentType },
method: HttpMethod.POST
});
res.redirect(redirectUrl);
} catch (err) {
res.status(500);
return next(err); // This will hit the Error Handler
}
};
The best way to pass a state between express middleware is to use the object res.locals what you already do.
You are on the correct and best way!
May be you have to look to the documentation from res.locals one time again:
Citate from documentation of res.locals
res.locals – an object that contains response local variables scoped to the
request, and therefore available only to the view(s) rendered during
that request / response cycle (if any). Otherwise, this property is
identical to app.locals.
This property is useful for exposing request-level information such as
the request path name, authenticated user, user settings, and so on.
app.use(function(req, res, next)
{
res.locals.user = req.user;
res.locals.authenticated = ! req.user.anonymous;
next();
});
And you can see – they recommend to use this object.
You are on the right way!
I am working with node/express/passport/ looking at code that attempts to use a request like:
req._parsedUrl.pathname;
I cannot figure out where this variable is coming from. Is this a canonical variable name that is set in a common .js library? It doesn't seem exposed in any headers.
req._parsedUrl is created by the parseurl library which is used by Express' Router when handling an incoming request.
The Router doesn't actually intend to create req._parsedUrl. Instead parseurl creates the variable as a form of optimization through caching.
If you want to use req._parsedUrl.pathname do the following instead in order to ensure that your server doesn't crash if req._parsedUrl is missing:
var parseUrl = require('parseurl');
function yourMiddleware(req, res, next) {
var pathname = parseUrl(req).pathname;
// Do your thing with pathname
}
parseurl will return req._parsedUrl if it already exists or if not it does the parsing for the first time. Now you get the pathname in a save way while still not parsing the url more than once.
You can write a middleware to handle then set properties for req.
var myMiddleWare = function () {
return function (req, res, next) {
req._parsedUrl = 'SOME_THING';
next()
}
};
app.get('/', myMiddleWare, function (req, res) {
console.log(req._parsedUrl); // SOME_THING
res.end();
})
Express middleware document in here
First of all, i have searched the solution to this problem and i didn't found anything. Sorry if it's duplicated.
I have in my express+node.js app two endpoints like this:
// Gets a tweet by unique id
app.get('/tweets:id', function(req, res, next) {
// Response management
});
// Gets mentions of user unique id
app.get('/tweets/mentions', function(req, res, next) {
// Response management
});
The problem is that requesting a GET petition to "/tweets/mentions", is attended first by "/tweets/:id" and later by "/tweets/mentions", making a conflict.
I have tried to change the declaration order of the endpoints, but always the request is attended by both endpoints.
Also I have tried things like "/tweets::mentions", but I need to access the endpoint via "/tweets/mentions", and I suppose there is a possible way.
How can i resolve this conflict?
Thanks.
Are you using next() in one of the handlers?
next() passes control to the next matching route, so in your example, if one of them is called and inside it you call next(), the other one will be called.
I allways recommend to use 'Router' if you have more than one base path because it helps you to keep it organized.
You can resolve the conflict by checking the value of req.params.id in the "tweet by id" handler.
For routes with additional parameters is always recommended to not use the same base path of other routes.
Something like could work for you:
app.get('/tweets/users/:id', function(req, res, next) {
// Response management
});
// Gets mentions of user unique id
app.get('/tweets/mentions', function(req, res, next) {
// Response management
});
More specifically, I have an auth system that uses passportjs and req.user is defined if the user is authenticated.
Right now my website only has about 5 pages, but it's growing, and at the top of every route, I check if req.user exists and I pass a true or false variable to the rendered template, and the template renders accordingly.
I messed around with things such as app.get("*") but I didn't end up finding anything good.
How could I check if req.user (or anything else that could exist within req...) exists -- when a user goes to any page of my website, without repeting code?
Progress:
Using this code in app.js:
app.use(function (req, res, next) {
// Using req.locals.isAuthenticated would be better, as it's automatically passed to every rendered templates.
req.context = {};
req.context.isLoggedIn = req.isAuthenticated();
// req.locals.isAuthenticated = req.isAuthenticated();
next();
});
app.use('/dashboard', dashboard);
and this in the routes/dashboard route:
router.get('/', function (req, res) {
res.render('dashboard', { isLoggedIn: req.context.isLoggedIn });
});
Works - I can then see if the user is logged in by doing for example {{ isLoggedIn }}.
However when I uncomment the req.locals line in the first code snippet, I get a 500 error.
Two things to note:
Usually when your application needs to do something for a bunch of different pages, you want to setup a middleware function via app.use
Express has a res.locals variable whose properties will be included in any rendered template
With the above points in mind, you can construct something like the following:
app.use(function(res, req, next) {
res.locals.isAuthenticated = typeof(req.user) !== 'undefined';
next();
});
You then supply your additional template variables when your routes call res.render. For example:
app.get('/about', function(res, req) {
res.render('about', { 'testValue': 14} );
});
Your template will have access to both isAuthenticated and testValue.
I recommend you put some middleware in place before your route handlers but after passport's.
app.use(function(req, res, next) {
// Create a `context` object for use in any view.
// This allows the context to grow without impacting too much.
req.context = {};
// Assign your flag for authenticated.
req.context.isAuthenticated = typeof req.user !== 'undefined';
// Let the next middleware function perform it's processing.
next();
});
Then you can render each view with the context.
app.use('/', function(req, res) {
res.render('index', req.context); // Context is passed to view for usage.
});
This is all untested code.
You can do it as is already mentioned here ,but in this case you are going to check completely every request. Maybe you have got / you are going to have some pages that don't require any authentification and in this case you have to make some statement that will skip auth for that particular page or you can use something like this:
function checkUser(req, res, next) {
req.userAuth = (req.user !== undefined);
next();
}
app.post("settings", checkUser, doSomething);
app.post("administration", checkUser, doSomething);
app.post("index", doSomething); // Doesn't require any authentification
Or you can straight a way redirect a user
function checkUser(req, res, next) {
if (req.user === undefined) {
res.redirect("/login"); // res.render
}
else {
next();
}
}