pass state around between express middleware in an isomorphic react app - javascript

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!

Related

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to client [duplicate]

I'm facing this weird issue in NodeJS when using with Passport.js, Express and Mongoose. Basically, I get an error saying "Cannot set headers after they are sent to the client" even though I don't send more than one header.
I've read other posts and tried them out as well, and none of them worked.
app.get - is there any difference between res.send vs return res.send
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
Cannot set headers after they are sent to the client
I've dug through github issues and I can't seem to find a solution. I get the problem that this error is triggered when I send multiple response headers, but the fact is that I am not sending multiple headers. It seems just weird.
This is my stack trace:
(node:9236) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect.
Server Running on port 5000
MongoDB Connected Error
[ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the
client
at validateHeader (_http_outgoing.js:503:11)
at ServerResponse.setHeader (_http_outgoing.js:510:3)
at ServerResponse.header (/Users/lourdesroashan/code/github/devlog/node_modules/express/lib/response.js:767:10)
at ServerResponse.json (/Users/lourdesroashan/code/github/devlog/node_modules/express/lib/response.js:264:10)
at Profile.findOne.then.profile (/Users/lourdesroashan/code/github/devlog/routes/api/profile.js:27:30)
at <anonymous>
This is my server code:
router.get("/userprofile", passport.authenticate('jwt', { session: false }), (req, res) => {
Profile.findOne({ user: req.user.id }).then(profile => {
if (!profile) {
return res.status(404).json({ error: "No Profile Found" });
}
else {
res.json(profile);
}
}).catch(err => {
console.log(err);
})
});
I understand what the error means, but from what I know, I don't think I am sending multiple headers, I even checked by console.log that only one of the blocks is run.
Thank you so much in advance! :)
Full Code at: https://github.com/lourdesr/devlog
EDIT:
I figured it out. It was a problem in my passport.js while trying to get the authenticated user. I forgot to use 'return' on the 'done' method, which had caused it. Just added the return statement and it worked!
That particular error occurs whenever your code attempts to send more than one response to the same request. There are a number of different coding mistakes that can lead to this:
Improperly written asynchronous code that allows multiple branches to send a response.
Not returning from the request handler to stop further code in the request handler from running after you've sent a response.
Calling next() when you've already sent a response.
Improper logic branching that allows multiple code paths to execute attempt to send a response.
The code you show in your question does not appear like it would cause that error, but I do see code in a different route here that would cause that error.
Where you have this:
if (!user) {
errors.email = "User not found";
res.status(404).json({ errors });
}
You need to change it to:
if (!user) {
errors.email = "User not found";
res.status(404).json({ errors });
// stop further execution in this callback
return;
}
You don't want the code to continue after you've done res.status(404).json({ errors }); because it will then try to send another response.
In addition, everywhere you have this:
if (err) throw err;
inside an async callback, you need to replace that with something that actually sends an error response such as:
if (err) {
console.log(err);
res.sendStatus(500);
return;
}
throwing inside an async callback just goes back into the node.js event system and isn't thrown to anywhere that you can actually catch it. Further, it doesn't send a response to the http request. In otherwords, it doesn't really do what the server is supposed to do. So, do yourself a favor and never write that code in your server. When you get an error, send an error response.
Since it looks like you may be new here, I wanted to compliment you on including a link to your full source code at https://github.com/lourdesr/devlog because it's only by looking at that that I was able to see this place where the error is occuring.
I was receiving this error because of a foolish mistake on my part. I need to be more careful when referencing my other working code. The truly embarrassing part is how long I spent trying to figure out the cause of the error. Ouf!
Bad:
return res
.send(C.Status.OK)
.json({ item });
Good:
return res
.status(C.Status.OK)
.json({ item });
Use ctrl + F hotkey and find all 'res.' keywords
then replace them with 'return res.',
change all 'res.' to 'return res.'
something like this:
res.send() change to --> return res.send()
maybe you have 'res.' in some block, like if() statement
Sorry for the Late response,
As per the mongoose documentation "Mongoose queries are not promises. They have a .then() function for co and async/await as a convenience. However, unlike promises, calling a query's .then() can execute the query multiple time"
so to use promises
mongoose.Promise = global.Promise //To use the native js promises
Then
var promise = Profile.findOne({ user: req.user.id }).exec()
promise.then(function (profile){
if (!profile) {
throw new Error("User profile not found") //reject promise with error
}
return res.status(200).json(profile) //return user profile
}).catch(function (err){
console.log(err); //User profile not found
return res.status(404).json({ err.message }) //return your error msg
})
here is an nice article about switching out callbacks with promises in Mongoose
and this answer on mongooses promise rejection handling Mongoose right promise rejection handling
There is a simple fix for the node error [ERR_HTTP_HEADERS_SET]. You need to add a return statement in front of your responses to make sure your router exits correctly on error:
router.post("/", async (req, res) => {
let user = await User.findOne({email: req.body.email});
if (!user) **return** res.status(400).send("Wrong user");
});
Because of multiple response sending in your request. if you use return key word in your else condition your code will run properly
if (!profile) {
return res.status(404).json({ error: "No Profile Found" });
}
else {
**return** res.json(profile);
}
This also happen when you tries to send the multiple response for a same request !!
So make sure you always use return keyword to send response to client inorder to stop the further processing !!
Where you have this:
if (!user) { errors.email = "User not found"; res.status(404).json({ errors }); }
You need to change it to:
if (!user) { errors.email = "User not found"; return res.status(404).json({ errors }); }
I got the same error using express and mongoose with HBS template engine. I went to Expressjs and read the docs for res.render, and it says // if a callback is specified, the rendered HTML string has to be sent explicitly. So I wasnt originally sending my html explicitly in the callback,. This is only for a contact form btw, not login info, albeit GET
//Original
let { username, email } = req.query; //My get query data easier to read
res.status(200).render('index', { username, email });
//Solution without error. Second param sending data to views, Third param callback
res.status(200).render('index', { username, email }, (err, html)=>{
res.send(html);
});
In react, if your are calling the function in useEffect hook, make sure to add a dependency to the dependency Array.
I had this error from an if statement not having an else block.
if(someCondition) {
await () => { }
}
await () => { }
I changed the above to this below and solved my issue
if(someCondition) {
await () => { }
} else {
await () => { }
}
For me, I accidentally put a res.status inside of a for loop. So my server would trigger the error the second time a res.status was returned. I needed to put the res.status outside of the for loop so it would only trigger once within the function.
First of all : make sure you didn't miss any asynchronous action without an async/await or use promises/callbacks.
Then attach any res with the return keyword : return res.status(..).json({});
And finally which was my problem: don't use return res.sendStatus if you always have some return res... inside a callback function, but you can always do a retun res.status();
in my case it was :
users.save((err,savedDoc){
if(err) return res.status(501).json({})
res.status(200).json({});
});
return res.status(500); // instead ofdoing return res.sendStatus(500)
you have to enable Promises in your programm, in my case i enabled it in my mongoose schema by using mongoose.Promise = global.Promise .
This enables using native js promises.
other alternatives to this soloution is :
var mongoose = require('mongoose');
// set Promise provider to bluebird
mongoose.Promise = require('bluebird');
and
// q
mongoose.Promise = require('q').Promise;
but you need to install these packages first.
My problem besides not returning, i was forgetting to await an asynchronous function in the handler. So handler was returning and after a bit the async function did its thing. 🤦🏻‍♀️
Before:
req.session.set('x', {...});
req.session.save();
return req.status(200).end();
When i needed to await:
req.session.set('x', {...});
await req.session.save();
return req.status(200).end();
I'm putting this here for anyone else who has the same problem as me- this happened to me because I'm using the next() function without a return preceding it. Just like a lot of the other answers state, not using return with your response will / can cause / allow other code in the function to execute. In my case, I had this:
app.get("/customerDetails", async (req, res, next) => {
// check that our custom header from the app is present
if (req.get('App-Source') !== 'A Customer Header') next();
var customerID = req.query.CustomerID
var rows = await get_customer_details(customerID);
return res.json(rows);
});
In my case, I forgot to include the header in my request, so the conditional statement failed and next() was called. Another middleware function must have then been executed. After the middleware finishes, without a return, the rest of the code in the original middleware function is then executed. So I simply added a return before my next() call:
// serve customer details payload
app.get("/customerDetails", async (req, res, next) => {
// check that our custom header from the app is present
if (req.get('App-Source') !== 'A Customer Header') return next();
var customerID = req.query.CustomerID
var rows = await get_customer_details(customerID);
return res.json(rows);
});

