Meteor node-simple-schema and Meteor.users collection

If you use Meteor you have to come across the node-simple-schema formerly known as meteor-simple-schema. This is definitely a great package which eases data validation a lot by allowing you to define some schema for your MongoDB (which is schema-less DB). Whereas creating a schema for your custom data type is easy (just follow the node-simple-schema docs), integration with external packages may require some knowledge. As an example I will describe the process of writing a basic schema for the Accounts Meteor functionality and the accounts-password package.

Educated guesses

There is no official documentation about Meteor Accounts and node-simple-schema integration, so (probably) the only way is to make some educated guesses about the internal structure of Meteor.users collection. The first place to look at is http://docs.meteor.com/api/accounts.html#Meteor-users (here is the relevant code):

{
  _id: 'QwkSmTCZiw5KDx3L6',  // Meteor.userId()
  username: 'cool_kid_13', // Unique name
  emails: [
    // Each email address can only belong to one user.
    { address: '[email protected]', verified: true },
    { address: '[email protected]', verified: false }
  ],
  createdAt: new Date('Wed Aug 21 2013 15:16:52 GMT-0700 (PDT)'),
  profile: {
    // The profile is writable by the user by default.
    name: 'Joe Schmoe'
  },
  services: {
    facebook: {
      id: '709050', // Facebook ID
      accessToken: 'AAACCgdX7G2...AbV9AZDZD'
    },
    resume: {
      loginTokens: [
        { token: '97e8c205-c7e4-47c9-9bea-8e2ccc0694cd',
          when: 1349761684048 }
      ]
    }
  }
}

After reading this short example you may have a general idea about the structure of data stored inside users collection. Based on that knowledge let's write some SimpleSchema related code:

import SimpleSchema from 'simpl-schema';
import { Tracker } from 'meteor/tracker';

const User = new SimpleSchema({
  username: { type: String, label: "Username" },
  services: { type: Object, blackbox: true },
  emails: { type: Array, optional: true },
  createdAt: Date,
  profile: { type: Object, blackbox: true, optional: true },
}, { tracker: Tracker });

export { User };

This seems to be the minimal working schema example for the Meteor users collection. As far as I know the _id field doesn't require to be defined inside SimpleSchema.

Adding accounts-password validation

If you are using accounts-password package (like I do) you may also specify the exact services.password keys for password type validation. Here is how it may look:

import SimpleSchema from 'simpl-schema';
import { Tracker } from 'meteor/tracker';

const User = new SimpleSchema({
  username: { type: String, label: "Username" },
  services: Object,
  'services.password': Object,
  'services.password.bcrypt': { type: String, label: "Password" },
  'services.resume': { type: Object, blackbox: true, optional: true },
  emails: { type: Array, optional: true },
  createdAt: Date,
  profile: { type: Object, blackbox: true, optional: true },
}, { tracker: Tracker });

export { User };

The above example assumes that you are using only password package for other packages (e.g. accounts-facebook) you will have to specify correct services.[package-name] keys (e.g. services.facebook.id, services.facebook.accessToken). Another thing worth noting is the services.resume key which is required in order to login user properly, because the data about existing sessions is stored inside resume object.

In order to check if your schema works correctly you may execute a command like:

Accounts.createUser({ password: 'some_password', username: 'test123' })

From the Meteor interactive shell, you can open it when the meteor server is running with meteor shell command.

Conclusion

It's fairly easy to validate existing/external package data with node-simple-schema if only you know the internal structure of the validated data. If you are adding the validation to the existing app you may find it useful to check the database first with meteor mongo and db.[your_collection].find() to ensure that the collection structure matches your expectations.