How to use MongoDB's new Schema Validation feature in Express.js?

How do you implement MongoDB’s Schema Validation feature in Express server? I’m working on a simple todo app and decided to use the native MongoClient instead of mongoose but i still do want to have a schema.

Base on MongoDB’s docs here: https://docs.mongodb.com/manual/core/schema-validation/#schema-validation You can either create a collection with Schema or update an existing collection without schema to have one. The commands are run in the mongo shell, but how do you implement it in express?

So far what i did is make a function that returns the Schema Validation commands and call it on every routes but i get an error saying db.runCommand is not a function.

Here’s my Express server:

const express = require("express");
const MongoClient = require("mongodb").MongoClient;
const ObjectID = require("mongodb").ObjectID;
const dotenv = require('dotenv').config();
const todoRoutes = express.Router();
const cors = require("cors");
const path = require("path");
const port = process.env.PORT || 4000;
let db;

const app = express();
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

MongoClient.connect(process.env.MONGO_URI,{useNewUrlParser: true},(err,database)=>{
    if(err){
        throw err;
        console.log(`Unable to connect to the databse: ${err}`);
    } else {
        db =  database;
        console.log('Connected to the database');
    }
});

/* Schema Validation Function */
const runDbSchemaValidation = function(){
    return db.runCommand( {
        collMod: "todos",
        validator: { $jsonSchema: {
           bsonType: "object",
           required: [ "description", "responsible","priority", "completed" ],
           properties: {
              description: {
                 bsonType: "string",
                 description: "must be a string and is required"
              },
              responsibility: {
                 bsonType: "string",
                 description: "must be a string and is required"
              },
              priority: {
                bsonType: "string",
                description: "must be a string and is required"
             },
             completed: {
                bsonType: "bool",
                description: "must be a either true or false and is required"
             }
           }
        } },
        validationLevel: "strict"
     } );
}

/* Get list of Todos */
todoRoutes.route('/').get((req,res)=>{
    runDbSchemaValidation();
    db.collection("todos").find({}).toArray((err,docs)=>{
        if(err)
            console.log(err);
        else {
            console.log(docs);
            res.json(docs);
        }
    });
});

/* Get Single Todo */
todoRoutes.route('/:id').get((req,res)=>{
    let todoID = req.params.id;
    runDbSchemaValidation();
    db.collection("todos").findOne({_id: ObjectID(todoID)}, (err,docs)=>{
        if(err)
            console.log(err);
        else {
            console.log(docs);
            res.json(docs);
        }
    });
});

/* Create Todo */
todoRoutes.route('/create').post((req,res,next)=>{
    const userInput = req.body;
    runDbSchemaValidation();
    db.collection("todos").insertOne({description:userInput.description,responsible:userInput.responsible,priority:userInput.priority,completed:false},(err,docs)=>{
        if(err)
            console.log(err);
        else{
            res.json(docs);
        }
    });
});

/* Edit todo */
todoRoutes.route('/edit/:id').get((req,res,next)=>{
    let todoID = req.params.id;
    runDbSchemaValidation();
    db.collection("todos").findOne({_id: ObjectID(todoID)},(err,docs)=>{
        if(err)
            console.log(err);
        else {
            console.log(docs);
            res.json(docs);
        }
    });
});

todoRoutes.route('/edit/:id').put((req,res,next)=>{
    const todoID = req.params.id;
    const userInput = req.body;
    runDbSchemaValidation();
    db.collection("todos").updateOne({_id: ObjectID(todoID)},{ $set:{ description: userInput.description, responsible: userInput.responsible, priority: userInput.priority, completed: userInput.completed }},{returnNewDocument:true},(err,docs)=>{
        if(err)
            console.log(err);
        else
            res.json(docs);
        console.log(db.getPrimaryKey(todoID));
    });
});

/* Delete todo */
todoRoutes.route('/:id').delete((req,res,next)=>{
    const todoID = req.params.id;
    runDbSchemaValidation();
    db.collection("todos").deleteOne({_id: ObjectID(todoID)},(err,docs)=>{
        if(err)
            console.log(err)
        else{
            res.json(docs);
        }
    });
});

app.use('/todos',todoRoutes);

app.listen(port,()=>{
    console.log(`Server listening to port ${port}`);
});

I also tried it on the initial client connection but i got the same error:

