skipping documents in mongodb for pagination - node.js

I want to skip some documents according to the page count.
For example when I make a GET request to http://localhost:3001/posts?page=2 I want to get 10 documents per page from 10-20.
router/posts.js
const express = require("express");
const {
getPosts,
createPost,
updatePost,
updateLikeCount,
getPostById,
threeLatestPosts,
deletePost,
getTenPostsPerPage,
} = require("../controllers/posts");
const verifyToken = require("../utils/verifyToken");
const router = express.Router();
router.get("/threelatest", threeLatestPosts);
router.get("/", getPosts);
router.post("/", verifyToken, createPost);
router.put("/:id", updatePost);
router.get("/:id", getPostById);
router.delete("/delete/:id", verifyToken, deletePost);
// this is how I do the GET request
router.get("/?page=:page", getTenPostsPerPage);
module.exports = router;
Here is what I have tried to skip the document but it doesn't even make the GET request and I don't even get back the console.log
const getTenPostsPerPage = async (req, res) => {
try {
console.log("this is not getting logged!")
const page = req.params.page;
const perPage = 10;
const skip = perPage * (page - 1);
const post = await PostDB.find()
.sort({ createdAt: -1 })
.skip(skip)
.limit(perPage);
if (!post) {
return res.status(404).json({ message: "Post not found" });
} else {
res.status(200).json(post);
}
} catch (err) {
res.status(400).json({ message: err });
}
};
when I make a GET request http://localhost:3001/posts?page=2 from postman, it returns all the post documents but I expect to get the documents from 10-20. The console.log is not logged in the terminal also.

I think it's because you are trying to include the querystring as part of the path.
If you see the documentation for expressjs here: http://expressjs.com/en/guide/routing.html
have a look at the section "Route paths".
You will see that
Query strings are not part of the route path.
and
The characters ?, +, *, and () are subsets of their regular expression
counterparts.
So your question mark is not being interpreted how you expect.
I imagine what is happening is that it is one of your other routes matching your request, and so that is why you are not seeing your console.log either. It's not even hitting this route.

Related

mongoose throws internal server error when ID doesn't exist

