0

I'm very new to JavaScript and Mongoose. I'm building a small project using express, mongoose and node.js. I have a mongoose model - Client that has an Array of Transactions

    var Client = mongoose.model('Client', {
 name: {
   type: String,
   required: true,
   minlength: 1
 },
 email: {
   type: String
 },
 phone: {
   type: Number
 },
 createdAt: {
   type: Number,
   default: null
 },
 transactions: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Transaction' }],
 _creator: {
   type: mongoose.Schema.Types.ObjectId,
   required: true
 }
});

module.exports = {Client};

This is Transaction model:

var Client = require('./client');

var Transaction = mongoose.model('Transaction',{
  _creator : { type: mongoose.Schema.Types.ObjectId, ref: 'Client' },
  amount : {type: Number, min: 0},
  date : {type: Number,default: null},
  total: {type: Number,default: null}
});

module.exports = {Transaction};

When I POST a new Transaction it goes through and saves in db:

 app.post('/clients/:id/transactions', authenticate, (req, res) => {
  var id = req.params.id;
  var transaction = new Transaction({
    amount: req.body.amount,
    date: new Date().getTime(),
    total: req.body.total,
    _creator: req.params.id
  })
  if (!ObjectID.isValid(id)) {
  return res.status(404).send();
  }

  transaction.save().then((doc) => {

    Client.findOneAndUpdate({
      _id: id,
      _creator: req.user._id,
      transactions: req.body.transaction
    });

    res.send(doc);
  }, (e) => {
    res.status(400).send(e);
  });

});

I am also able to GET all the transactions associated with the client:

  app.get('/clients/:id/transactions', authenticate, (req, res) => {
  var id = req.params.id;
  if (!ObjectID.isValid(id)) {
  return res.status(404).send();
 }
 Transaction.find({
   _creator: id
 }).then((transactions) => {
  res.send({transactions});
 }).catch((e) => {
  res.status(400).send();
 });
});

But when I make a GET call to '/clients' - Array of Transactions is empty:

    {
  "clients": [
    {
      "_id": "1095d6de3867001108b803",
      "name": "Peter",
      "email": "[email protected]",
      "phone": 1232321,
      "_creator": "5321df6d57868ec7001108b801",
      "__v": 0,
      "transactions": [],
      "createdAt": null
    } ]
}

And this is the GET call to /clients

    app.get('/clients', authenticate,  (req, res) => {
  Client.find({
    _creator: req.user._id,
  })
  .populate('transactions.transaction')
  .then((clients) => {
    res.send({clients});
  }, (e) => {
    res.status(400).send(e);
    console.log('Unable to get clients', e);
  })
});

I know that I'm likely doing something completely wrong but I don't know where I need to look for my mistake. Please help!

2 Answers 2

1

I would check if the client exist before adding a transaction. A transaction needs a client first.

Forewarn, I'm not a fan of then and catch so this answer does not use it. I normally use async.js when dealing with multiple asynchronous operations.

Anyways, I would do it like

app.post('/clients/:id/transactions', authenticate, (req, res) => {


    Client.findOne({ _id: req.params.id }, (err, client) => {
        if (err)
            return res.status(400).send(err); 
        if (!client)
            return res.status(400).send(new Error('No client'));


        Transaction.create({
            amount: req.body.amount,
            date: new Date(), // I don't think you need .getTime()
            total: req.body.total,
            _creator: client._id
        }, (err, transaction) => {
            if (err)
                return res.status(400).send(err);


            client.transactions.push(transaction._id);
            client.save(err => {
                if (err)
                    return res.status(400).send(err);

                res.json(transaction);
            });
        });
    });


});

Good idea to also turn on debugging mode to see your queries: mongoose.set('debug', true).

You might also find using timestamps option for Transaction schema more useful than explicitly using date field

To get clients with their transactions

app.get('/clients', authenticate,  (req, res) => {
    Client.find({ _creator: req.user._id }).populate('transactions').exec((err, clients) => {
        if (err)
            return res.status(400).send(err);
        res.json(clients);
    });
});
Sign up to request clarification or add additional context in comments.

4 Comments

Transactions actually are saved. But they don't show when I hit GET /clients. The array is empty. Although when I call GET clients/{id}/transactions - they are there
@MikeB Check edit. You should still rethink your order of evaluation as (I believe) you don't want to end up saving transactions for clients that don't exist.
It works even without populate(). I just pasted your app.post('/clients/:id/transactions' and now I can see transactions id in my array! Thank you very much!
Weird. I don't see how that would be possible without the populate. Oh well. Kindly mark it answered then.
1

so first of all i don't exactly know what _creator key in Client model representing, it's probably user identifier who has some clients, but if I'm wrong please correct me.

Honestly I don't know why you are making two way document connection, (keeping client in transactions, and also keeping transactions in clients) in my opinion first option is better for mongodb and using that you can easily get transaction's list with find, or mongodb aggregation, but you can't get data using populate.

In second option you need to remember that one document could have maximum 16MB. And also keeping thousands of transactions in one array is not well for performance. Think about example that you have 5000 transaction and you want to show list with pagination (50 records per page), with array option you have to get whole document, and splice array to 50 records. In first option you could use mongodb skip and limit. Please think about it.

Returning to question, mistake you are doing is here: transaction.save().then((doc) => {

Client.findOneAndUpdate({
  _id: id,
  _creator: req.user._id,
  transactions: req.body.transaction
});

res.send(doc);

Here you don't exactly say how this document should have to updated about.

Mongoose in method findOneAndUpdate using mongodb findAndModify method. But params are used from update. https://docs.mongodb.com/manual/reference/method/db.collection.update/

And also documentation says that you what params like: Query#findOneAndUpdate([query], [doc], [options], [options.passRawResult], [options.strict], [callback])

So first query param is mongo query to find one document in database, next param is object with updating query, and after that you could send some additional options in third param. So your code should looks like this:

transaction.save().then((doc) => {

Client.findOneAndUpdate({
  _id: id,
  _creator: req.user._id,
}, {
    $addToSet:  {
        transactions: doc._id,
    }
});

res.send(doc);

You could use addToSet or push both are putting element into array, but addToSet avoiding duplicates in array. And as you se we push new transaction identifier into this array. And after all you only populate transaction key.

I hope I helped. If you have any questions please ask.

11 Comments

Thanks for you answer. I corrected my code with your suggestions but it still doesn't work..
Could you check find action without populate works? Are there ids in array?
Yes I removed populate(). The array is still empty. The call to clients/{client_id}/transactions displays all the transactions for this user. But call to GET ./clients returns clients with empty array
Ok, so the problem is in update action. Did you post some transactions after my suggestions?
You see this two queries you make Transaction.find, and Client.find are independent, So if your client transactions array is empty but you have got some transactions with _creator as same client, this things have no relation. You want to do populate action on array, so mongoose can't take your transactions document. Because populate action depends on mongo identifiers in array.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.