Node.js API return different values for the same request - javascript

I'm learning to use Node.js + Express to build a REST API. In this API
I have the following method:
apiRouter.route('/training/session/byId/:id_session')
// ===== GET =======
.get(function(req, res) {
//Get the session
Session.findById(req.params.id_session, function(err, session) {
//Get an array of exercise associated with the session
Exercise.find({session: session._id}, function(err, exercise) {
let movements = [];
let sets = [];
let i = exercise.length-1;
//For every exercise get the movements and the sets
exercise.forEach(function (ex,index) {
Movement.findById(ex.movement,function(err,movement){
if(movement)
movements.push(movement);
//***** Here?
Set.find({exercise: ex}, function (err, set) {
if(set.length)
sets.push(set);
if(index == i){
res.json({ message: 'ok' ,session,exercise,movements,sets});
}
})
})
})
});
});
})
The idea is obtain all the session related information from the database.
First:
I think that is not the correct way of make multiple querys and return an object with the info of all the querys, but I'm novice with the async working of Node... So what is the correct way to make multiple querys where the data of one query depends of other query?
Second: In the Front-End (React + Redux) I'm making Ajax request with axios and for the same Ajax request sometimes not all 'sets' are fetched (//***** Here?). The problem is in the API?
Thanks in advance.
EDIT: DB models
Session:
var SessionSchema = new Schema({
date: {type: Date, default: Date.now },
time: Number, //Time in seconds
user: {required: true, type: mongoose.Schema.Types.ObjectId, ref: 'User'},
});
Exercise:
var ExerciseSchema = new Schema({
session: {type: mongoose.Schema.Types.ObjectId, ref: 'Session'},
movement: {type: mongoose.Schema.Types.ObjectId, ref: 'Movement'},
timestamp: {
type: Date,
default: Date.now
}
});
Set:
var SetSchema = new Schema({
repetitions: Number,
weight: Number,
rest: Number,
timestamp: {
type: Date,
default: Date.now
},
exercise : {type: mongoose.Schema.Types.ObjectId, ref: 'Exercise'}
});
Movement:
var MovementSchema = new Schema({
name: { type: String, required: true, index: true, unique: true },
material:{ type: String, required: true},
muscles : [{
name : { type: String, required: true},
percentage : { type: Number, required: true}
}]
});

Set.find({exercise: ex}, function (err, set) {
if(set.length)
sets.push(set);
}).then(function(set){
if(index == i){
res.json({ message: 'ok' ,session,exercise,movements,sets});
}
})
Of course my prev answer wouldn't work. The set query callback would execute after the if(index == i). Actually I'm not sure this will produce different results from your code. I've never actually used Mongoose but as far as I know, you can't do joins so nested queries is the way to do it.
You might want to consider using promises. Not necessary, but they make your code easier to read and think about: http://eddywashere.com/blog/switching-out-callbacks-with-promises-in-mongoose/
It might make more sense as well to create a single result object that you build up as the queries return so you end up sending a single JSON object representing your session that looks like:
{
exercises: [
{
sets: [],
movements: [],
},
{
sets: [],
movements: [],
},
...
]
}

Related

Mongoose - Best way to get followers posts

I am building a social media and one of the features I have is the ability to follow other users and have their posts show up on a home page, much like an Instagram feed. I have an endpoint that loops through all the users you are following and gets the post that are rated for your age, sorts them and puts them in an array.
router.get("/following", auth, async (req, res) => {
try {
const user = await User.findOne({ firebaseUID: req.authId });
const dob = dayjs(user.dob.toISOString().split("T")[0]);
const currentDate = dayjs(new Date().toISOString().split("T")[0]);
const age1 = currentDate.diff(dob, "year");
let posts = [];
for (let i = 0; i < user.following.length; i++) {
posts.push(
await Post.find({
userID: user.following[i].user,
age: { $lte: age1 },
})
);
}
posts = [].concat.apply([], posts);
posts = _.sortBy(posts, "date").reverse();
res.json(posts);
} catch (err) {
console.error(err.message);
res.status(500).json({ msg: "Server Error" });
}
});
The user schema looks like this
const UserSchema = new Schema({
firebaseUID: {
type: String,
required: true,
},
name: {
type: String,
required: true,
},
username: {
type: String,
required: true,
unique: true,
},
avatar: {
type: String,
},
dob: {
type: Date,
default: Date.now,
required: true,
},
slug: {
type: String,
},
following: [
//array of user IDs
{
user: {
type: Schema.Types.ObjectId,
ref: "users", //here so we know which lieks came from which users
},
},
],
followers: [
//array of user IDs
{
user: {
type: Schema.Types.ObjectId,
ref: "user", //here so we know which lieks came from which users
},
},
],
});
The problem is that I am trying to add infinite scroll pagination to the front end that will start by getting the first 15 posts and then get the next 15 and so on from endpoints that provide this.
What would the best way to do this be? For other routes, I have used .skip() and .limit(). I could split the array of posts up right before I send it as a response, but is there a way to do without getting all the posts as this would be taxing if you're following 100s of people with 1000s of posts.
I would need to loop through all the users you are following and get all their posts in line with your age rating but only get the first 15 sorted in date order from newest to oldest. Is there a mongoose function that I could use or would the best way be to split the array of posts?
Thank you.

