Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript Build a REST API With Express Completing and Testing the API Test Routes with Postman

Piyush Patel
Piyush Patel
17,253 Points

Cannot read property scope of undefined

I'm getting an error when I run this project. Here is the stack trace.

TypeError: Cannot read property 'scope' of undefined
    at EmbeddedDocument.set [as votes] (/home/pc/Desktop/stackoverflow-sample-rest/node_modules/mongoose/lib/document.js:1915:38)
    at _createConstructor (/home/pc/Desktop/stackoverflow-sample-rest/node_modules/mongoose/lib/schema/documentarray.js:81:35)
    at new DocumentArray (/home/pc/Desktop/stackoverflow-sample-rest/node_modules/mongoose/lib/schema/documentarray.js:29:26)
    at Function.Schema.interpretAsType (/home/pc/Desktop/stackoverflow-sample-rest/node_modules/mongoose/lib/schema.js:581:14)
    at Schema.path (/home/pc/Desktop/stackoverflow-sample-rest/node_modules/mongoose/lib/schema.js:526:29)
    at Schema.add (/home/pc/Desktop/stackoverflow-sample-rest/node_modules/mongoose/lib/schema.js:407:12)
    at new Schema (/home/pc/Desktop/stackoverflow-sample-rest/node_modules/mongoose/lib/schema.js:100:10)
    at Object.<anonymous> (/home/pc/Desktop/stackoverflow-sample-rest/models.js:43:22)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/home/pc/Desktop/stackoverflow-sample-rest/routes.js:5:35)

Here's my models.js file

'use strict';

var mongoose = require('mongoose');

var Schema = mongoose.Schema;

var sortAnswers = function (a,b) {
  // negative if a before b
  // 0 to leave the order
  // positive if a is after b based on votes
  if(a.votes === b.votes) {
    // If vote counts same, order by latest first.
    return b.updatedAt - a.updatedAt;
  }
  return b.votes - a.votes;
};

// Define AnswerSchema before QuestionSchema.
// Mongo has a document size limit of 16MB so if the children document is going to grow too big then it is advisable to store them in a separate collection referencing using ObjectId
var AnswerSchema = new Schema({
  text: String,
  createdAt: {type: Date, default: Date.now},
  updatedAt: {type: Date, default: Date.now},
  votes: {type: Number, default: 0}
});

// Mongoose way of defining instance methods
AnswerSchema.method("update", function (updates, callback) {
  Object.assign(this, updates, {updatedAt: new Date()});
  this.parent().save(callback);
});


AnswerSchema.method("votes", function (vote, callback) {
  if(vote === "up") {
    this.votes += 1;
  } else {
    this.votes -= 1;
  }
  this.parent().save(callback);
});

var QuestionSchema = new Schema({
  text: String,
  createdAt: {type: Date, default: Date.now},
  answers: [AnswerSchema],
});

QuestionSchema.pre("save", function (next) {
  this.answers.sort(sortAnswers);
  next();
})

var Question = mongoose.model("Question", QuestionSchema);

module.exports.Question = Question;

Here's my routes.js file

'use strict';

var express = require('express');
var router = express.Router();
var Question = require('./models').Question;

// param route handler.
router.param('qID', function (req, res, next, id) {
  Question.findById(req.params.qID, function (err, doc, next) {
    if(err) return next(err);
    if(!doc) {
      err = new Error('Not Found');
      err.status = 404;
      return next(err);
    }
    req.question = doc;
    return next();
  });
});

router.param('aID', function (req, res, next, id) {
  req.answer = req.question.answers.id(id);
  if(!req.answer) {
    err = new Error('Not Found');
    err.status = 404;
    return next(err);
  }
  return next();
})

// This will create route to /questions/
router.get('/', function (req, res, next) {
  // Return all questions.
  Question.find({})
    .sort ({createdAt: -1})
    .exec(function (err, questions) {
    if(err) return next(err);
    res.json(questions);
  });
});

// Check with postman /.POST http://localhost:3000/questions
router.post("/", function (req, res) {
  var question = new Question(req.body);
  question.save(function (err, question, next) {
    if(err) return next(err);
    res.status(201);
    res.json(question);
  });
});

// GET particular question
// check with postman /GET http://localhost:3000/questions:2
router.get('/:qID', function (req, res, next) {
  res.json(req.question);
});

// POST /questions/:id/answers
router.post("/:qID/answers", function (req, res, next) {
  req.question.answers.push(req.body);
  req.question.save(function (err, question) {
    if(err) return next(err);
    res.status(201);
    res.json(question);
  });
});

// PUT /questions/:qID/answers/:aID
router.put('/:qID/answers/:aID', function (req, res) {
  req.answer.update(req.body, function (err, result) {
    if(err) return next(err);
    res.json(result);
  });
});

// DELETE /questions/:qID/answers/:aID
router.delete('/:qID/answers/:aID', function (req, res) {
  req.answer.remove(function (err) {
    req.question.save(function (err, question) {
      if(err) return next(err);
      res.json(question);
    });
  });
});

// POST /questions/:qID/answers/:aID/vote-up
/// POST /questions/:qID/answers/:aID/vote-down
// Express can handle any number of middleware and it will execute them sequentially.
// Here, I have added second middleware to make sure correct parameter is passed to dir parameter.
router.post('/:qID/answers/:aID/vote-:dir',
function (req, res) {
  if(req.params.dir.search(/^up|down$/) === -1) {
    var err = new Error("Not Found");
    err.status = 404;
    // If error call next with parameter.
    next(err);
  } else {
    // else call next without parameter.
    req.vote = req.params.dir;
    next();
  }
},
function (req, res, next) {
  req.answer.vote(req.vote, function (err, question) {
    if(err) return next(err);
    res.json(question);
  });
});

module.exports = router;
Piyush Patel
Piyush Patel
17,253 Points

Ok, I got it. But this stack trace looks so scary and I cannot figure out just by looking at the trace. The problem was that I had defined instance method "votes" rather than "vote" which is nowhere mentioned in the stack trace. Not even line number.

I compared the code with project files and only then I found the error.

1 Answer

Steven Parker
Steven Parker
229,644 Points

The top line of the trace mentions "votes".

It says, "at EmbeddedDocument.set [as votes] ..." and points to the line where it discovered the errorr inside the document.js module of the mongoose library.

And congratulations on resolving your own issue! :+1:

Piyush Patel
Piyush Patel
17,253 Points

Hi Thanks for pointing that out. However, it doesn't mention the line number and document as models.js file.

It shows models.js file down the line with line number 43 and column 22 which wasn't relevant as that line was the definition of QuestionSchema. That's why I didn't notice that phrase "as votes".

and especially as it wasn't quoted and didn't mention correct document, I thought it might be something internal. Although, thanks for your response.

Steven Parker
Steven Parker
229,644 Points

In complex code, it's not unusual for an error in one module to be discovered in a different module. That's why the trace has more than just one line. :wink: