I have an express-router route as follows:
.post(
authController.protect,
itemController.createItem, //create Item
itemController.sendNotification('new-item'), //if user exists send notification
);
the createItem controller has some logic that can run sendNotifications with type('new-match') and if skipped will continue to the next controller using next() as follows:
if(foo) next()
else sendNotifications('new-match')
my sendNotifications middleware is defined as follows:
const sendNotification = type =>
catchAsync(async (req, res, next) => {
if (req.userFound) {
await Notification.create({ user: req.userFound, notType: type });
const user = await User.findByIdAndUpdate(req.userFound, {
$inc: { notifications: 1 }
});
req.userFoundEmail = user.email;
}
next();
});
exports.sendNotification = sendNotification;
My problem is that when sendNotifications middleware is induced from the route stack it will have access to req,res,next but when it is called from the controller itself req,res,next are undefined
I am calling the sendNotification from inside the controller as explained above:
sendNotification('new-match');
How i can provide access to req,res,next from both ways?
Related
ExpressJS can have a chain of middlewares for a single route, which at the very least allows to split input validation logic from the actual handling of the inputs.
The usual pattern looks like this:
import { Router } from "express";
const router = Router();
router.use("/entity/:id", async (req, res, next) => {
try {
// validation logic
const isValid = ...
// short circuit if validation fails
if (!isValid) {
throw new Error("error message")
}
// get to the next middleware in chain
next()
} catch (error) {
// pass the error to the error-handling middleware chain instead
next(error)
}
});
router.get("/entity/:id", async (req, res, next) => {
try {
// route handling logic
...
} catch (error) {
next(error)
}
});
This works fine as long as validation middleware doesn't require database calls. But in the most basic usecase, auth, it does.
So auth code looks like this:
import { Router } from "express";
// `db` object created as per instructions
// https://vitaly-t.github.io/pg-promise/index.html
import { db } from "#db"
const router = Router();
router.use("/auth", async (req, res, next) => {
try {
// key retrieval logic
// assume it throws on any mistake
const authKey = ...;
// retrieve session by key
const session = await db.one(
"...",
{ authKey }
)
// throw if it's not found for whatever reason
if (!session) {
throw new Error("error message")
}
// save the session value for the request duration
// so the middleware/handlers can access
res.locals.session = session
// get to the next middleware in chain
next()
} catch (error) {
// pass the error to the error-handling middleware chain instead
next(error)
}
});
router.get("/auth/:id/posts", async (req, res, next) => {
try {
const { id } = req.params;
// it is guarantied to be always there because
// of the middleware before
const session = res.locals.session;
const posts = await db.manyOrNone("...", {session, id})
return res.status(200).json(posts);
} catch (error) {
next(error)
}
});
The issue here is these 2 middlewares will use 2 separate connections, and it will only get worse with more complicated routing.
Is there a way to create a transaction/task object, which is an argument for the callback for db.tx()/db.task(), which could be passed around outside of these methods?
What I'm trying to do is to test a REST API endpoint that validates requests with req.session (set by express-session).
Here are snippets of my code:
middleware.ts
export const validateSecurityCode = (req, res, next) => {
// How do I mock/spy on this "req.session.securityCode"?
if (req.session.securityCode !== req.body.securityCode) {
throw new BadRequestError('Wrong security code was provided');
}
return next();
};
api/item.ts
router.post('/item', [validateSecurityCode], async (req, res) => {
await createItem(req.body);
res.send({ message: 'DONE' });
});
As you can see, if req.session.securityCode !== req.body.securityCode, this endpoint has to throw an error.
Is it possible to mock/spy on req.session while testing the endpoint with jest and supurtest?
Below is what I'd like to achieve:
it('should pass security code check', async () => {
// " MOCK_REQ_SESSION('valid-code')" will ensure that "req.session.securityCode" will get 'valid-code'
validateSecurityCode = MOCK_REQ_SESSION('valid-code');
const response = await request(app).post('/api/item').send({
securityCode: 'valid-code',
// other request body values
});
expect(response.statusCode).toEqual(200);
});
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
I have the following protect handler to protect some URLs from users that are not signed in
exports.protect = async (req, res, next) => {
if (!req.cookies.jwt) {
return res.redirect("/auth");
}
const verifiedToken = await verifyJwt(
req.cookies.jwt,
process.env.JWT_PRIVATE_KEY
);
if (!verifiedToken.id) {
return res.redirect("/auth");
}
const user = await userModel.findOne({ _id: verifiedToken.id });
if (!user) {
return res.redirect("/auth");s
}
if (user.passwordChangedAt) {
if (new Date(verifiedToken.iat * 1000) < user.passwordChangedAt) {
res.clearCookie("jwt");
return res.redirect("/auth");
}
}
user._id = verifiedToken.id;
req.user = user;
next();
};
and I use it like this in my route handlers
my auth route handler:
router.post("/signup", authController.signup);
router.post("/signin", authController.signin);
router.use(authController.protect);
router.post("/password", authController.updatePassword);
my views route handler:
router.get("/auth", viewController.showAuthPage);
router.use(authController.protect);
router.get("/", viewController.showIndexPage);
router.get("/auth/account", viewController.showAccountPage);
and I use both route handlers in app.js by this order:
app.use("/auth", authRoutes);
app.use("/", viewRoutes);
With this, I can't access any route of my page and it just gives me 127.0.0.1 redirected you too many times error.
When I remove protect from my authRoutes it just works fine.
when I use viewRoutes before auth, it doesn't give me that error but all routes in authRoute will be inaccessible and my app doesn't reach any of them.
This is my project in Github and you can check all of it:
here
I'm having some problems using 2 middlewares inside the same function, already tried to search for all internet and didn't find a useful solution.
validator file
module.exports = {
create: async (req, res, next) => {
await celebrate(options.create)(req, res, next);
return res.status(500).json({ message: 'middleware 2'});
},
}
routes file
routes.post('/user', UserValidator.Create ,UserController.create);
The celebrate lib filters some basic validations like string lenght, null values, etc. And the celebrate() function returns another function with the (req, res, next) params.
When the celebrate returns the validation error, it stills continues to execute the code, so it tries to execute the next return and I get an error because the return has already been sent.
When using separate middlewares in the routes, it works normally:
routes.post('/user', celebrate(...), middleware2 ,UserController.create);
I also tried this way but the same thing happens, but now without an error, just returning the middleware2 result.
module.exports = {
create: async (req, res, next) => {
await celebrate(options.create)(req, res, () => {
return res.status(500).json({ message: 'middleware 2'});
});
},
Is there a way to fix this?
u should try this structure
// API
app.post('/something', Middleware.validate, Controller.create)
//Middleware
const validate = (req, res, done) => {
const errorArray = []
const body = req.body
// identifier is required, Validating as String, and length range.
if (!_.isString(body.identifier) || body.identifier.length < 2 || body.identifier.length > 10) {
errorArray.push({
field: 'identifier',
error: 70000,
message: 'Please provide only valid \'identifier\' as string, length must be between 2 and 10.'
})
}
if (!_.isEmpty(errorArray)) {
return errorArray
}
done()
}
module.exports = {
validate
}
// Controller
const create = function (req, res) {
return // your functionality
}
module.exports = {
create
}