I found this MongoDB doc which shows how to create a schema for a collection. You are doing something a bit different (but may be the same). I have not tried to test your $jsonSchema with what the documentation shows, but it might be worth trying what is show to see.

If you notice, you never see either of the following messages:

  • “Unable to connect to the databse:”
    OR
  • “Connected to the database”

That is because MongoClient.connect is asynchronous which means it does not stop other code from running, Your code errors out before these other console.log statements even have a chance to execute. Are you sure db has been assigned a value before runDbSchemaValidation executes? Do you get undefined if you add the following console.log call runDebSchemaValidation?

todoRoutes.route('/').get((req,res)=>{
    console.log(db);  // what you see here?
    runDbSchemaValidation();
    db.collection("todos").find({}).toArray((err,docs)=>{

@RandellDawson Hey thanks for the reply, basically the link you shared is the same as mine lol. Just as what i state above there’s two ways to add schema or validation rules to the collection.

  1. db.createCollection() - To specify validation rules when creating a new collection. Here’s the mondodb example i copied on the documentation:
db.createCollection("students", {
   validator: {
      $jsonSchema: {
         bsonType: "object",
         required: [ "name", "year", "major", "gpa", "address.city", "address.street" ],
         properties: {
            name: {
               bsonType: "string",
               description: "must be a string and is required"
            },
            gender: {
               bsonType: "string",
               description: "must be a string and is not required"
            },
            year: {
               bsonType: "int",
               minimum: 2017,
               maximum: 3017,
               exclusiveMaximum: false,
               description: "must be an integer in [ 2017, 3017 ] and is required"
            },
            major: {
               enum: [ "Math", "English", "Computer Science", "History", null ],
               description: "can only be one of the enum values and is required"
            },
            gpa: {
               bsonType: [ "double" ],
               minimum: 0,
               description: "must be a double and is required"
            },
            "address.city" : {
               bsonType: "string",
               description: "must be a string and is required"
            },
            "address.street" : {
               bsonType: "string",
               description: "must be a string and is required"
            }
         }
      }
   }
})

I’ve seen a youtube tutorial where it is run on the cmd mongo shell. However the tutorial is incomplete, there is no update part which suppose to look like the code below:

  1. callMod - To add document validation to an existing collection. Here’s the mongodb example:
db.runCommand( {
   collMod: "contacts",
   validator: { $jsonSchema: {
      bsonType: "object",
      required: [ "phone", "name" ],
      properties: {
         phone: {
            bsonType: "string",
            description: "must be a string and is required"
         },
         name: {
            bsonType: "string",
            description: "must be a string and is required"
         }
      }
   } },
   validationLevel: "moderate"
} )

So what i’m trying to follow is the 2nd option since i already have an existing collection.
Anyway i tried to console log the db and comment out the runDbSchemaValidation, i was able to connect but i don’t see the db in the console, cause i was using the old way of connecting with the MongoClient, the previous versions is this:

MongoClient.connect("mongodb://localhost:27017/integration_test", function(err, database) {
  if(err) throw err;
  db = database;
});

As you can see the callback function returns the database, while the new one doesn’t so i have to supply the database name:

MongoClient.connect("mongodb://localhost:27017/integration_test",(err,client)=>{
    if(err) throw err;
    db =  client.db(dbName);
});

I still get the same error though, "db.runCommand is not a function"

Do you have your project on GitHub? I can clone it and test a few things and see if I can give you a solution or at least some better insight in what might be going on.

1 Like

I still get the same error though, "db.runCommand is not a function"

Uhm, probably it’s a bit naive, but…shouldn’t the command be: db.command? ( Link to Mongo driver docs )

That said i never tried to add a schema to an existing collection directly, looks interesting!

1 Like

@RandellDawson Sure here’s my repo: https://github.com/mav1283/mernstacktodo.git It’s a MERN stack up and unfinished yet. On the client side (react) i just fetch data from the default express port. I haven’t run the build on react yet. The database is simple, all strings and a boolean for the last field.

@Layer Yes but it’s db.runCommand in the Schema Validation documentation: https://docs.mongodb.com/manual/core/schema-validation/#schema-validation

As far as i understand those commands (the ones presented in the mongodb doc) are issued against mongo with the shell, you are trying to connect via driver^^

1 Like