How to route through a path and perform different functionality at different URLs?

I would like to post at the path /users and then immediately post to /users/:id, but the actions need to be different at each of these URLs, so I can't use the array method for applying the same middleware to different URLs
The idea is that POST(/users/:id, ...) will never be called by the client. It only gets called immediately after POST(/users, ...)
When using express, you are providing a handler function for a specific endpoint. Actually it's an array of those functions (middlewares). That means that you can switch from :
route.post('/users/`, (req, res, next) => {
// do your magic
});
to
route.post('/users/', handleMyCall);
This way you can easily reuse those functions in multiple endpoints without your need to actually make requests:
route.post('/users/', (req, res) => {
// do something +
handleMyCall(req, res);
// either return the result of this call, or another result
});
route.post('/users/:userID', (req, res) => {
// do another operation +
handleMyCall(req, res);
});
Update:
Using GET or POST differs in the way the data is sent to the server. You can use both for your cases, and it really depends on the testing client you have.
Typically, a GET request is done to query the database and not do any actions. POST is usually used to create new entities in the database.
In your scenario, I'd guess you would have post('/users/) in order to create a user. And then have get('/users/:userID') to find that user and return it to the client.
You can easily have different endpoints with different handles for those cases.
As I understood from the comments, you'll need a POST request on /users (to persist data in some database) and GET /users/:id to retrieve these data, which is very different from POSTing the same thing on 2 different endpoints.
POST is generally used to persist and GET to retrieve data.
I'll assume you use some kind of NoSQL DB, perhaps MongoDB. MongoDB generate a unique ID for each document you persist in it.
So you'll have to have 2 routes :
const postUser = async (req, res, next) => {
try {
// persist your user here, perhaps with mongoose or native mongo driver
} catch (e) {
return next(e);
}
}
const getUserById = async (req, res, next) => {
try {
// get your user here thanks to the id, in req.params.id
} catch (e) {
return next(e);
}
}
export default (router) => {
router.route('/users').post(postUser);
router.route('/users/:id').get(getUserById);
};

