Why I am getting Query was already executed error? - javascript

I have the following Schema and Model:
const userSchema = new mongoose.Schema({
name: String,
})
const UserModel = mongoose.model('User', userSchema, 'users')
and I have written the following express middleware, which simply takes one argument, awaits that argument, set the returned value from that awaiting job to the req object on a property called gottenDocs, (i.e.: req.gottenDocs)
function getDocumentsMw(query) {
return async (req, res, next) => {
const dbRes = await query
req.gottenDocs = dbRes
next()
}
}
and I have the following route:
app.get(
'/users',
getDocumentsMw(UserModel.find({})),
(req, res, next) => {
const gottenDoc = req.gottenDocs
res.status(200).json({
status: 'success',
data: gottenDoc,
})
})
That's all I have, now, when I request [ GET " /users " ] I recieve the following response which is great, and nothing is wrong:
{
"status": "success",
"data": []
}
but, the weird thing is when I request this route again, it throws this error:
MongooseError: Query was already executed: User.find({})
What could be the problem? is it a bug in Nodejs? which could be hmmm, something like, that it is not removing the function call from the call stack after the response has been sent?
any help appreciated.

The problem is on this line
getDocumentsMw(UserModel.find({})),
Here you create a query once the application start, because of that the query is created once but executed multiple times.
You may need to refactor your code to something like that
getDocumentsMw(() => UserModel.find({})),
Now you are passing a function and not a query. The function creates a query, aka factory. Next step is to refactor getDocumetnsMw to call the function to create a query when it needs to do something with it.
function getDocumentsMw(queryFactory) {
return async (req, res, next) => {
const dbRes = await queryFactory()
req.gottenDocs = dbRes
next()
}
}

Related

How to attach user credentials to the request pipeline in expressjs?

I am trying to write a middleware that extracts the user model and attach it to the request pipeline.
I have already written a token extractor middleware and managed to attach the token to the request pipeline, but for some reason when I try to extract the user model, it works fine inside the middleware function yet inside my controller it returns as undefined.
Here's what I have tried:
utils/middleware.js
const tokenExtractor = async (request, response, next) => {
const authorization = await request.get('authorization');
if (authorization && authorization.toLowerCase().startsWith('bearer ')) {
request.token = authorization.substring(7);
} else{
request.token = null;
}
next();
};
const userExtractor = async (request, response, next) => {
tokenExtractor(request, response, next);
if(request.token){
const decodedToken = jwt.verify(request.token, process.env.SECRET);
request.user = await User.findById(decodedToken.id);
console.log(request.user); // Works
next();
} else{
response.status(403).json({ error: 'no token received' });
}
};
Inside my controllers it breaks down:
controllers/blogs.js
blogRouter.post("/", async (request, response, next) => {
if (request.body.title && request.body.url) {
const token = request.token;
if (!token) {
return response.status(401).json({ error: 'invalid token' });
}
console.log(request.user); // undefined !
if(!request.user){
return response.status(401).json({ error: 'invalid user' });
}
const user = request.user;
const blog = new Blog({
title: request.body.title,
author: request.body.author,
url: request.body.url,
likes: request.body.likes,
user: user._id,
});
await blog.save();
user.blogs = user.blogs.concat(blog._id);
await user.save();
response.status(201).json(blog);
}
response.status(400).end();
});
Both middleware are already attached to the express app.
EDIT:
I have fixed the issue by removing the call to tokenExtractor from userExtractor function, and then chaining the middleware to the router instead of calling them before everything.
I was using the tokenExtractor globaly, while the userExtractor locally to the blogsRouter. What was happening was that while the tokenExtractor was working fine, the blogRouters was being called before the userExtractor ever get called, hence why I was getting undefined.
app.js
// app.use(tokenExtractor);
app.use(requestLogger);
app.use(errorHandler);
// app.use(userExtractor);
app.use('/api/login', tokenExtractor, loginRouter);
app.use('/api/users', usersRouter);
app.use('/api/blogs', tokenExtractor, userExtractor, blogRouter); // chaining the extractors
It makes sense, let next() carry the (req, res, next) instances forward, as a pipe. No hacks are needed and you can stack as many middlewares as needed and even reuse values from one inside the other - if you can trust the order of the call stack.
You don't need to chain it. The callback argument for the next middleware function only needs to be specified as follows.
const tokenExtractor = (request, response, next) => {
const auth = request.get('authorization')
if (auth && auth.toLowerCase().startsWith('bearer ')) {
request.token = auth.substring(7)
next()
} else {
next()
}
}

