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.
Related
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 a trying to implement a rest API for our project then I go for node js and express. I have built all the models and controllers. I faced an issue while trying to handle an error. Errorhandler function doesn't receive all the properties of error that caught in try/catch block. I can not read its name in a handler but I can use its name in the controller. Could you please help me?
const errorHandler = (err, req, res, next) => {
console.log(`Error in method:${req.method}: ${err.stack}`.bgRed);
let error = { ...err };
console.log(`Error handler: ${err.name}`);
res.status(error.statusCode || 500).json({
success: false,
data: error.message || 'Server Error',
});
};
module.exports = errorHandler;
controller
const mongoose = require('mongoose');
const Product = require('../models/Product');
const ErrorResponse = require('../utils/error');
const routeName = 'PRODUCT';
// #desc getting single product via id
// #route GET api/v1/products
// #acces public
exports.getProdcut = async (req, res, next) => {
try {
const product = await Product.findById(req.params.id);
if (!product) {
return next(
new ErrorResponse(`Product not found with id:${req.params.id}`, 404)
);
}
res.status(200).json({
success: true,
data: product,
});
} catch (err) {
console.log(err.name);
console.log('ERRO APPEND');
next(new ErrorResponse(`Product not found with id:${req.params.id}`, 404));
}
};
Assuming that errorHandler is part of your middleware that is somewhere after getProdcut, you can try just throwing the error and Express will automatically detect that for you, because error handling middleware such as yours accepts 4 parameters. So the following would work:
const getProdcut = async (req, res, next) => {
try {
// ...
} catch (err) {
throw err;
}
};
const errorHandler = (err, req, res, next) => {
if (err) {
console.log('hello from the error middleware');
console.log(err.name);
}
else {
// next() or some other logic here
}
}
app.use('/yourRoute', getProdcut, errorHandler);
And inside of your errorHandler you should have access to the error object.
Error-handling middleware always takes four arguments. You must provide four arguments to identify it as an error-handling middleware function. Even if you don’t need to use the next object, you must specify it to maintain the signature. Otherwise, the next object will be interpreted as regular middleware and will fail to handle errors.
https://expressjs.com/en/guide/using-middleware.html#middleware.error-handling
As the title suggest, I get a weird error when responding with data from my server.
In homepage.js (which I want to load after loggin in) I have this request to the server to get the posts and then set the posts to the response.
useEffect(() => {
//userService.getDashboard() === Axios.get('http://localhost:3001/homepage')
userService.getDashboard().then((response) => {
setListOfPosts(response)
});
}, []);
This request first goes to the homepage.js, which further sends a request to getPosts, like so:
const headers = req.headers;
const getPosts = Axios.get('http://localhost:3001/getPosts', {headers: headers});
getPosts.catch((response) => {
//NEVER GET ANY RESPONSE???
console.log('Error in homepage.js')
//res.send(response);
});
getPosts.then((response) => {
//NEVER GET ANY RESPONSE???
res.send(response.data);
});
And lastly in the chain I have the getPosts router which does:
router.get('/', authenticateToken, async (req, res) => {
await db.query('SELECT * FROM posts',
(err, result) => {
if (err) {
console.log('HELLO FROM ERROR')
res.send({errorMessage: err});
} else {
console.log(result)
res.send(result);
}
});
});
So I can confirm that after every request to homepage I get all the way to getPosts() and the database query always works fine and goes into the result where "console.log(result)" lies and I can confirm that the result is indeed all the posts. The weird stuff happens when I'm sending back the data. So from getPosts() I'm obviously doing a res.send(result) which sends the data back to homepage.js. But this is when I get the error "UnhandledPromiseRejectionWarning: Error: Request failed with status code 304"
Any idea why?
you should not use res.send inside the .then of axios
this code works for me
useEffect(() => {
getPosts.then((response) => {
console.log("inside getPosts.then ");
console.log(response);
});
and this is my controller file to send request to backend:
const axios = require("axios");
export const getPosts = axios.get("http://localhost:5000/tasks/taskscheck");
getPosts.catch((response) => {
console.log("Error in homepage.js");
});
getPosts.then((response) => {
console.log("inside then get posts");
console.log(response);
});
I have tasks project and I can see in the response all my tasks.
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.
Test
it('should fail trying to GET bookmarks with false user id',async () => {
try {
const response = await request(app)
.get(baseApiUrlUnderTest + 'false_user_id/bookmarks')
.set('Authorization', bearerToken);
} catch (e) {
console.log(e); //it doesn't reach this point
expect(e.httpStatus).to.equal(HttpStatus.UNAUTHORIZED);
}
});
The relevant part of the method under test:
/* GET bookmark of user */
personalBookmarksRouter.get('/', keycloak.protect(), wrapAsync(async (request, response) => {
userIdTokenValidator.validateUserIdInToken(request);
...
}));
where wrapAsync makes sure the error is passed to the custom error handler:
let wrapAsync = function (fn) {
return function(req, res, next) {
// Make sure to `.catch()` any errors and pass them along to the `next()`
// middleware in the chain, in this case the error handler.
fn(req, res, next).catch(next);
};
}
The validateUserIdInToken method which causes the method under test to throw an exception:
const AppError = require('../models/error');
const HttpStatus = require('http-status-codes');
let validateUserIdInToken = function (request) {
const userId = request.kauth.grant.access_token.content.sub;
if ( userId !== request.params.userId ) {
throw new AppError(HttpStatus.UNAUTHORIZED, 'Unauthorized', ['the userId does not match the subject in the access token']);
}
}
module.exports.validateUserIdInToken = validateUserIdInToken;
and the custom error handler in the root middleware:
app.use(function(err, req, res, next) {
if (res.headersSent) {
return next(err)
}
if(err instanceof AppError) { //execution lands here as expected and the test stops...
res.status(err.httpStatus);
return res.send(err);
} else {
res.status(err.status || HttpStatus.INTERNAL_SERVER_ERROR);
res.send({
message: err.message,
error: {}
});
}
});
I think you may be approaching this incorrectly. Invalid auth should not raise errors in the app - it's not an error really, is a validation issue.
If the auth fails, simply send the relevant http error code - 401 back to the client.
res.send(HttpStatus.UNAUTHORIZED, 'a message if you want'); // 401
In your route handler:
personalBookmarksRouter.get('/', keycloak.protect(), wrapAsync(async (request, response) => {
const userId = request.kauth.grant.access_token.content.sub;
if ( userId !== request.params.userId ) {
return response.send(HttpStatus.UNAUTHORIZED);
}
...
}));
In your test, check the for status 401:
chai.request(server)
.get('/false_user_id/bookmarks')
.end((err, result) => {
if (err) {
return callback(err);
}
result.should.have.status(401);
});
Thanks to #laggingreflex's comment I missed debugging that the response actually returned with the expected status and error message
The adjusted test case now looks like this:
it('should fail trying to GET bookmarks with false user id',async () => {
const response = await request(app)
.get(baseApiUrlUnderTest + 'false_user_id/bookmarks')
.set('Authorization', bearerToken);
expect(response.status).to.equal(HttpStatus.UNAUTHORIZED);
});