ValidatedMethod with server-only functions

#1

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.

#2

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

#3

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?

#4

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
#5

I used require for a case like this.

#6

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.

#7

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

#8

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.

#9

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.

#10

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) => {

})
#11

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
#12

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!