How to pass req.params as an an argument to a middleware function?

I'm trying to figure out a way to use req.params as an argument in my middleware. Take this (obviously broken) code for example:
router.post('/:myParam', checkSchema(schemas[req.params.myParam]), async (req, res, next) => {
// do stuff
})
The goal here is that I am using express-validator and I load a dynamic schema based on what param is passed. The above code is obviously broken because I don't yet have the scope to access the req variable, I'm just trying to illustrate what I'm trying to accomplish.
if you know the possible params ahead, you could do something like the following:
router.post("/:myParam", checkSchema("soccer"), async (req, res, next) => {});
//checkSchema.JS
const soccerSchema = require("../schemas/soccerSchema");
const swimmingSchema = require("../schemas/swimmingSchema");
module.exports = function (schemaName) {
return (req, res, next) => {
const schemas = { soccer: soccerSchema, swimming: swimmingSchema };
//You can access it here schemas[schemaName]
console.log(schemas[schemaName]);
next();
};
};
You can directly call schemas(req.params.myParam) inside the checkSchema middleware since the middleware will have access to the request object.

chain middleware functions in custom function

I know that I can chain middleware functions after passing in the route like
const express = require('express');
const router = express.Router();
router.post('/', middlewareFunction1, middlewareFunction2, controllerFunction);
module.exports = router;
I would like to know if it's possible to call only one function (called gateway)
router.post('/', gatewayFunction1);
and this function is able to chain all those methods
const controller = require('../controllers/controller');
function gatewayFunction1(request, response, next) {
// validate route
// do other middleware stuff
// call controller.function1
}
module.exports = {
gatewayFunction1,
};
Why would I do that? I was thinking about separating the middleware logic from the routes. This gateway should just get executed after routing and before calling the router.
I tried to return an array of functions (example code)
function gatewayFunction1(request, response, next) {
return [(request, response, next) => {
console.log('called middleware 1');
next();
}, (request, response, next) => {
console.log('called middleware 2');
next();
}, (request, response, next) => {
response.status(200).json({
message: 'that worked',
});
}];
}
but when I call this api route I get no response
Could not get any response
so it keeps loading forever. Is there a way to chain these middleware functions within another function?
Your gatewayFunction1 does nothing except returns an array.
Just use router.
const express = require('express');
const gatewayFunction1 = express.Router();
gatewayFunction1.use(middlewareFunction1, middlewareFunction2, controllerFunction);
module.exports = gatewayFunction1;
Then
const gatewayFunction1 = require('...'); // where you define gatewayFunction1
router.post('/', gatewayFunction1);
Middleware should be a function and you are returning an array.If next function is not called it will get stuck. I don't like the whole idea combining them but I think the best way is to import all your middleware functions in one function and call them individually then use that function as your combined middleware.

Node/ Express Rest API keeps entering same controller function inspite of correct mapping

