Meteor Method Best Practices


#1

According to the Meteor tutorials, we should defined our Meteor Methods in both the client and server on account of Latency compensation.

With Meteor Methods and latency compensation, you get the best of both worlds — the security of server code and no round-trip delay.

So I placed my Meteor methods in a ‘Methods’ folder under root, for example:

 /Application_Name
   /methods
   /server
   /client

But what about code that has to be run on the server?

For example, the Meteor Email.send(options) call? This is server side only.

I created a function in my server directory /Application_Name/server, just to make a call out to Email.send(), my Meteor.Method in the shared (client/server) /methods calls the function on the server in a Meteor.isServer scoped block:

/methods/email.js

/** Client/Server side stub */
Meteor.methods({
  sendMessagesEmail: function (options) {
    check(options, {
      from: String,
      to: String,
      subject: String,
      html: String
    });

    this.unblock();
   
    if (Meteor.isServer) {
      send_email(options);
    }
  }
});

/server/email.js

send_email = function (options) {
  Email.send(options);
}

I then tried making two Meteor Method calls.

One in the /methods directory, and from there to the /server directory. This can’t be right.

For example:

/methods/email.js

/** Client/Server side stub */
Meteor.methods({
  sendMessagesEmail: function (options) {
    check(options, {
      from: String,
      to: String,
      subject: String,
      html: String
    });

    this.unblock();

    Meteor.call('send_mail', options, function (error) {
      if (error) throw new Meteor.error(error);
    });
  }
});

/server/email.js

Meteor.methods({
  send_mail: function (options) {
    check(options, {
      from: String,
      to: String,
      subject: String,
      html: String
    });

    this.unblock();

    Email.send(options)
  }
});

What is the best way to defined Meteor Methods where some code must be on the server?


#2

The purpose of the client side method implementation is only to give the user instant feedback. It’s not required.

If the implementation of the method should not be exposed to the client (for security) you can have two separate files that implement the same Meteor method. One in a client folder and one in a server folder.

Otherwise you can use this.isSimulation (http://docs.meteor.com/#/full/method_issimulation) to check if it’s the real call on the server. Or Meteor.isClient and Meteor.isServer.


#3

What about when calling insert method on a collection, collection_name.insert(…)? Should these calls be server side only? What’s best practice in this situation?

Or another way, shouldn’t Meteor Methods that perform inserts or updates be server side only? In this case wouldn’t a this.unblock() be enough in terms of latency compensation?


#4

Collection.insert already has client side method for compensation. And this.unblock() does no effect for latency. It is mostly a network issue. Meteor simply adds /<name>/insert/ method for client and server sides when you call new Collection(<name>)
It is recommended to define your own server-side methods for any DB operations and remove insecure package


#5

@mrzafod, thanks.

I have insecure and autopublish removed of course. But if I put a collection.insert call in a Meteor Method in a shared folder, it will perform the insert.

This is where I’m going…

I understand that a call to insert a document in a collection has both client and server side support, what I’m asking is, should we only have these inserts and updates in server side code only?

Just because we can, doesn’t mean we should – or am I not understanding what’s happening when I make a call to collection_name.insert({}) in a client/server directory (for example /application_name/both/collection_operations.js)?

Also, we wouldn’t want the check(…) in client side code would we?


#6

For example, I could have something like this:

// Client and Server directory

/lib/collections.js

collection_name = new Mongo.Collection('collection_name');

/lib/collection_operations.js

Meteor.methods({
  insert_document: function (document) {
     this.unblock();
     check(document, {...});
     if (Meteor.user()) {
       collection_name.insert(document);
     }
  }
});

/client/insert_collection.js

insert_documents = function (doc) {
  Meteor.call('insert_document', doc, function (error) {
    if (error) console.log(error);
  }
}

Here, when I make a call from the Client to the insert_document Meteor method, it’s actually making a call to both Client and Server versions (even though I only declared one)?

Should this be rewritten like so:

/lib/collection_operations.js

Meteor.methods({
  insert_document: function (document) {
     this.unblock();
     check(document, {...});
     insert_documents(document);
  }
});

/server/collection_helper.js

insert_documents = function (doc) {
   if (this.userId) {
     collection_name.insert(doc);
   }
}

or even just server side only:

/server/collection_operations.js

Meteor.methods({
  insert_document: function (document) {
     this.unblock();
     check(document, {...});
     if (this.userId) {
       collection_name.insert(document);
     }
  }
});

#7

I’m just trying to understand this more clearly. What happens when you write code for a Meteor Method in shared (client/server) folder.?

For example, what portion of this takes place on the client and what portion takes place on the server?

/application/lib/methods.js <-- shared directory

Meteor.methods({ <-- Meteor Methods in shared directory
  upsert_document: function (object) {
    this.unblock(); <-- server only
    check(object, {...}); <-- anywhere
    if (! this.userId) new Meteor.error('auth'); <-- this.userId: server only
    if (object.field1 === 'no-check') 
      object.field2 = null;
    return collection_name.upsert({...}); <-- anywhere, should be server only?
  }
});

In the following example, if I place Email.send() in a shared directory Meteor method, Meteor will throw that Email.send() is server only:

/application/lib/methods.js

Meteor.methods({
  send_email: function (message) {
    check(message, {..}); < -- server
    if(! Meteor.user()) new Meteor.error('auth'); <-- anywhere
    Email.send(message); <-- server
  }
});

Should I perform validations (and separation of concerns) on the server inside a shared folder Meteor Method like so?

/application/lib/methods.js

Meteor.methods({
  send_email: function (message) {
    if (Meteor.isServer) check(message, {..}); < -- server
    if (Meteor.isServer server_side_email_validation (message);
    if(! Meteor.user()) new Meteor.error('auth'); <-- anywhere
    if (Meteor.isServer) send_email (message); <-- just wrap in isServer? 
  }
});

/application/server/method-helpers.js <-- then move some server side only functionality to a server file

server_side_email_validations = function (message) {
  if (! message.field2) message.field3 = null;
  if(message.field4 === 'required') message.to = 'some@email.com';
}

send_email = function (message) {
  if (! this.userId) new Meteor.error('auth');
  Email.send(message);
};

#8

I think maybe our blog posts on the topic would help?

https://www.discovermeteor.com/blog/latency-compensation/
https://www.discovermeteor.com/blog/advanced-latency-compensation/


#9

These two posts helped out a lot! Thank you!