ExpressJS apparent race condition between Promise and EventEmitter

I have a NodeJS/Express web application that allows the user to upload a file, which I then parse using connect-busboy save to my database using Sequelize. Once that's done, I want to redirect the user to a given page. But Express is returning a status of 404 before my Promise resolves, even though I'm never calling next(), which I thought was mandatory in order to call the next handler in the middleware chain and thus result in a 404.
This is my code so far:
function uploadFormFile(req, res, next) {
var documentInstanceID = req.params.documentInstanceID;
// set up an object to hold my data
var data = {
file: null,
documentDate: null,
mimeType: null
};
// call the busboy middleware explicitly
// EDIT: this turned out to be the problem... of course this calls next()
// removing this line and moving it to an app.use() made everything work as expected
busboy(req, res, next);
req.pipe(req.busboy);
req.busboy.on('file', function (fieldName, file, fileName, encoding, mimeType) {
var fileData = [];
data.mimeType = mimeType;
file.on('data', function (chunk) {
fileData.push(chunk);
});
file.on('end', function () {
data.file = Buffer.concat(fileData);
});
});
req.busboy.on('finish', function () {
// api methods return promises from Sequelize
api.querySingle('DocumentInstance', ['Definition'], null, { DocumentInstanceID: documentInstanceID })
.then(function (documentInstance) {
documentInstance.RawFileData = data.file;
documentInstance.FileMimeType = data.mimeType;
// chaining promise
return api.save(documentInstance);
}).then(function () {
res.redirect('/app/page');
});
});
}
I can confirm that my data is being persisted correctly. But due to the race condition, the web page says 'can't POST' due to the 404 status being returned by Express, and the res.redirect is failing with an error setting the headers because it's trying to redirect after the 404 has been sent.
Can anyone help me figure out why Express is returning the 404?
The problem is coming from your internal call to busboy inside your handler. Rather than it executing and simply returning control to your handler, it would be calling the next which is passed to it before it returns control. So you code after the busboy call does execute, but the request has already advanced past that point.
In cases in which you want some middleware to only be executed for certain requests, you can chain middleware into those requests, such as:
router.post('/upload',busboy,uploadFromFile)
You can also separate them with .use() such as:
router.use('/upload', busboy);
router.post('/upload', uploadFromFile);
Either of the above will chain the middleware in the way you intended. In the case of .use() the middleware would also be applied to any applicable .METHOD() as Express refers to it in their documentation.
Also, note that you can pass in an arbitrary number of middleware this way, either as separate parameters or as arrays of middleware functions, such as:
router.post('/example', preflightCheck, logSomeStuff, theMainHandler);
// or
router.post('example', [ preflightCheck,logSomeStuff ], theMainHandler);
The execution behavior of either of the above examples will be equivalent. Speaking only for myself and not suggesting it is a best practice, I normally only use the array-based addition of middleware if i am building the middleware list at runtime.
Good luck with it. I hope you enjoy using Express as much as I have.

