Each res.send(200, line when is reached according to the logic is working fine:
const implementation = async (req, res, next) => {
try {
if (req.rut) {
const data = await someAPI();
res.send(200, data); // WORKING
} else {
const data2 = await SomeAPI2();
if (data2) {
res.send(200, data2}); // WORKING
}
res.send(400, 'Error'); // ERROR
}
} catch (error) {
res.send(400, error);
}
};
but when the code reach the line that uses res.send(400 I'm getting this error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:561:11)
Why? I don't see that other res.send() lines are reached.
So this happens because the execution reach the line
if (data2) {
res.send(200, data2}); // WORKING
}
It sends the response, how its supposed to, but then the code continue its execution since there's nothing that tells it to stop (res.send() does not stop the execution) reaching the next line
res.send(400, 'Error'); // ERROR
But because the response object was already sent, it throws the error
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:561:11)
An easy way to fix this is to add a return before each res.send, that will stop the execution, thus not reaching the next line of code, for example
if (data2) {
return res.send(200, data2});
}
return res.send(400, 'Error');
Even though i don't like the way you wrote the code this could solve your issue:
const implementation = async (req, res, next) => {
try {
if (req.rut) {
const data = await someAPI();
res.send(200, data); // WORKING
} else {
const data2 = await SomeAPI2();
if (data2) {
res.send(200, data2}); // WORKING
} else {
res.send(400, 'Error'); // ERROR
}
}
} catch (error) {
res.send(400, error);
}
};
FYI:
i have added another else statement after you call the SomeAPI2().
Related
Situation
I am building and testing a simple api with express. One of the routes is /api/blogs/:id, where the api should return a status 400 if the provided id is in the wrong format, and 404 if the id is not present in the database.
Problem
The api works fine and responds with the right status codes when making requests by browser or the REST client plugin in vsCode. But when I make requests with a malformed id via Superagent in my unit tests, the server responds with a 404, when it should be a 400.
Route
blogsRouter.get('/:id', async (req, res, next) => {
try {
const { id } = req.params;
const blog = await Blog.findById(id);
if (blog) {
res.status(200).json(blog);
} else {
res.status(404).end();
}
} catch (error) {
next(error);
}
});
Normally, Mongoose throws an error when running Blog.findById(id), triggering catch (error) { next(error) }, which executes the next middleware function errorHandler. For some reason though, this doesn't seem to happen when testing.
errorHandler
const errorHandler = (error, req, res, next) => {
if (error.name === 'CastError') {
res.status(400).json({ error: 'malformatted id' });
return;
} if (error.name === 'ValidationError') {
console.log(error.message);
res.status(400).json({ error: error.message });
return;
}
next(error);
};
test
test("fails with status 400 when 'id' has wrong format", async () => {
const invalidId = '45234sdsdasf';
await api.get(`/api/blogs/${invalidId}`)
.expect(400);
});
I found the solution.
The problem was const invalidId = '45234sdsdasf'.
This should be a malformed id, but for some reason Mongoose sees 12 characters long strings as a correct objectId. Changing invalidId to something else solved the problem.
My problem is that for some reason express router doesn't wait for next() function inside middleware and goes str8 to execution fo router.post.
Router.use('/posts/add', addPosts);
Router.post('/posts/add', (req, res) => {
if(req.success){
res.status(200).send('post added');
}else{
res.status(400).send({error: true, message: 'sth went wrong'})
}
});
Below is the middleware code:
module.exports = (req, res, next) => {
try {
if (req.authorization && req.authorization.access_level < 3) {
let post = new postsModel(req.body);
post.save().then(post => {
console.log(post);
req.success = true;
next();
});
} else {
throw new Error('unauthorized access');
}
} catch (err) {
res.status(400).send({ error: true, message: err.message });
}
};
Despite middlewares execution, router for some reason is always executing the router.post, doesn't wait for the next() function therefore awlays return error. Anybody could help with that?
Maybe try with the following approach:
Router.route('/posts/add', addPosts)
.post((req, res) => {
Refer to this documentation for further info.
I wanted to deeply apologize as I haven't posted the code for 1 more middleware invoked before anything else.
try {
const path = req.path;
switch (path) {
case '/add':
let data = req.body;
let error = addPostSchema.validate(data).error;
console.log(error);
if (error) {
throw new Error(error.message);
} else {
console.log('addPost validated');
next()
}
}
next();
} catch (err) {
console.log('walalala');
res.status(400).send({ error: true, message: err.message });
}
};
As you can see above, I've had double next() invoked. What I didn't know is that invoking next() does not stop rest of the code in middleware from executing, therefore it was prematurly invoking Router.post. Apologies for the confusion. Hope it will help somebody though having similiar problem.
I'm setting up a stripe webhook to check if the payment intent was successful or not. But while doing so, I'm getting this error Cannot set headers after they are sent to the client in stripe, what am I doing wrong in the route?
import express from 'express';
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: "2020-08-27" });
const router = express.Router();
router.post(
"/webhook",
express.raw({ type: "*/*" }),
async (request, response) => {
const sig = request.headers["stripe-signature"];
let event;
try {
event = stripe.webhooks.constructEvent(
request.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET_KEY
);
console.log("type", event);
} catch (err) {
// console.log("type2", err);
response.status(400).send(`Webhook Error: ${err.message}`);
}
response.json({ received: true });
}
);
export default router;
There's a section here https://stripe.com/docs/identity/handle-verification-outcomes that skips the body parsing if the url points to the webhook service
app.use((req, res, next) => {
if (req.originalUrl === '/webhook') {
next();
} else {
bodyParser.json()(req, res, next);
}
});
That could be the issue: webhook expects you not to execute express.raw() if you're going to give him the request.body to analyse
You are trying to send a json response with response.json() after the error handling sends the 400 response with response.status(400).send() -- you can't do that.
You need to either move your response.json() to the end of the try block or have the catch block return (as #abhishek noted) to stop the execution.
In the catch block you are not returning.
catch (err) {
// console.log("type2", err);
return response.status(400).send(`Webhook Error: ${err.message}`);
}
I am part of a project which uses nodeJS + ExpressJS for the backend application, and We have a middleware function to log accesses on routes in the database.
When an User tries to access the /user route with a post method, a middleware receives the Request, get information like the URL, ip address, origin, a description of the event and record it in the database.
Everything works just fine, but some of my teammates were discussing about how to log the erros also in the database.
I will put bellow a code example
const create = (request, response) => {
try {
const user = request.body;
const userExists = await usersRepository.findOne({ where: { email } });
if(userExists) {
return response.status.json({ error: 'E-mail already in use' });
}
const creadtedUser = await usersRepository.create(user);
return response.status(200).json({ user: creadtedUser });
} catch (error) {
response.status(500).json({ error });
}
};
When we were discussing about how to implement it, we realized we'd have to call a log error function in a lot of places since we have many flows which leads to an error response.
So the code would be just like:
const create = (request, response) => {
try {
const user = request.body;
const userExists = await usersRepository.findOne({ where: { email } });
if(userExists) {
function() // here we would log the error
return response.status.json({ error: 'E-mail already in use' });
}
const creadtedUser = await usersRepository.create(user);
return response.status(200).json({ user: creadtedUser });
} catch (error) {
function() // here we would log the error
response.status(500).json({ error });
}
};
is it a properly way of dealing with error logging or is there any better way of doing it? Thank you for reading!
You can use the built-in error handler provided by Express.JS for this kind of logic, of course it requires a bit of setup. Like most things in Express.JS, the error handler it's just a middleware function with four parameters err, req, res and next, which MUST be placed after all your other middlewares. It comes to play when, inside a router handle (for example), your call next(err) (where err it's an Error) or by simply throwing err. Check out the documentation for more.
app.use(...)
app.use(...)
app.use((req, res, next) => {
if (req.params.id === undefined) {
let error = new Error("ID required.")
error.statusCode = 400
error.statusMessage = "Request not valid, ID not found."
throw error;
} else {
// Do some stuff...
}
})
// NOTE: After ALL your other middlewares
app.use((err, req, res, next) => {
console.error(err)
res
.status(err.statusCode)
.json(err.statusMessage)
})
Ideally you should log the errors only inside the catch block. Whenever you encounter an error just throw a new error by calling throw new Error("Type your error message here"). Then your function inside catch block will log and handle the error appropriately.
I would change your code to this:
const create = (request, response) => {
try {
const user = request.body;
const userExists = await usersRepository.findOne({ where: { email } });
if(userExists) {
throw new Error("E-mail already in use")
}
const creadtedUser = await usersRepository.create(user);
return response.status(200).json({ user: creadtedUser });
} catch (error) {
function() // log your error
response.status(500).json({ error.message });
}
};
Read more about Errors here.
I have the following code:
"use strict";
const Raven = require("raven");
Raven.config(
"test"
).install();
module.exports = function(Reservation) {
function dateValidator(err) {
if (this.startDate >= this.endDate) {
err();
}
}
function sendEmail(campground) {
return new Promise((resolve, reject) => {
Reservation.app.models.Email.send(formEmailObject(campground),
function(
err,
mail
) {
if (err) {
console.log(err);
Raven.captureException(err);
reject(err);
} else {
console.log(mail);
console.log("email sent!");
resolve(mail);
}
});
});
}
function formEmailObject(campground) {
return {
to: "loopbackintern#yopmail.com",
from: "noreply#optis.be",
subject: "Thank you for your reservation at " + campground.name,
html:
"<p>We confirm your reservation for <strong>" +
campground.name +
"</strong></p>"
};
}
Reservation.validate("startDate", dateValidator, {
message: "endDate should be after startDate"
});
Reservation.observe("after save", async function(ctx, next) {
try {
const campground = await Reservation.app.models.Campground.findById(
ctx.instance.campgroundId
);
const mail = await sendEmail(campground);
next();
} catch (e) {
Raven.captureException(e);
next(e);
}
});
};
Sorry for the poor formatting. When the flow is done I get this error:
(node:3907) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Callback was already called.
I am calling the next() callback in two places, one in the try code and one in the catch code. I assume that when it all goes right, next callback is called only once, and the same when it goes wrong. But it seems that it is called twice and I don't know why.
I also tried to call next outside the try/catch code but it results in the same error. If I left only the next that is called inside the catch code it doesn't throw the error.
Any idea? Thanks!
if you are using async function you shouldn't explicitly call next, it gets automatically called.
check out this github issue for loopback async/await
so your hook can be like the following.
Reservation.observe("after save", async ctx => {
try {
const campground = await Reservation.app.models.Campground.findById(
ctx.instance.campgroundId
);
const mail = await sendEmail(campground);
} catch (e) {
Raven.captureException(e);
throw e;
}
});
NB: you don't need to wrap it in try catch unless you want to modify/work with the error.
You should declare your sendEmail method as async as it returns a promise.
async function sendEmail(campground) {
...
}
After reading this article, I created a await-handler.js file which include following code.
module.exports = (promise) =>
promise
.then(data => ({
ok: true,
data
}))
.catch(error =>
Promise.resolve({
ok: false,
error
})
);
Then in MyModel.js file, I created a async function to get a value from database as follow.
const awaitHandler = require("./../await-handler.js")
const getMaxNumber = async (MyModel) => {
let result = await awaitHandler(MyModel.find());
if (result.ok) {
if (result.data.length) {
return result.data.reduce((max, b) => Math.max(max, b.propertyName), result.data[0] && result.data[0].propertyName);
} else {
return 0;
}
} else {
return result.error;
}
}
As per #Mehari's answer, I've commented call to next() method as follow:-
module.exports = function(MyModel) {
MyModel.observe('before save', async(ctx, next) => {
const maxNumber = await getMaxNumber (MyModel);
if(ctx.instance) {
...
set the required property using ctx.instance.*
like createdAt, createdBy properties
...
// return next();
} else {
...
code for patch
...
// return next();
}
})
}
This solves the warning issue whenever saving endpoint is triggered.
But the warning issue still appear when I run the endpoint to load the resource.Like
http://localhost:3000/api/MyModel
Previously, the issue appear only when the before save operation hook gets triggered.
After encountering this issue, I checked adding access and loaded operation hooks and I found that the the warnings are issued after loaded operation hook.
MyModel.observe('access', (ctx, next) => {
return next();
})
MyModel.observe('loaded', (ctx, next) => {
return next();
})
What could have caused this issue and how can it gets resolved?