I am trying to pass variable to the next middlewares through the req object.
getting some data from database and passing that data to request for next middlewares to use.
User.findone({ _id: someId })
.then(user => { req.user = user })
.catch(err => { })
After that then and catch block i am using next().
Therefore for the next middlewares i am getting req.user undefined.
but if i pass the next() function in the then block after
req.user = user like .then(user=> {req.user = user; next()}) than i am getting req.user a valid user object to use for the next middlewares.
what is the reason for this behaviour??
That's because the User.findOne function is asynchronous. The result of that function is only known in the then block.
const middleware = (req, res, next) => {
User.findOne({ _id: someId })
.then(user => {
req.user = user;
})
.catch(err => { });
next(); // If you put next() here, the request will go straight to the next middleware without waiting for User.findOne() to complete.
};
const middleware = (req, res, next) => {
User.findOne({ _id: someId })
.then(user => {
req.user = user;
next(); // Putting next() here is correct
})
.catch(err => {
next(err); // Remember to handle error to avoid hanging the request
});
};
then... is called after the User.findone promise resolves. Thus, if you put next() outside of then, it will be called before then.
You could read more details at promise-basics
Alternatively try to use async-await as it looks more straightforward.
Related
I would like to get the data from session variable (req.user.username) then use it for posting. I'm using passportjs as authentication. I'm using router. Here is my code:
router.use('/login', passport.authenticate("local-register", async (err, user, info) => {
if (err) {
return next('Error');
}
if (!user) {
return next('Error');
}
req.user = user;
return req.login(user, (error: Error) => {
if (error) {
return next('Error');
}
return req.session.save((erro: Error) => {
if (erro) {
return next('Error');
}
return next();
});
});
})(req, res, next);)
router.get('/', async (req, res) => {
console.log(req.user.username) // working just fine
});
router.post('/upload', async (req, res) => {
const uploaderName = req.user.username // I'm getting undefined
const upload = await database.query('INSERT INTO user WHERE username=$1', [uploaderName])
console.log(uploaderName);
})
So I finally found the answer to the question. For those who will encounter the problem in the future. You just add the session middleware AGAIN on the top of the routes. If your routes are separated to the main server file.
/src/routes/routes.ts -> add again the middleware on top.
const app = router();
app.use(sessions) // -> right here you need to add the middleware again to //access the req.user session variable
app.get('/', async (req, res) => {
console.log(req.user.username) // working just fine
});
app.post('/upload', async (req, res) => {
const uploaderName = req.user.username // I'm getting undefined
const upload = await database.query('INSERT INTO user WHERE username=$1', [uploaderName])
console.log(uploaderName);
})
I have auth function, I want it to authenticate my user route.
// auth.js
function auth(request, response, next) {
passport.authenticate('jwt', { session: false }, async (error, token) => {
if (error || !token) {
response.status(401).json({ message: 'Unauthorized' });
}
next(token);
})(request, response, next);
next()
}
module.exports = auth;
And heres my jwt strategy
// passport.js
passport.use(
new JwtStrategy(opts, (payload, done) => {
console.log('payload', payload) // this works
User.findById(payload.id)
.then(user => {
if (user) {
console.log('here user', user) // this also works
return done(null, user);
}
return done(null, false);
})
})
);
But why when I console log my request It doesn't show me the user that I already declare in done(null, user)
const auth = require('../auth.js')
router.get('/', auth, async (req, res) => {
console.log(req.user) // return undefined
// other code
});
There are a couple issues that I can see:
From your auth() middleware function, your are calling next() before passport has had a chance to authenticate the incoming request - which happens asynchronously. You should remove the synchronous call to next() there, and defer to passport.authenticate() callback to handle this.
In passport.authenticate() callback, you're calling next() with an argument - express will take this as an error occurring and jump to the next error middleware in line.
Edit: I also checked the signature of the passport.authenticate() callback and it seems to be (error, user, info) - not (error, token).
Edit 2: It also seems like when passing passport.authenticate() a custom callback, it becomes your responsability to expose user on the req object by calling passport req.login() function. Please take a look here:
http://www.passportjs.org/docs/authenticate/ (custom callback section at the end)
http://www.passportjs.org/docs/login/
I have the following endpoint:
app.get('/users/:id', async (req, res) => {
const _id = req.params.id;
try {
const user = await User.findById(_id);
if(!user) {
res.status(404).send();
}
res.send(user);
} catch (e) {
res.status(500).send(e);
}});
When I make the request with a valid user ID, the server sends back the user, no problem with that.
The problem is when I try to find a user with a ID which doesnt exist in the database. The server should response with a 404 Error but instead it sends back a Error 500 and I dont understand why!
Could anyone help me please?
Thank you in advance!
One nice way to handle the errors is to create an express error middleware, this allows you to put all of your error handling in one place so that you dont have to write it more than once.
With express when you use async routes handlers if a promise rejects the error will automatically be passed to the next error middleware.
// First register all of your routes
app.get('/user/:id', async (req, res) => {
const user = await User.findById(req.params.id);
if(!user) return res.status(404).send();
res.send(user);
})
// Then register you error middleware
app.use((err, req, res, next) => {
console.error(err.message)
// if mongoose validation error respond with 400
if(err.message.toLowerCase().includes('validation failed'))
return res.sendStatus(400)
// if moongoose failed because of duplicate key
if(err.message.toLowerCase().includes('duplicate key'))
return res.sendStatus(409)
// if mongoose failed to cast object id
if(err.message.toLowerCase().includes('objectid failed'))
return res.sendStatus(404)
res.sendStatus(500)
})
Thank you for your answers.
I have solved it adding the following to the user model schema:
_id: {type: String}
And adding a return before sending the 404 error:
app.get('/users/:id', async (req, res) => {
const _id = req.params.id;
try {
const user = await User.findById(_id);
if (!user) {
return res.status(404).send();
}
res.send(user);
} catch (error) {
res.status(400).send(error);
}});
I have an async function as a route handler, and i'd like to have errors handled as some kind of middleware. Here is my working attempt:
router.get(
"/",
asyncMiddleware(
routeProviderMiddleware(
async ({ y }) => ({
body: await db.query({x: y})
})
)
)
)
// This is the middleware that catches any errors from the business logic and calls next to render the error page
const asyncMiddleware = fn =>
(req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch(next)
}
// This is a middleware that provides the route handler with the query and maybe some other services that I don't want the route handler to explicitly access to
const routeProviderMiddleware = routeHandlerFn => async (req, res) => {
const {status = 200, body = {}} = await routeHandlerFn(req.query)
res.status(status).json(body)
}
What I strive to is a way to make the route declaration cleaner - I don't want the 2 middleware wrappers there, ideally i'd like for the business logic function there only, and somehow declare that every route is wrapped in these.
Even combining the two middlewares together would be nice, but I didn't manage.
I use following approach:
Create asyncWrap as helper middleware:
const asyncWrap = fn =>
function asyncUtilWrap (req, res, next, ...args) {
const fnReturn = fn(req, res, next, ...args)
return Promise.resolve(fnReturn).catch(next)
}
module.exports = asyncWrap
All your routes/middlewares/controllers should use this asyncWrap to handle errors:
router.get('/', asyncWrap(async (req, res, next) => {
let result = await db.query({x: y})
res.send(result)
}));
At app.js, the last middleware will receive the errors of all asyncWrap:
// 500 Internal Errors
app.use((err, req, res, next) => {
res.status(err.status || 500)
res.send({
message: err.message,
errors: err.errors,
})
})
Express 5 automatically handles async errors correctly
https://expressjs.com/en/guide/error-handling.html currently says it clearly:
Starting with Express 5, route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error. For example:
app.get('/user/:id', async function (req, res, next) {
var user = await getUserById(req.params.id)
res.send(user)
})
If getUserById throws an error or rejects, next will be called with either the thrown error or the rejected value. If no rejected value is provided, next will be called with a default Error object provided by the Express router.
I have shown that in an experiment at: Passing in Async functions to Node.js Express.js router
This means that you will be able to just make the callback async and use await from it directly without any extra wrappers:
router.get("/", async (req, res) =>
const obj = await db.query({x: req.params.id})
// Use obj normally.
)
and errors will be correctly handled automatically.
Express permits a list of middlewares for a route and this approach sometimes works for me better than higher-order functions (they sometimes look like an overengineering).
Example:
app.get('/',
validate,
process,
serveJson)
function validate(req, res, next) {
const query = req.query;
if (isEmpty(query)) {
return res.status(400).end();
}
res.locals.y = query;
next();
}
function process(req, res, next) {
Promise.resolve()
.then(async () => {
res.locals.data = await db.query({x: res.locals.y});
next();
})
.catch((err) =>
res.status(503).end()
);
}
function serveJson(req, res, next) {
res.status(200).json(res.locals.data);
}
What you can do is add an error handlers after your routes. https://expressjs.com/en/guide/error-handling.html
app.use(function (err, req, res, next) {
console.error(err.stack)
res.status(500).send('Something broke!')
})
What I ended up doing is unifying the wrappers like this:
const routeProvider = routeHandlerFn => async (req, res, next) => {
try {
const {status = 200, body = {}} = await routeHandlerFn(req.query)
res.status(status).json(body)
} catch(error) {
next(error)
}
}
This wrapper is all any route would need. It catches unexpected errors and provides the route handler with the needed params.
isAuthenticated() {
return compose()
// Validate jwt
.use(function(req, res, next) {
// allow access_token to be passed through query parameter as well
if (req.query && req.query.hasOwnProperty('access_token')) {
req.headers.authorization = 'Bearer ' + req.query.access_token;
}
validateJwt(req, res, next);
})
// Attach user to request
.use(function(req, res, next) {
User.findById(req.user._id)
.exec()
.then(user => {
if (!user) {
return res.status(401).end();
}
req.user = user;
return next();
})
.catch(err => { console.log(err); next(err) });
});
}
I am writing a piece of composable middle ware and the .then for the User.findById throws the following error Warning: a promise was created in a handler but was not returned from it. why does this happen? I return the next(); and if the user is not found i also return.