ValidatedMethod with server-only functions

I’m having trouble understanding how server-only imports work in client-server mixed code. I’m getting an error in one of my files which is based on this example. A ValidatedMethod executes some server-side code in a client-server file, splitting it using isSimulation.

What I don’t understand is, in that same file the server-side code should be imported somewhere like this:

import MMR from 'xxx /server/MMR.js';

Which, when you start your app should throw an error like this on the client (server is obviously fine):

Uncaught Error: Cannot find module 'xxx/server/MMR.js''.

How is this working? How do you import and use server-only code inside a mixed client-server file without using Meteor.call()?

Another Stack Overflow issue about this.

If you wrap your import statement and imported server function in !this.isSimulation, you should be good to go.

this is not defined at the root of a file, so I don’t think that works, it throws an error if you do that. Did I miss something?

If I’m not mistaken @vigorwebsolutions is refering to this:

// In a file loaded on client and server
const Meteor.users.methods.updateMMR = new ValidatedMethod({
  name: 'Meteor.users.methods.updateMMR',
  validate: null,
  run() {
    if (this.isSimulation) {
      // Simulation code for the client (optional)
    } else {
        import myServerOnlyCode from './server/api.js';
        myServerOnlyCode();
    }
  }
});
1 Like

I used require for a case like this.

Yep @orcprogramming that works, my bad, I misunderstood.

eslint actually throws a fatal error on that kind of code, wish there was a cleaner way.

I tried putting all imports inside a function like this:

const importServerMethods = () => {
  if (!this.isSimulation) {
    import { myMethod } from '/imports/api/server/methods';
    // can you return myMethod here?
    return myMethod;
  }
};

And then I try calling that in the small blocks where it is needed:

...
    } else {
        const myMethod = importServerMethods();
        myMethod();
    }
...

But that doesn’t work, and I get an error: "myMethod" is read-only.

If I recall you need to do myMethod.run() ?

Yes, actually myMethod.call();, I didn’t include it in the sample to simplify it, but the error still triggers in my real code, where methods are called correctly.

For the ‘secret’ methods I didn’t use meteor validated methods and imports, instead I used a global server object and attached JS functions to it and then only call this object form within a server block.

I also had no issues importing and using validated methods in a shared file (client and server) but all the methods has a the Meteor.isServer check for server side only code.

I had a different use case where I was trying to import npm stripe package in the test environment and the IDE (webstorm) was complaining the I can’t use import in the middle of the file, which I worked around by using require and disable the eslint


// Using require instead of import to prevent IDE
// inspection error on statement expected. Also loading
// stripe.js only on the server since
// it contains global server methods used by stripe methods being tested here.
// If the isServer check is removed,
// Meteor will attempt to load stripe.js at the client but it will not be able to find it
if (Meteor.isServer) {
  require('/imports/api/membership/server/stripe.js');

then I disabled the eslint global with /* eslint-disable global-require*/

But I’ve never encountered the"myMethod" is read-only error.I hope that helps.

Hey @florianbienefelt not sure if understand correctly what you are trying to achieve. But in case you want to define a method that contains some code that is only supposed to run server side, I would do as follows:

1- I define my method in some common folder available for both client and server:

// In a file loaded on client and server
const MyMethod = new ValidatedMethod({
  name: 'MyMethod',
  validate: null,
  run() {
    if (this.isSimulation) {
      // Simulation code for the client (optional)
    } else {
        import myServerOnlyCode from './server/api.js';
        myServerOnlyCode();
    }
  }
});

export { MyMethod };

Later on, whenever I want to use my method, I have 2 options

Old approach:

import { Meteor } from 'meteor/meteor';

// bla bla

Meteor.call('MyMethod', someParams, (err) => {
   if (err) {
     // handle error
   }
})

Second approach:

import { MyMethod } from 'some/path';

// stuff

MyMethod.call(someParams, (err) => {

})

Yes, this is essentially what I do.

  1. Create your validated method, and wrap your server-side import and invocation in a !this.isSimulation conditional
  2. Import your method on the server side in a startup or eagerly loaded block
  3. Import your method on the client where you need to use it and make your method.call

Thanks for all the useful tips!

My last question was about how to do this in a DRY way. I was wondering if there was a way to use ES6 import statements in a function and return those later. I ended up using require to do it, which works well!