I want to send my token and my user's role to my login.component.ts.
When I was trying to find the problem, in my research I came across someone's suggestion to use
res.write(foo1)
res.write(foo2)
res.end
Instead of
res.send(foo1)
res.send(foo2)
But that didn't work.
I then tried using this to test it:
res.write(foo1)
res.end()
But this is giving me an error:
events.js:174
throw er; // Unhandled 'error' event
^
TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be one of type string or Buffer. Received type object
at write_ (_http_outgoing.js:595:11)
at ServerResponse.write (_http_outgoing.js:567:10)
at User.findOne (C:\Users\notan\GitHub\se3316-notansandwich-lab5\server\controllers\user.controller.js:46:33)
at C:\Users\notan\node_modules\mongoose\lib\model.js:4604:16
at C:\Users\notan\node_modules\mongoose\lib\query.js:4348:12
at process.nextTick (C:\Users\notan\node_modules\mongoose\lib\query.js:2850:28)
at process._tickCallback (internal/process/next_tick.js:61:11)
Emitted 'error' event at:
at C:\Users\notan\node_modules\mongoose\lib\model.js:4606:13
at C:\Users\notan\node_modules\mongoose\lib\query.js:4348:12
at process.nextTick (C:\Users\notan\node_modules\mongoose\lib\query.js:2850:28)
at process._tickCallback (internal/process/next_tick.js:61:11)
This is my user.controller.js, which I use in my route.js which is used in my sever.js
const User = require('../models/user.model')
const jwt = require('jsonwebtoken')
exports.user_create = function (req, res, next) {
let user = new User(
{
_id: Math.random().toString(36).substr(2, 5), // generate a random ID
email: req.body.email,
password: req.body.password,
firstName: req.body.firstName,
lastName: req.body.lastName,
role: "user"
}
);
user.save(function (err, registeredUser) {
if (err) {
return next(err);
}
else {
let payload = { subject: registeredUser._id}
let token = jwt.sign(payload, 'secretKey')
res.status(200).send({token})
}
})
}
exports.user_login = function (req, res, next) {
let userData = req.body
User.findOne({email: userData.email}, (err, user) => {
if (err) {
console.log(err)
}
else {
if (!user) {
res.status(401).send('Invalid email')
}
else if (user.password !== userData.password) {
res.status(401).send('Invalid password')
}
else {
let payload = {subject: user._id}
let token = jwt.sign(payload, 'secretKey')
//res.status(200).send({token})
res.status(200).write({token})
//let role = this.user.role
// res.status(200).write({role})
res.end()
}
}
})
}
Using this works
res.status(200).send({token})
But this does not
res.status(200).write({token})
res.end()
In response to the title of your question:
Can I use res.send(foo) twice?
No, you cannot call that twice for the same request.
See the second part of the answer since the OP changed their question after I wrote this first part
In Express, you can only use res.send() or res.json() or res.end() once per request. When you execute those, it sends the request. If you try to send more on the same request, it will do nothing and will show a warning in Express.
res.write() can be called more than once, then followed by res.end() when you are finally done with the request.
In your example:
res.status(200).send({token})
res.end()
The res.send() already calls .end() for you so trying to call it again is considered an error because the request has already been sent.
FYI, .status(200) is not necessary. The default status is already 200 so res.send({token}) will already have a 200 status.
More Recent Answer for the Modified Question
Now that you've completely changed the question to be about res.write({token}), that does not work because res.write() requires a String or a Buffer as an argument and you were giving it an object. You would have to manually convert the object to JSON yourself:
res.type('application/json');
res.write(JSON.stringify({token}));
res.end();
And note that this also sets the appropriate content type. If your object is large with res.write() you may also have to pay attention to the write buffer being full and listen for the drain event. res.write() is a much lower level facility (it's at the http level, not at the Express level) than the Express functions for sending data.
Built into Express, you can use res.send() or res.json() which are Express methods that will both that you passed an object, automatically convert it to JSON for you and set the content type to JSON also. It will also handle any buffer full issues in the write stream too.
res.send({token});
or, I prefer to be more explicit in my Express code with:
res.json({token});
And, if you're trying to send multiple pieces of data, you can put them into the same object:
res.json({token, role});
Calling res.status(200).send({ token }) ends the request and sends the object back to the client...
Since the request is now ended... Calling res.end() generates the error...
You'd usually use res.end if u were piping some data (usually binary) after several res.write to close the pipe...
For more info... checkout Express' docs in the response object...
https://expressjs.com/en/api.html#res
Also... U can't send an object using res.write...
From your error... it says that it inly accepts a string or buffer. If u want to send plain objects... res.send or res.json would be more appropriate...
I found the solution: I can send multiple things in an res.send(...)
res.status(200).send({token, role})
If you want to use res.write(argument) you have to pass the argument as a string or Buffer, just like the error message says. To get the same effect just convert your response object to a string:
res.status(200).write(JSON.stringify({token}))
Related
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);
});
I am trying to update a collection in mongoDB after the user finishes some tasks. However, whenever I attempt to save the information and update mongo, I'm getting the error POST http://localhost:3000/updateSurvey/634124db6f 400 (Bad Request). Any ideas why my code isn't functioning correctly?
Backend js script
app.post('/updateSurvey', async (req, res) => {
try {
await client.connect();
var db = client.db('Admin_Db');
var collection = db.collection('Survey');
await collection.findOneAndUpdate({"_id": ObjectId(req.body.id)}, {completion: req.body.completion});
res.send("updated");
} catch (error) {
console.log(error);
res.status(400).send(error);
}
});
Main.js (this is how I am fetching the data from mongo)
fetch("http://localhost:3000/updateSurvey", {
method:'POST',
headers:{'Content-Type': 'application/json'},
body: JSON.stringify({id: surveyID, completion: true})})
.then(response=>{response.text()})
.catch(function(err){console.log("Fetch Problem: " + err)});
You don't have a route http://localhost:3000/updateSurvey/634124db6f exposed on your server. Therefore the 404 error. Since you are using a post call, just pass the surveyID in your body when making the post call using fetchAPI instead of sending it as a query param.
And make sure http://localhost:3000/updateSurvey is the route to which your are sending your data, without the surveyId in the URL.
Edit
Edits made as per request received in comments.
collection.findOneAndUpdate({"_id": id)}, {completion: req.body.completion});
should be:
collection.findOneAndUpdate({"_id": new ObjectId(id))}, {completion: req.body.completion});
_id is of type ObjectId in MongoDB. You are passing id as string. This should most likely be the error from what I can gather by the code shared in your question. You can cast a string to ObjectId by using the ObjectId class provided by the Mongo Driver for node. Even mongoose provides this class.
I am doing a registration method in Next.js. I need to hash the password when I put it into the database so for that I use bcrypt. It seems working but if I call the registration API it outputs a warning which says:
API resolved without sending a response for /api/register, this may result
in stalled requests.
Here is my code:
// imports and constants
export default async (req: any, res: any) => {
if (req.method === "POST") {
//some variables and validation schema
await db
.collection(COLLECTION)
.find({ email })
.count()
.then((result) => {
if (result > 0) {
return res.status(409).end();
} else {
bcrypt.hash(password, 10).then(function (hash) {
// <----- THE PROBLEM IS HERE
//password is the plainedPassword that I get from frontend
user.password = hash;
db.collection(COLLECTION).insertOne(user);
return res.status(204).end();//<<--- the request doesn't end here but goes forward..
});
}
// if I put another return.status(204).end(); here it works
});
} else {
return res.status(405).end();
}
};
It seems to be working, it stores the hash password into the DB, then I can make the login as well. However, I believe that warning could cause serious problems in the future.
I also logged a string before and after the bcrypt statement and I found out the problem is there.
I have found this answer but it did not help me.
In my case I was sending a POST request and expecting a response with a 200 that I wasn't receiving (with the same error that you have), so after that, my app couldn't continue with the normal workflow, the solution to that was sending a "dummy" response in json format, it could be something like:
return res.status(204).json({ message: "No content" });
It seems like nextjs always expect you to send some type of content.
Try returning something from the API function, modify your code like this:
return await db.collection()
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!
Copied directly from Braintree's tutorial, you can create a client token with a customer ID like this:
gateway.clientToken.generate({
customerId: aCustomerId
}, function (err, response) {
clientToken = response.clientToken
});
I declare var aCustomerId = "customer" but node.js closes with the error
new TypeError('first argument must be a string or Buffer')
When I try to generate a token without the customerId, everything works fine (though I never get a new client token but that's another question).
EDIT: Here is the complete test code as requested:
var http = require('http'),
url=require('url'),
fs=require('fs'),
braintree=require('braintree');
var clientToken;
var gateway = braintree.connect({
environment: braintree.Environment.Sandbox,
merchantId: "xxx", //Real ID and Keys removed
publicKey: "xxx",
privateKey: "xxx"
});
gateway.clientToken.generate({
customerId: "aCustomerId" //I've tried declaring this outside this block
}, function (err, response) {
clientToken = response.clientToken
});
http.createServer(function(req,res){
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(clientToken);
res.end("<p>This is the end</p>");
}).listen(8000, '127.0.0.1');
Disclaimer: I work for Braintree :)
I'm sorry to hear that you are having trouble with your implementation. There are a few things that might be going wrong here:
If you specify a customerId when generating a client token, it must be a valid one. You do not need to include a customer id when creating a client token for a first time customers. Typically you would create create a customer when handling the submission of your checkout form, and then store that customer id in a database for use later. I'll talk to our documentation team about clarifying the documentation around this.
res.write takes a string or a buffer. Since you were writing response.clientToken, which was undefined because it was created with an invalid customer id, you were receiving the first argument must be a string or Buffer error.
Some other notes:
If you create a token with an invalid customerId, or there is another error processing your request, response.success will be false, you can then inspect the response for the reason why it failed.
You should generate your client token within the http request handler, this will allow you generate different tokens for different customers, and to better handle any issues that result from your request.
The following code should work, provided you specify a valid customerId
http.createServer(function(req,res){
// a token needs to be generated on each request
// so we nest this inside the request handler
gateway.clientToken.generate({
// this needs to be a valid customer id
// customerId: "aCustomerId"
}, function (err, response) {
// error handling for connection issues
if (err) {
throw new Error(err);
}
if (response.success) {
clientToken = response.clientToken
res.writeHead(200, {'Content-Type': 'text/html'});
// you cannot pass an integer to res.write
// so we cooerce it to a string
res.write(clientToken);
res.end("<p>This is the end</p>");
} else {
// handle any issues in response from the Braintree gateway
res.writeHead(500, {'Content-Type': 'text/html'});
res.end('Something went wrong.');
}
});
}).listen(8000, '127.0.0.1');