I am writing a node/express rest api.
Hitting,
http://localhost:5000/api/news
and
http://localhost:5000/api/news/?id=c5f69d56be40e3b56e55d80
both give me all the news objects because it enters the same .getNews function on for both the urls.
My controller:
const NewsController = {};
const News = require('../models/news.model');
// This implementation of getNews is using Promises
NewsController.getNews = function(req, res) {
console.log('Inside getNews');
sendResponse = function(arg) {
res.json(arg);
}
const allnews = News.find({}, function(err, ns) {
sendResponse(ns);
});
};
// ES6 style
NewsController.getSingleNews = async (req, res) => {
console.log("Inside getSingleNews");
const news = await News.findById(req.params.id);
res.json[news];
};
NewsController.createNews = async (req, res) => {
const news = new News(req.body);
await news.save();
res.json[{
'status': 'item saved successfully'
}];
};
NewsController.deleteNews = async (req, res) => {
await News.findByIdAndRemove(req.params.id);
res.json[{
'status': 'item deleted successfully'
}]
};
module.exports = NewsController;
My routes.js (I am using the router at /api. So app.js has // use Router
app.use('/api', newsRoutes);
)
const express = require('express');
const router = express.Router();
var newsController = require('../controllers/NewsController')
router.get('/news', newsController.getNews);
router.get('/news/:id', newsController.getSingleNews);
router.post('/news', newsController.createNews);
router.delete('news/:id', newsController.deleteNews);
module.exports = router;
My Model
const mongoose = require('mongoose');
const { Schema } = mongoose;
const newsSchema = new Schema({
title: { type: String, required: true },
content: { type: String, required: true },
author: { type: String },
image: { type: String },
source: { type: String }
});
module.exports = mongoose.model('news', newsSchema);
The issue with your code is the way you are trying to call your endpoint. Express routes don't match query string parameters.
Having said that, your call to the news endpoint that looks like this:
http://localhost:5000/api/news/?id=c5f69d56be40e3b56e55d80
Should look like this instead:
http://localhost:5000/api/news/c5f69d56be40e3b56e55d80
That way the id parameter will get mapped to the req.params.id property inside your getSingleNews controller.
Being that the expected behavior for the way you declared your route:
router.get('/news/:id', newsController.getSingleNews);
For more information on how express routes work, check the documentation here: https://expressjs.com/en/guide/routing.html
Use /news/:id first. Your request will be redirected to the first matched url following the declaration order.
So /api/news satisfies app.get(/news)? Yep, gets redirected to that controller.
/api/news/?id=c5f69d56be40e3b56e55d80 satisfies app.get(/news)? Yep, also gets redirected to /news controller.
By the way, as your getting the id from req.params you should use /news/c5f69d56be40e3b56e55d80. If you were to get it from req.query you wouldn't need another route. /news?id=c5f69d56be40e3b56e55d80 would be perfect, you'd just need to check the existence of req.query.

Using chai to mock http requests

I'm testing a nodejs app written using express. For the unit testing I'm using chai and sinon. I have the following route in my API that I would like to test.
In my test, I'm simulating the get request with the following code:
chai.request(app)
.get('/downloads')
.send({ wmauth: {
_identity: {
cn: "username",
}
} })
.end((err, res) => {
res.status.should.be.equal(200);
res.body.should.be.a('object');
res.body.should.have.property('Items', []);
AWS.restore('DynamoDB.DocumentClient');
done();
However, I'm always getting the error "Cannot read property '_identity' of undefined". Because the object "wmauth" is not sent in the request, so it is undefined. I have tried to use the send method to try to include it in the request, but no luck. I guess I need to mock it somehow and send it into the request but have no idea how to do it. Could someone help me with this?
Below the method to test:
app.get('/downloads', async (req, res) => {
const created_by_cn = req.wmauth['_identity'].cn;
if(!created_by_cn) {
return res.status(400).json({
error: 'Mandatory parameters: created_by_cn',
});
}
try {
const data = await downloadService.getDownloads(created_by_cn);
return res.status(200).json(data);
}
catch(error){
res.status(500).json({error: error.message});
}
});
THanks
I guess you forgot to use req.body as in:
const created_by_cn = req.body.wmauth['_identity'].cn;
Hope can solve your issue
Since chai-http use superagent, so according to its doc, you need to use query() in order to pass query parameter in get request:
chai.request(app)
.get('/downloads')
.query({ wmauth: {_identity: {cn: "username"}}})
.end((err, res) => { ... });
Then in the express route you can find the parameters in req.query:
app.get('/downloads', function (req, res) {
const created_by_cn = req.query.wmauth._identity.cn;
...
})

Categories