How to hook after `res.send` but before the response leaves the server, ==> when res.statusCode is know

I already use the middleware facility in Express.js by means of app.use() to hook on all requests and log a message, but found the fact that res.statusCode is nearly always 200, which I checked is not correct. I presume this is the default value and since it is not yet replaced with my own statusCode with res.send(204) well, is always 200.
I know I can log the message after every res.send(x) in every request, but as you might imagine, that is cumbersome.
So, the question is:
Where/How can I hook my middleware function with the conditions that is only in one place and res.statusCode is the one that the client really sees?
Relevant code:
// Log API requests.
app.use(function (req, res, next) {
logger.info('API request.', {
module: 'core'
data : {
req: {
method: req.method,
url : req.url,
ip : req.ip
},
res: {
status_code: res.statusCode // Always 200 and not the really one.
}
}
});
next();
});
app.use(app.router);
If I move app.use(app.router) the middleware don't get executed.
res inherits from http.ServerResponse in the standard library. This means that it is an EventEmitter with a finish event. So in your middleware, register for the finish event and do the logging when that fires:
app.use(function (req, res, next) {
res.on('finish', function() {
logger.info('API request.', {
module: 'core'
data : {
req: {
method: req.method,
url : req.url,
ip : req.ip
},
res: {
status_code: res.statusCode
}
}
});
});
next();
});
You could, of course, register with res.once, but the finish event is guaranteed to fire only once, so that shouldn't be necessary unless there's a bug in the standard library.
This won't necessarily occur "after res.send but before the response leaves the server" as you request - the docs on the finish event only claim that
[T]his event is emitted when the last segment of the response headers and body have been handed off to the operating system for transmission over the network.
So, some of the response may have already left the server. However, I believe it is the closest you'll get without monkey-patching express, which I would advise against.

Redirecting a response to an HTTP 500 error handler, when an error is thrown

If some underlying component throws an error, how do I get to inform the express.js stack about it? I am using an underlying data access framework which occasionally throws specific errors (for instance, when a record is not found in the DB).
Does express.js offer a way to jump straight to a 500 error handler, and redirect the response to a corresponding page? Unfortunately, I do not see a direct way to use the standard express.js way (using a next middleware handler) unless maybe if I use try...catch everywhere, which is also a bit of overhead
Usually you don't redirect to a 500 page. What you do is let the exception propagate and node.js will do a 500 for you if an exception was thrown.
I have some related rules I follow. Maybe you'll find them useful:
Never use try/catch in async code (like node.js). Errors are usually propagated through the first parameter of the supplied callback.
If I need to return an error to the user, I do it using res.send('Error', 500);
Something for those who are still looking for an answer like me:
1- Use express default errorHandler middleware
app.use(express.errorHandler());
Or
2- Supply your own error handling middleware:
app.use(function (error, req, res, next) {
res.status(500);
res.render("500.jade", {});
});
Ideally you would want to use the first option on DEV and the second one on PROD
// development only
if ('development' == app.get('env')) {
app.use(express.errorHandler());
}
app.use((function (error, req, res, next) {
res.status(500);
res.render("500.jade", {});
});
Note: the error handler should be strategically placed below the app.router

Categories