Express: return is not showing array in api

So I have collections of Teacher, Classes. to understand my problem its necessary to understand my database. the structure is
const TeacherSchema = new Schema(
{
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
role: {
type: String,
default: "teacher"
},
userid: {
type: String,
required: true
},
password: {
type: String,
required: true
},
profileImage: {
type: String,
required: false
},
classes: [{
type:Schema.Types.ObjectId,
ref:'class'
}]//1 teacher will have multiple classes
},
{ timestamps: true }
);
and the class:
const ClassSchema = new Schema(
{
subject: {
type: String,
required: true
},
teacher:[{
type:Schema.Types.ObjectId,
ref: 'teacher'
}],//once class will have only one teacher
},
{ timestamps: true }
);
What i want to do is, I want to show teacher how many classes he/she has. here is my controller.
exports.getAllClass= async (req, res, next) => {
let teacherClasses
try{
teacherClasses= await Teacher.findById(req.params.id)//teacher database
arrayOfClass= teacherClasses.classes //getting the class it has array of objectID
arrayOfClass.forEach(async (classes)=>{
let classDB =await Class.findById(classes)
console.log(classDB.subject)// in the console it shows all the classes.
return res.status(200).json({
'name':classDB.subject //but here it only shows one class,the first class name
})
});
}catch(err){
console.log(err)
}
}
I have tried many thing, but cant reach to a proper solution. Can you tell me what did i do wrong here or is there any better approach? I want to show all the classes in the return. I am new in programming hence assigned in complex task, please help me.
You are using forEach on the arrayOfClass and within that forEach you return and send the response. So as soon as the first promise is resolved, the response is sent.
One simple way to do this is to use a for .. of loop and await each class-promise inside and keep track of each result. Once that's done you can send the response:
...
const classes = [];
for(const classId of arrayofClass) {
const classDB = await Class.findById(classId);
classes.push({ name: classDB.subject });
}
return res.json(classes);
Another way to do this is to use Promise.all() which might be better if you're dealing with a bigger dataset as is processes in parallel whereas the first solution processes in sequence:
...
const classPromises = await Promise.all(arrayofClass.map(classId => Class.findById(classId)));
const result = classPromises.map(classDB => ({
name: classDB.subject
}));
return res.json(result);

Adding JSON array to a Mongoose schema (JavaScript)

