Search code examples
mongodbexpresspermalinks

Creating permalinks with Express/MongoDB


Suppose I have the following schemas for a LMS app:

const CourseSchema = new mongoose.Schema({
    name: { type: String, required: true },
    code: { type: String, required: true, unique: 1, uppercase: 1 }, // ex. CSCA48
    quizzes: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Quiz' }]
});

const QuizSchema = new mongoose.Schema({
    name: { type: String, required: true }, // ex. 1a
    questions: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Question' }]
});

const QuestionSchema = new mongoose.Schema({
    number: { type: String, required: true }, // ex. 4
    question: { type: String, required: true },
    type: { type: String },
    choices: [String],
    answers: [String]
});

I built (or tried to build) the app following RESTful API guidelines. For example,

GET /courses/[ObjectID]
GET /courses/[ObjectID]/quizzes
GET /courses/[ObjectID]/quizzes/[ObjectID]
GET /courses/[ObjectID]/quizzes/[ObjectID]/questions

The reason why I used ObjectID was because they are unique and makes it easy to retrieve objects -- particularly with app.param(). For example,

router.param('courseID', controllers.Course.getCourseByParam);
router.param('quizID', controllers.Quiz.getQuizByParam);
router.param('questionID', controllers.Question.getQuestionByParam);

router.get('/courses', controllers.Course.getCourses);
router.get('/courses/:courseID', controllers.Course.getCourse);
router.get('/courses/:courseID/quizzes', controllers.Quiz.getQuizzes);
router.get('/courses/:courseID/quizzes/:quizID/questions', controllers.Question.getQuestions);

But now, I am wondering if there is a way to not use ObjectID in order make the URLs more friendly/readable?

GET /courses/[code]/quizzes/[name]/questions/[number] 

ex. /courses/CSCA48/quizzes/1a/questions/4 

My biggest concern with this is that, although course code is unique, quiz name and question number are not. For example, /courses/CSCA48/quizzes/1b/questions/4 would be a different question for a different quiz. So, unless I am mistaken, it is not really possible to use app.param(). Therefore I would need to somehow ensure that the quiz belongs to the matched course and then question belongs to the matched quiz.

I am not really looking for code, just a general idea of how I can approach this (or not).


Solution

  • Since the combination [code]-[name]-[number] is unique (and [code] too, which is important), you should be able to run a query that would bring up the correct question:

    Course.findOne({ code : req.params.code })
          .populate({
            path     : 'quizzes',
            match    : { name : req.params.name },
            populate : {
              path  : 'questions',
              match : { number : req.params.number }
            }
          })
    

    Alternatively, you could add two fields to QuestionSchema to reflect both the course and the quiz to which the question belongs, in which case you'd only have to run a single query to find the correct question document (population requires 3 queries).