Nodejs Pagination using Shipment.all and Tracker.all - easypost

Appolgies i am quite new to Nodejs and programming, so Long story short:
Using the Easypost Nodejs API, I need to retrieve all Tracking Status 3 times a day using my Nodejs/express backend.
I hit a rate limit on my very inefficient way of retrieving all my tracking status'. Which is a seperate call for each Shipmentid to get the Trackingid which then gets the most recent status. Horrendously bad i know.
So i went to the Docs and thought, cool i'll do things seperately, get all my packages (myDB) then seperately get a list of all trackers then match up using shipment ID.
Problems...
i'm a newbie.
how to do multiple calls with pagination -> my first thoughts are to run an initial call to see if there are multiple pages, if "has_more" is true in then run as many times as needed on as many pages as i need for the last 2 months.
next problem... i seem to be missing the paging information in the response from the easypost API. In the docs there should be a response of
{trackers:[all tracker info here], "has_more": true}
but there is is only an array of trackers in the response. even if i set the page size to 1 or 2...
Current code for getting the list of trackers:
.get((req, res) => {
console.log('Recieved - ' + req.method + req.originalUrl)
easyPostAPI.Tracker.all(req.body)
.then( response => {
console.log('Sent - ' + req.method + req.originalUrl)
res.json(response)
})
.catch(err => {
console.log(err)
res.status(400).json(err)
})
})```
hopefully it's not just me getting it wrong.

EasyPost just released a 3.8.0 version of the node library in which you can access the has_more property on the array, like so:
api.Shipment.all({ page_size: 2, ... }).then(shipment => console.log(shipment.has_more))
In #easypost/api v4.0.0 there is likely to be a more convenient way to paginate, but for now, the above property should allow you to paginate.

Related

Searching one collection multiple times in one call

I have an array with multiple values which need to be used as queries to search a collection. I am not sure of a way of doing this using one route. for example:
router.get('/', ensureAuthenticated, (req, res) => {
let ArrayOfIds = [id1, id2, id3]
Movie.find({user: req.user.id}).then(items => {
//Something along this lines
items.subscriptions.forEach(subscription => {
Movie.find({user: subscription})
//This wont work beacuse of the callback promise
.then(movies => {
items.push(movies)
})
})
res.render('index/home',
{pageName: 'Movies', items: items})
.catch(err => console.log(err));
});
})
I want to search the same collection (Movie) for each id and add it to the items object. Doing it with a for loop seems to create a set header error. Any ideas?
This S/O response should have your answer...
Error: Can't set headers after they are sent to the client
From the response:
The error "Error: Can't set headers after they are sent." means that
you're already in the Body or Finished state, but some function tried
to set a header or statusCode. When you see this error, try to look
for anything that tries to send a header after some of the body has
already been written. For example, look for callbacks that are
accidentally called twice, or any error that happens after the body is
sent.
In your case, you called res.redirect(), which caused the response to
become Finished. Then your code threw an error (res.req is null). and
since the error happened within your actual function(req, res, next)
(not within a callback), Connect was able to catch it and then tried
to send a 500 error page. But since the headers were already sent,
Node.js's setHeader threw the error that you saw.
It's likely that you're calling res.redirect() multiple times if you're implementing a for-loop.
Maybe if I understand correctly, you can query the collection with list of ids in your case ArrayOfIds into in query of mongo.
Check $in query of mongoDB.
FYI: create some index on that field.

Firebase Cloud Function, getting a 304 error

I have a firebase cloud function that resets a number under every user's UID every day back to 0. I have about 600 users and so far it's been working perfectly fine.
But today, it's giving me a 304 error and not reseting the value. Here is a screenshot:
And here is the function code:
export const resetDailyQuestsCount = functions.https.onRequest((req, res) => {
const ref = db.ref('users');
ref.once('value').then(snap => {
snap.forEach(item => {
const uid = item.child('uid').val();
ref.child(uid).update({ dailyQuestsCount: 0 }).catch(err => {
res.status(500).send(err);
});
});
}).catch(err => {
res.status(500).send(err);
})
res.status(200).send('daily quest count reset');
})
Could this be my userbase growing too large? I doubt it, 600 is not that big.
Any help would be really appreciated! This is really affecting my users.
An HTTP function must only send a single response to the client. This means a single call to send(). Your function can possibly attempt to send multiple responses to the client in the even that there are multiple updates that fail. Your logging isn't complete enough to demonstrate this, but it's a very real possibility with what you've shown.
Also bear in mind that this function is very much not scalable since it reads all of your users prior to processing them. For large number of users, this presents memory problems. You should look into ways to limit the number of nodes read by your query in order to prevent future problems.

Nodejs - tips for creating multiple endpoints

I have a nodejs/express server being used by both Web application and Mobile application, but for now they use the same end points. But I want to divide my api into 2 one of which is for mobile and obviously the other is for web. The requests are going to be "exactly" the same. What comes to my mind as a solution is duplicating all the request where paths for newly created ones are different(so that in the mobile app, these request can be used). But this solution does not seem right, as it may mean making big changes on the client side. Is there an elegant and also favourably easier solution? Any suggestion would be appreciated.
router.get('api/snow/manuel',
function (req, res, next) {
const snowProjection = {_id: 0};
snowThick.find({}, snowProjection)
.toArray(function (err, data) {
if (err) return next(new APIError.ServerError("An error occured" + " " + err));
return res.send(data);
})
});
Here is an sample get request in my server.

Should I return an array or data one by one in Mongoose

I have this simple app that I created using IOS, it is a questionnaire app, whenever user clicks play, it will invoke a request to node.js/express server
Technically after a user clicks an answer it will go to the next question
I'm confused to use which method, to fetch the questions/question
fetch all the data at once and present it to the user - which is an array
Fetch the data one by one as user progress with the next question - which is one data per call
API examples
// Fetch all the data at once
app.get(‘/api/questions’, (req, res, next) => {
Question.find({}, (err, questions) => {
res.json(questions);
});
});
// Fetch the data one by one
app.get('/api/questions/:id', (req, res, next) => {
Question.findOne({ _id: req.params.id }, (err, question) => {
res.json(question);
});
});
The problem with number 1 approach is that, let say there are 200 questions, wouldn’t it be slow for mongodb to fetch at once and possibly slow to do network request
The problem with number 2 approach, I just can’t imagine how to do this, because every question is independent and to trigger to next api call is just weird, unless there is a counter or a level in the question mongodb.
Just for the sake of clarity, this is the question database design in Mongoose
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const QuestionSchema = new Schema({
question: String,
choice_1: String,
choice_2: String,
choice_3: String,
choice_4: String,
answer: String
});
I'd use Dave's approach, but I'll go a bit more into detail here.
In your app, create an array that will contain the questions. Then also store a value which question the user currently is on, call it index for example. You then have the following pseudocode:
index = 0
questions = []
Now that you have this, as soon as the user starts up the app, load 10 questions (see Dave's answer, use MongoDB's skip and limit for this), then add them to the array. Serve questions [index] to your user. As soon as the index reaches 8 (= 9th question), load 10 more questions via your API, and add them to the array. This way, you will always have questions available for the user.
Very good question. I guess the answer to this question depends on your future plans about this app.
If you are planning to have 500 questions, then getting them one by one will require 500 api calls. Not the best option always. On the other hand, if you fetch all of them at once, it will delay the response depending on the size of each object.
So my suggestion will be to use pagination. Bring 10 results, when the user reaches 8th question update the list with next 10 questions.
This is a common practice among mobile developers, this will also give you the flexibility to update next questions on the basis of previous responses from user. Like Adaptive test and all.
EDIT
You can add pageNumber & pageSize query parameter in your request for fetching questions from server, something like this.
myquestionbank.com/api/questions?pageNumber=10&pageSize=2
receive these parameters in on server
var pageOptions = {
pageNumber: req.query.pageNumber || 0,
pageSize: req.query.pageSize || 10
}
and while querying from your database provide these additional parameters.
Question.find()
.skip(pageOptions.pageNumber * pageOptions.pageSize)
.limit(pageOptions.pageOptions)
.exec(function (err, questions) {
if(err) {
res.status(500).json(err); return;
};
res.status(200).json(questions);
})
Note: start your pageNumber with zero (0) it's not mandatory, but that's the convention.
skip() method allows you to skip first n results. Consider the first case, pageNumber will be zero, so the product (pageOptions.pageNumber * pageOptions.pageSize) will become zero, and it will not skip any record.
But for next time (pageNumber=1) the product will result to 10. so it will skip first 10 results which were already processed.
limit() this method limits the number of records which will be provided in result.
Remember that you'll need to update pageNumber variable with each request. (though you can vary limit also, but it is advised to keep it same in all the requests)
So, all you have to do is, as soon as user reaches second last question, you can request for 10 (pageSize) more questions from the server as put it in your array.
code reference : here.
You're right, the first option is a never-to-use option in my opinion too. Fetching that much data is useless if it is not being used or has a chance to not to be used in the context.
What you can do is that you can expose a new api call:
app.get(‘/api/questions/getOneRandom’, (req, res, next) => {
Question.count({}, function( err, count){
console.log( "Number of questions:", count );
var random = Math.ceil(Math.random() * count);
// random now contains a simple var
now you can do
Question.find({},{},{limit:1,skip:random}, (err, questions) => {
res.json(questions);
});
})
});
The skip:random will make sure that each time a random question is fetched. This is just a basic idea of how to fetch a random question from all of your questions. You can put further logics to make sure that the user doesn't get any question which he has already solved in the previous steps.
Hope this helps :)
you can use the concept of limit and skip in mongodb.
when you are hitting the api for the first time you can have your limit=20 and skip=0 increase your skip count every time you that api again.
1st time=> limit =20, skip=0
when you click next => limit=20 , skip=20 and so on
app.get(‘/api/questions’, (req, res, next) => {
Question.find({},{},{limit:20,skip:0}, (err, questions) => {
res.json(questions);
});
});

How to get Express to return the object I just deleted in MongoDB

Feel free to let me know if this isn't a common practice - I'm a fairly new programmer - but I thought I've seen APIs in the past that, when you submit a DELETE request to a resource (/todo/1234), some servers will return the object you just deleted in the response. Is that a thing? If so, I'd be interested in learning how to do it. Here's what I have:
.delete(function (req, res) {
Todo.findById(req.params.todoId).remove(function (err) {
if (err) res.status(500).send(err);
res.send("Todo item successfully deleted");
});
});
This code does delete the item, but I would like to return the item that got deleted in the response instead of a string message. If that's a normal/okay thing to do. If it isn't normal or okay for some reason, please let me know why and I'll just move on. Or perhaps there's a more common way.
This is what I found in the [RFC 7231 docs][1]:
If a DELETE method is successfully applied, the origin server SHOULD
send a 202 (Accepted) status code if the action will likely succeed
but has not yet been enacted, a 204 (No Content) status code if the
action has been enacted and no further information is to be supplied,
or a 200 (OK) status code if the action has been enacted and the
response message includes a representation describing the status.
I'm having a hard time interpreting what the 200 response means - is it only kosher to send a string message (Success!) or an object containing a message attribute ({message: "Success!"})? Or can you do whatever you want there? What's the best practice in Express using Mongoose?
Thanks in advance for the help, and sorry for my noobness with HTTP stuff.
[1]: https://www.rfc-editor.org/rfc/rfc7231#section-4.3.5
You should use findOneAndRemove! Something like:
Todo.findOneAndremove({ id: req.params.todoId }, function( error, doc, result) {
// it will be already removed, but doc is what you need:
if (err) res.status(500).send(err);
res.send(doc.id);
});

Resources