mongoose findOne query throws Interal Server Error (500) when looking for a value that doesn't exists.
I'm pretty sure it should return null or empty array instead of throwing error.
Mongoose version: ^5.8.7
First attempt
router.get('/:myId', async(req, res, next) => {
const myId = req.params.myId;
await myDocument.findOne({ _id: myId }, (err, myData) => {
if (!myData) {
res.status(404).render('404');
} else {
res.render('myPage', myData);
}
}).exec()
});
second attempt
router.get('/:myId', async(req, res, next) => {
const myId = req.params.myId;
const myData = myDocument.findOne({_id: myId}).exec();
if (myData) {
//render normal page
}
// render 404 page
});
According to documentation, this should NOT happen.
Note: conditions is optional, and if conditions is null or undefined, mongoose will send an empty findOne command to MongoDB, which will return an arbitrary document. If you're querying by _id, use findById() instead.
Also tried to use findById() and find(). All cases the same happens.
It works perfectly when I pass a valid ID parameter.
How to search for data without throwing http error in case Id doesn't exists in collection?
router.get('/:myId', async(req, res, next) => {
You should check your route
You should try this code. First assign the ObjectId on top where are dependencies being called.
MongoDB id is not string. It is adviseble to pass the ObjectId.
const { ObjectId } = require('mongodb');
router.get('/:myId', async(req, res, next) => {
const myId = ObjectId(req.params.myId);
const myData = await myDocument.findOne({_id: myId}).exec();
if (myData) {
//render normal page
}
// render 404 page
});
The error was actually happening during the page render.
In fact, mongoose does NOT throws any error in this case.
I was trying to render a html page while setting ejs as View Engine.
Apparentely we can't mix things.

Should I use one or more global error handler middleware or manually send error responses in Express?

I hope you're having a good day.
First of all, I'm sorry if you think this is a stupid question but I'm genuinely curious as to what you do in your own projects or let's say in a production environment.
So I've been using express for quite some time now and I usually send my errors back to the client manually in each request handler, when something isn't expected or an error occurs as such:
userController.js
const ERRORS = require('../utils/errors.js');
const userController = {
updateUser: (req, res) => {
let id = req.params.id;
if(!validUserId(id))
return res.status(400).json({error:ERRORS.INVALID_USER_ID});
let user = await findUserById(id);
if(!user)
return res.status(404).json({error: ERRORS.USER_NOT_FOUND});
try {
let updatedUser = await updateUser(id);
return res.status(201).json({data: updatedUser});
}
catch(e){
return res.status(500).json({error:ERRORS.FAILED_UPDATE_USER});
}
}
}
As you can see this can get really unmanageable really quickly and just overall makes the code less readable, especially if you're doing this for every request.
My question here is: does using a catch all global error handler middleware slow down express or create potential problems I'm not aware of or is it actually a suggested approach by some?
The code would then look like:
errorHandler.js
const errorHandler = (err, req, res, next) => {
// assume this function returns details about an error given an error message
// such as if the custom error message is USER_NOT_FOUND
// => it returns {status:404, message: "User not found"}
const {status, message} = getErrorDetails(err.message);
if(!res.headersSent)
res.status(status).json({error: message})
}
userController.js
const ERRORS = require('../utils/errors.js');
const userController = {
updateUser: (req, res) => {
let id = req.params.id;
if(!validUserId(id))
throw new Error(ERRORS.INVALID_USER_ID);
let user = await findUserById(id);
if(!user)
throw new Error(ERRORS.USER_NOT_FOUND);
try {
let updatedUser = await updateUser(id);
return res.status(201).json({data: updatedUser});
}
catch(e){
throw new Error(ERRORS.FAILED_UPDATE_USER);
}
}
}
Thank you. Your insights are very much appreciated.

Node.js - Http get will not display data from MongoDb query in Postman or browser

I'm working on a node.js backend project that uses a MongoDb database. After I query the database and received the data it will not display the data in my browser using res.send(). I also tried res.json(). However, the data does display on my console,but just will not display in postman or my browser. Is the query data from mongoDB not json or an array? I did a little reading and it states it's a cursor pointing to the data. Can this data not be converted to display in a broswer?
mycode as well as console and browser display
ProductRouter.js
router.get('/', (req, res) => {
const products = allProducts.plantProducts();
setTimeout(() => {
if (products === "400") {
res.status("400").send("Error querying database");
}else{
console.log(products);
res.status("200").send(products);
}
}, 1000);
ProductController.js
async function plantProducts(){
try {
const products = await getProducts();
return products;
} catch(err) {
let code = "400";
return code;
//res.status('400').send(err.message);
}
}
plantProducts();
//Search mongodb for all products.
function getProducts() {
return new Promise((resolve, reject) => {
const products = Product
.find()
resolve(products);
});
}
The problem is that products yields a promise which express' res.json will not automatically await and convert to a json-response. You need await the promise and then send the response. Something like:
router.get('/', async (req, res) => {
const products = await allProducts.plantProducts();
res.json(products);
});

Chaining async await calls in Node/Express with an external time limit

I'm building a Slackbot that makes a call to an Express app, which then needs to 1) fetch some other data from the Slack API, and 2) insert resulting data in my database. I think I have the flow right finally using async await, but the operation is timing out because the original call from the Slackbot needs to receive a response within some fixed time I can't control. It would be fine for my purposes to ping the bot with a response immediately, and then execute the rest of the logic asynchronously. But I'm wondering the best way to set this up.
My Express route looks like:
const express = require('express');
const router = express.Router();
const knex = require('../../db/knex.js');
const slack = require('../../services/slack_helpers');
// POST api/slack/foo
router.post('/foo', async (req, res) => {
let [body, images] = await slack.grab_context(req);
knex('texts')
.insert({ body: body,
image_ids: images })
.then(text => { res.send('worked!'); }) // This sends a response back to the original Slackbot call
.catch(err => { res.send(err); })
});
module.exports = router;
And then the slack_helpers module looks like:
const { WebClient } = require('#slack/web-api');
const Slack = new WebClient(process.env.SLACKBOT_TOKEN);
async function grab_context(req) {
try {
const context = await Slack.conversations.history({ // This is the part that takes too long
channel: req.body.channel_id,
latest: req.headers['X-Slack-Request-Timestamp'],
inclusive: true,
limit: 5
});
} catch (error) {
return [error.toString(), 'error'];
}
return await parse_context(context);
};
function parse_context(context) {
var body = [];
context.messages.forEach(message => {
body.push(message.text);
});
body = body.join(' \n');
return [body, ''];
}
module.exports = {
grab_context
};
I'm still getting my head around asynchronous programming, so I may be missing something obvious. I think basically something like res.send perhaps needs to come before the grab_context call? But again, not sure the best flow here.
Update
I've also tried this pattern in the API route, but still getting a timeout:
slack.grab_context(req).then((body, images) => {
knex ...
})
Your timeout may not be coming from where you think. From what I see, it is coming from grab_context. Consider the following simplified version of grab_context
async function grab_context_simple() {
try {
const context = { hello: 'world' }
} catch (error) {
return [error.toString(), 'error']
}
return context
}
grab_context_simple() /* => Promise {
<rejected> ReferenceError: context is not defined
...
} */
You are trying to return context outside of the try block where it was defined, so grab_context will reject with a ReferenceError. It's very likely that this error is being swallowed at the moment, so it would seem like it is timing out.
The fix is to move a single line in grab_context
async function grab_context(req) {
try {
const context = await Slack.conversations.history({
channel: req.body.channel_id,
latest: req.headers['X-Slack-Request-Timestamp'],
inclusive: true,
limit: 5
});
return await parse_context(context); // <- moved this
} catch (error) {
return [error.toString(), 'error'];
}
};
I'm wondering the best way to set this up.
You could add a higher level try/catch block to handle errors that arise from the /foo route. You could also improve readability by staying consistent between async/await and promise chains. Below is how you could use async/await with knex, as well as the aforementioned try/catch block
const express = require('express');
const router = express.Router();
const knex = require('../../db/knex.js');
const slack = require('../../services/slack_helpers');
const insertInto = table => payload => knex(table).insert(payload)
const onFooRequest = async (req, res) => {
try {
let [body, images] = await slack.grab_context(req);
const text = await insertInto('texts')({
body: body,
image_ids: images,
});
res.send('worked!');
} catch (err) {
res.send(err);
}
}
router.post('/foo', onFooRequest);
module.exports = router;

[Koa]404 while passing throught the routeur

I'm having some trouble with the Koa framework. I'm trying to build a pretty basic server by I'm having a problem with my router. The ctx always return 404 despite passing in my functions.
Some code :
//www.js
const Koa = require('koa');
const app = new Koa();
const version = require('./routes/version');
app.listen(config.port, () => {
console.log('Server is listenning on port ' + config.port);
});
app.use(version.routes());
app.use(ctx => {
console.log ('test')
});
//version.js
const Router = require('koa-router');
const router = new Router();
router.prefix('/version');
router.use((ctx, next) => {
ctx.vFactory = new VersionFactory(ctx.app.db);
next();
});
router.get('/', getAllVersions);
async function getAllVersions(ctx, next) {
const ret = await ctx.vFactory.getAllVersions();
ctx.body = JSON.stringify(ret.recordset);
console.log(ctx.body)
await next();
}
I've checked a few threads. Most of the time, the problem seems to come from a non Promise based function in the await part of the router function. Here it is a simple DAO using mssql which is pretty promise based.
class DaoVersion {
constructor(db) {
this.pool = db;
}
async getAllVersions() {
const me = this;
return new Promise((resolve) => {
const ret= me.pool
.query(getVersion);
resolve(ret);
});
}
}
The console output seems good. I have my ctx.body set with my db data but if I try to check the whole context, I still have a 404. More interesting, if I try to ctx.res.write (using default node response) I got the "already end" message. So it seems Koa have sent the message before passing threw my function.
Any idea why and how I could correct that ?
Koa default response.status code is 404, unlike node's res.statusCode which defaults to 200.
Koa changes the default status code to 200 - when your route set's a non empty value to ctx.body or in some cases you can manually change (like if you need to set it to 202) it by using ctx.status = xxx.
You can use this documentation for reference: https://github.com/koajs/koa/blob/master/docs/api/response.md
Also, your route should be an async function:
router.get('/', async(ctx, next) => {
ctx.body = await getAllVersions
await next()
}

Resources