I'm creating an Android App (A baseball app) where I'm using MongoDB to store my data. the way I want my JSON data to be stored into the database is like this.
{"email#domain.com":{
"coachName": "Smith"
players:[
player1:{
"throws_":"n\/a",
"position":"position not set",
"number":"1",
"playerNum":"H8E83NxRo6",
"bats":"n\/a",
"team_name":"Team",
"name":"Name"}
player2:{
"throws_":"n\/a",
"position":"position not set",
"number":"1",
"playerNum":"H8E83NxRo6",
"bats":"n\/a",
"team_name":"Team",
"name":"Name"}
]
}
sorry if there is any syntax error, but essentially that is the layout i want for the JSON. Where the mongo page "id" is the persons email. and where "players" is an array of the list of players the coach has.
My question is, how can I
properly setup the Mongoose schema to look like this?
when the app sends the JSON data, how can I parse it to store the data?
and if possible (ill try and figure this part on my own if no one can) if multiple players are being added at once, how can i store them if there's already players in the array?
All of this is backend/server side, I have the android working properly, i just need help with storing it to look like this in JavaScript.
You dont want to use a dynamic variable as a field name. I'm talking about the email you have "email#domain.com". This wouldn't be good because how will you find the object. Its not common to search for object by there fields, you use the field name to describe what it is your looking for. You will need 2 models. One for player and one for coach. Coach refrences a Player in its Players array field.
If you format your JSON correctly you wont have to do any parsing, just make sure the JSON you are sending is correctly formatted.
Look at the addPlayer function.
Controller file (Answer for questions 2 and 3)
create = function(req, res) {
var coach = new Coach(req.body);
coach.user = req.user;
coach.save(function(err) {
if (err) {
return res.status(400).send({
// Put your error message here
});
} else {
res.json(coach);
}
});
};
read = function(req, res) {
res.json(req.coach);
};
addPlayer = function(req, res) {
var coach = req.coach;
console.log('updating coach', req.coach);
var player = new Player(req.newPlayer);
coach.players.push(newPlayer);
coach.save(function(err) {
if (err) {
return res.status(400).send({
// Put your error message here
});
} else {
res.json(coach);
}
});
};
Player
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
/**
* Player Schema
*/
var PlayerSchema = new Schema({
created: {
type: Date,
default: Date.now
},
throws: {
type: Number,
},
name: {
type: String,
default: '',
trim: true
},
position: {
type: String,
default: '',
trim: true
},
playerNum: {
type: String,
default: '',
trim: true
},
position: {
type: Number,
default: 0,
trim: true
},
teamName: {
type: String,
default: '',
trim: true
}
});
mongoose.model('Player', PlayerSchema);
Coach Schema
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
/**
* Coach Schema
*/
var CoachSchema = new Schema({
created: {
type: Date,
default: Date.now
},
email: {
type: String,
unique: true,
lowercase: true,
trim: true,
default: ''
},
name: {
type: String,
default: '',
trim: true
},
players: [{
type: Schema.ObjectId,
ref: 'Player'
}
});
mongoose.model('Coach', CoachSchema);

Mongoose/MongoDb ,how to validate an array of Ids against another model

I have 2 moongose Schema:
var Schema2 = new Schema({
creator : { type: String, ref: 'User'},
schema_name : [{ type: String}],
});
var Schema1 = new Schema({
creator : { type: String, ref: 'User'},
schema_ref : [{ type: String, ref: 'Schema2' }],
});
Would like to know which is the best practice when I create a new Schema1 check that every element of array schema_ref, have the same creator.
Because schema1 elements are added by client form and so i have to check that the schema_ref elements are owned by same User that send the form
You can try with either validator function, or with a simple 'save' middleware:
Schema1.pre('save', function(next) {
let owner;
for (let entry in this.schema_ref) {
if (!owner) {
owner = entry;
} else {
if (entry !== owner) {
return next(new Error("owner mismatch");
}
}
}
});
Also, your schema might not work as you expect it to, it looks like you actually need:
schema_ref: [{
type: {type: String},
ref: "User"
}]
Additionally, take a look at id-validator plugin, or some similar to that - it will, in addition to your validation, also check that all ref-type properties like this actually exist in the other (Users) collection.

Updating a child object property of a parent object which needs to be populated in mongoose

I'm new to Mongoose and I'm having difficulty getting my head around accessing properties deeper in the model and updating properties on the following model structures.
Game Schema
var gameSchema = new Schema({
opponents: [{
type: Schema.Types.ObjectId,
ref: 'teams'
}],
startTime: { type: Date, default: Date.now },
endTime: Date,
pauses: [{
start: Date,
end: Date
}],
winner: {
type: Schema.Types.ObjectId,
ref: 'teams'
},
status: {type: String, default: "created"},
score: [{
"opponent1": {type: Number, default: 0},
"opponent2": {type: Number, default: 0}
}],
}, { versionKey: false });
Team Schema
var teamSchema = new Schema({
name:String,
badge:String,
goals:[{type: Date, default: Date.now}],
totalWins:Number
}, { versionKey: false });
My problem is I'm trying to add a goal to a team from a specific game.
So my end point:
POST: /api/game/GAME_ID/goal
DATA: {_id: TEAMID}
I thought the following would work:
Games.findById(GAME_ID)
.populate('opponents')
.find({'opponent._id': TEAM_ID})
.exec(function(err, team) {
// Team from game with matching ID returned
// Now push goal time into array
team.goal.push(Date.now());
});
The above does not appear to return a team. if I remove the second find the game is returned and then I have to do something horrible like this:
Games.findById(GAME_ID)
.populate('opponents')
.exec(function(err, game) {
if(game.opponents[0]._id.toString() === req.body._id) {
game.opponents[0].goals.push(Date.now());
} else if (game.opponents[1]._id.toString() === req.body._id) {
game.opponents[1].goals.push(Date.now());
} else {
// Throw error no matching team with id
}
});
game.save(function(err, game) {
//Game saved
});
this last example appears to work but when I try to add further goals pushing into the goals array it overwrites the old goal time.
So to recap.
How do I query the Games model to retrieve a child by id which has
yet to be populated?
How do I set push the goal time stamp into the goals array without
overwriting the previous one?
Is it possible to do these a bit more gracefully than the current example given above.
Games.findById(GAME_ID)
.populate(
path: 'opponents',
match: { _id: TEAM_ID}
)
.exec(function(err, team) {
// Team from game with matching ID returned
});

Categories

Resources