33

Using Meteor, I'm attempting to understand when to use server-side Meteor.methods() while still retaining instant UI updates.

From Andrew Scala's introductory tutorial, he claims that Meteor.methods() should be used when you want to update and modify your database documents:

The idea is that you define all the functions on the server that do dangerous stuff like modify and update data, and then let the client call those functions and get return values like regular functions. The client never sees the implementation and doesn’t personally modify the data. The server does all the work.

And following this advice, I implemented this in my code:

Server-side:

Meteor.methods({

  addMovie: function(data) {
    var movie = Movies.insert({name: data});
    return movie;
  },

  ...

Client-side:

Template.movies.events = ({

  'click #add-movie': function(e) {

    var name = document.getElementById('movie-name').value;
    Meteor.call('addMovie', name);

    return false;

  }, 

  ...

This works, but it's slow. The UI doesn't update instantly like it would if you called Movies.insert() on the client-side. The docs indicate that, to rectify the problem, you can create stubs on the client-side:

Calling methods on the client defines stub functions associated with server methods of the same name. You don't have to define a stub for your method if you don't want to. In that case, method calls are just like remote procedure calls in other systems, and you'll have to wait for the results from the server.

But what should these stubs look like? Should it basically look the same as the server-side method? If so, what's the point? I'm looking for a more comprehensive explanation of the use and purpose of Meteor.methods(), the point/use of stubs, and their implementation.

EDIT: David Greenspan has helped clarify the use of Meteor.methods() and stubs on meteor-talk.

Dan Dascalescu
  • 143,271
  • 52
  • 317
  • 404
bento
  • 4,846
  • 8
  • 41
  • 59

4 Answers4

19

here's another example.

say you're writing a bingo game and you click the button to call "house!".. in the click event you might call a Method, e.g.

Method.call("callHouse");

this will invoke the server method:

// on the server
Meteor.methods({
  callHouse: function () {
    if (currentGame.isInProgress) {
      currentGame.winner = this.userId;
      currentGame.end();
    }
  }
});

if you are the first to call "house", the method will mark you as the winner.. however, let's pretend the method is extremely slow and your client app is waiting.. you're 99% sure the server will confirm you are the winner - you just want to update the user's screen without the wait.. in this case implement a client-side stub:

// on the client
Meteor.methods({
  callHouse: function () {
    currentGame.winner = Meteor.userId();
    // add any other side-effects you expect to occur here
  }
});

when the server result returns, if the data returned is different to what you set in the stub, it will correct it and refresh the screen accordingly.

Lloyd
  • 8,204
  • 2
  • 38
  • 53
  • Why did you use `this.userId` on the server and `Meteor.userId()` on the client? – CaptSaltyJack Feb 01 '15 at 02:41
  • i can't remember, i binned Meteor years ago – Lloyd Feb 02 '15 at 13:52
  • 4
    That's too bad. 1.0 rocks! – CaptSaltyJack Feb 02 '15 at 17:27
  • From my experience, use `this.userId` on server-only method calls and publish functions to avoid errors. On the other hand, use `Meteor.userId()` whenever the client is involved (anywhere but the publish functions). See http://stackoverflow.com/questions/17823268/in-meteor-js-why-is-this-userid-undefined – FullStack May 05 '15 at 13:53
  • Does this mean while getting the answer from the local cache, a request sent to server's method in the background? – securecurve Oct 26 '15 at 14:56
  • OH: from Wikipedia: "There are no formal rules as to what can be shouted, but most players will shout "Yes" or "Bingo". Some players may also choose to shout "Line" or "House" depending on the prize, whilst others choose to shout "house" for any win (including a line or two lines)..." https://en.wikipedia.org/wiki/Bingo_%28United_Kingdom%29 – Wray Bowling Nov 25 '15 at 20:14
12

In short :

Define some methods (Meteor.methods) in the files pushed to the sever that will do actual work on the server, define some methods (Meteor.methods) in the files pushed to the client to get an 'instant' behavior on the client (such as a loading indicator) until the server pushes the resulting changes back to the client

Here's David's original post :

Hi Ben,

In principle, a method can perform completely different actions on the client and server, for example showing a loading indicator on the client and talking to a remote API on the server. Calls to Meteor.methods on the client define the client behavior, and calls to Meteor.methods on the server define the server behavior.

For methods that operate on the database, often the same implementation will do for both. The client version affects the client-side database (the browser-side "cache" of documents subscribed to) and the server-side version affects the real database. When the client hears back, it "snaps" to the result of the server-side mutations; the client-side database mutations are discarded (or undone, depending on how you think about it). If a client-side method calls other methods, these secondary calls are not remoted to the server. The server-side version will either call the same methods on the server, or not, as it sees fit.

So any client-side method impl you provide is just a "simulation" and doesn't have to be accurate (it may not be able to be). The hope is that you typically get the simulation impl for free because it's the same as the server impl!

Does this answer your question?

-- David

jay
  • 507
  • 4
  • 16
  • Thanks Jay. I had been meaning to write a followup answer to this, once I had more experience dealing with stubs. I may eventually do this, but I'll accept your answer for now. – bento Oct 25 '12 at 22:57
  • @jay, Does using one method for both client and server (outside client and server folders) will have the same effects? – securecurve Oct 26 '15 at 14:53
  • 2
    Hi @securecurve The stub is meant for the client instant update only, and its result will be overwritten once the server performed the operation and returned. For example, if you have an upVote method, on your client, you can just add 1 to the votes, and update the UI, but at the same time, Meteor will call the same method on the server ( which may or may not return the same result depending if other people voted for example ) – jay Oct 26 '15 at 18:11
  • @jay, I like the example you gave :) ... Appreciated, many thanks! – securecurve Oct 26 '15 at 18:23
3

If you define a method on a file shared by client/server like /collections wouldn't it be accessible to both and automatically stub?

So:

/collections/houses.js

Meteor.methods({
  callHouse: function () {
    if (currentGame.isInProgress) {
      currentGame.winner = this.userId;
      currentGame.end();
    }
  }
});

This will be available to both the client and server. If it doesn't pass, the server will automatically reject the client update/revert it.

Daniel Fischer
  • 3,042
  • 1
  • 26
  • 49
  • Is it safe? Because the user has access to the code – Bruno Lemos Nov 13 '14 at 20:52
  • Yes, you use meteor security rules for that. – Daniel Fischer Nov 19 '14 at 22:42
  • @BrunoLemos I had that concern in the beginning too, but part of the design principles behind Meteor is that it is database everywhere, so you can use the same methods to access your database from the client and server. It doesn't matter that the client has access to the code, because no matter what is on the client-side, the server-side will always validate. – d4nyll Jan 14 '15 at 16:23
  • @DanielFischer, Does mean the client will get the answer from the local cache first, while sending a request to the server, and when the server responds it patches the client's cache, or what happens exactly? – securecurve Oct 26 '15 at 14:51
3

As said Daniel you can define a method on a file which not in client or server directories and available on both sides. You can also use isSimulation boolean value to make additional checking. For example it may looks something like this:

Meteor.methods({
  addMovie: function (movieData) {
    if (!this.isSimulation) {
      check(movieData, someAdditionaCheckinFunc);
    }
    Movies.insert(movieData);
  }
})

Thus code in the conditional branch will be executed on server only.

Yaroslav Melnichuk
  • 820
  • 1
  • 11
  • 15