{"id":14954676,"url":"https://github.com/meteor/validated-method","last_synced_at":"2025-10-19T22:30:15.073Z","repository":{"id":65983033,"uuid":"46298872","full_name":"meteor/validated-method","owner":"meteor","description":"Meteor methods with better scoping, argument checking, and good defaults.","archived":false,"fork":false,"pushed_at":"2023-03-15T14:44:37.000Z","size":67,"stargazers_count":194,"open_issues_count":25,"forks_count":30,"subscribers_count":20,"default_branch":"master","last_synced_at":"2025-01-28T23:44:15.777Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://atmospherejs.com/mdg/validated-method","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/meteor.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2015-11-16T20:00:17.000Z","updated_at":"2024-07-21T18:08:07.000Z","dependencies_parsed_at":"2023-04-11T10:31:10.179Z","dependency_job_id":null,"html_url":"https://github.com/meteor/validated-method","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meteor%2Fvalidated-method","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meteor%2Fvalidated-method/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meteor%2Fvalidated-method/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meteor%2Fvalidated-method/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/meteor","download_url":"https://codeload.github.com/meteor/validated-method/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":237221176,"owners_count":19274447,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-09-24T13:04:48.012Z","updated_at":"2025-10-19T22:30:14.476Z","avatar_url":"https://github.com/meteor.png","language":"JavaScript","funding_links":[],"categories":["Packages"],"sub_categories":[],"readme":"# mdg:validated-method\n\n### Define Meteor methods in a structured way, with mixins\n\n```js\n// Method definition\nconst method = new ValidatedMethod({\n  name, // DDP method name\n  mixins, // Method extensions\n  validate, // argument validation\n  applyOptions, // options passed to Meteor.apply\n  run // Method body\n});\n\n// Method call\nmethod.call({ arg1, arg2 });\n\n// Method callAsync added in 1.3.0\nmethod.callAsync({ arg1, arg2 }); \n//      ˆˆˆˆˆ not callback based, returns a Promise.\n\n```\n\nThis is a simple wrapper package for `Meteor.methods`. The need for such a package came\nwhen the Meteor Guide was being written and we realized there was a lot of best-practices\nboilerplate around methods that could be easily abstracted away.\n\n\u003e Note: the code samples in this README use the Meteor 1.3 import/export syntax, but this package works great in Meteor 1.2 as well. In that case, we recommend attaching your ValidatedMethod objects to the relevant collection, like `Lists.methods.insert = new ValidatedMethod(...)`.\n\n### Benefits of ValidatedMethod\n\n1. Have an object that represents your method. Refer to it through JavaScript scope rather than\nby a magic string name.\n1. Built-in validation of arguments through `aldeed:simple-schema`, or roll your own argument validation.\n1. Easily call your method from tests or server-side code, passing in any user ID you want. No need for [two-tiered methods](https://www.discovermeteor.com/blog/meteor-pattern-two-tiered-methods/) anymore!\n1. [Throw errors from the client-side method simulation](#validation-and-throwstubexceptions) to prevent execution of the server-side\nmethod - this means you can do complex client-side validation in the body on the client, and not\nwaste server-side resources.\n1. Get the return value of the stub by default, to take advantage of [consistent ID generation](#id-generation-and-returnstubvalue). This\nway you can implement a custom insert method with optimistic UI.\n1. Install Method extensions via mixins.\n\nSee extensive code samples in the [Todos example app](https://github.com/meteor/todos).\n\n### Defining a method\n\n#### Using SimpleSchema\n\nLet's examine a method from the new [Todos example app](https://github.com/meteor/todos/blob/b890fc2ac8846051031370035421893fa4145b86/packages/lists/methods.js#L17) which makes a list private and takes the `listId` as an argument. The method also does permissions checks based on the currently logged-in user. Note this code uses new [ES2015 JavaScript syntax features](http://info.meteor.com/blog/es2015-get-started).\n\n```js\n// Export your method from this module\nexport const makePrivate = new ValidatedMethod({\n  // The name of the method, sent over the wire. Same as the key provided\n  // when calling Meteor.methods\n  name: 'Lists.methods.makePrivate',\n\n  // Validation function for the arguments. Only keyword arguments are accepted,\n  // so the arguments are an object rather than an array. The SimpleSchema validator\n  // throws a ValidationError from the mdg:validation-error package if the args don't\n  // match the schema\n  validate: new SimpleSchema({\n    listId: { type: String }\n  }).validator(),\n\n  // This is optional, but you can use this to pass options into Meteor.apply every\n  // time this method is called.  This can be used, for instance, to ask meteor not\n  // to retry this method if it fails.\n  applyOptions: {\n    noRetry: true,\n  },\n\n  // This is the body of the method. Use ES2015 object destructuring to get\n  // the keyword arguments\n  run({ listId }) {\n    // `this` is the same method invocation object you normally get inside\n    // Meteor.methods\n    if (!this.userId) {\n      // Throw errors with a specific error code\n      throw new Meteor.Error('Lists.methods.makePrivate.notLoggedIn',\n        'Must be logged in to make private lists.');\n    }\n\n    const list = Lists.findOne(listId);\n\n    if (list.isLastPublicList()) {\n      throw new Meteor.Error('Lists.methods.makePrivate.lastPublicList',\n        'Cannot make the last public list private.');\n    }\n\n    Lists.update(listId, {\n      $set: { userId: this.userId }\n    });\n\n    Lists.userIdDenormalizer.set(listId, this.userId);\n  }\n});\n```\n\nThe `validator` function called in the example requires SimpleSchema version 1.4+.\n\nBe aware that by default the `validator` function does not [clean](https://github.com/aldeed/meteor-simple-schema#cleaning-data)\nthe method parameters before checking them. This behavior differs from that of\n`aldeed:collection2`, which always cleans the input data before inserts, updates,\nor upserts.\n\nIf you want the validator to clean its inputs before checking, make sure to pass\nthe `{ clean: true }` option to the `validator` function:\n\n```js\n  validate: new SimpleSchema({\n    listId: { type: String }\n  }).validator({ clean: true }),\n```\n\n#### Using your own argument validation function\n\nIf `aldeed:simple-schema` doesn't work for your validation needs, just define a custom `validate`\nmethod that throws a [`ValidationError`](https://github.com/meteor/validation-error) instead:\n\n```js\nconst method = new ValidatedMethod({\n  name: 'methodName',\n\n  validate({ myArgument }) {\n    const errors = [];\n\n    if (myArgument % 2 !== 0) {\n      errors.push({\n        name: 'myArgument',\n        type: 'not-even',\n        details: {\n          value: myArgument\n        }\n      });\n    }\n\n    if (errors.length) {\n      throw new ValidationError(errors);\n    }\n  },\n\n  // ...\n});\n```\n\n#### Using `check` to validate arguments\n\nYou can use `check` in your validate function if you don't want to pass `ValidationError` objects to the client, like so:\n\n```js\nconst method = new ValidatedMethod({\n  name: 'methodName',\n\n  validate(args) {\n    check(args, {\n      myArgument: String\n    });\n  },\n\n  // ...\n});\n```\n\n#### Skipping argument validation\n\nIf your method does not need argument validation, perhaps because it does not take any arguments, you can use `validate: null` to skip argument validation.\n\n#### Defining a method on a non-default connection\n\nYou can define a method on a non-default DDP connection by passing an extra `connection` option to the constructor.\n\n#### Options to Meteor.apply\n\nThe validated method, when called, executes itself via `Meteor.apply`.  The `apply` method also takes a few [options](http://docs.meteor.com/#/full/meteor_apply) which can be used to alter the way Meteor handles the method. If you want to use those options you can supply them to the validated method when it is created, using the `applyOptions` member.  Pass it an object that will be used with `Meteor.apply`.\n\nBy default, `ValidatedMethod` uses the following options:\n\n```js\n{\n  // Make it possible to get the ID of an inserted item\n  returnStubValue: true,\n\n  // Don't call the server method if the client stub throws an error, so that we don't end\n  // up doing validations twice\n  throwStubExceptions: true,\n};\n```\n\nOther options you might be interested in passing are:\n\n- `noRetry: true` This will stop the method from retrying if your client disconnects and reconnects.\n- `onResultReceived: (result) =\u003e { ... }` A callback to call when the return value is sent from the server. This actually happens before the regular Method callback fires, you can read more [details about the Method lifecycle in the Meteor Guide](http://guide.meteor.com/methods.html#call-lifecycle).\n\n#### Secret server code\n\nIf you want to keep some of your method code secret on the server, check out [Served Files](http://guide.meteor.com/security.html#served-files) from the Meteor Guide.\n\n### Using a ValidatedMethod\n\n#### method#call(args: Object)\n\nCall a method like so:\n\n```js\nimport {\n  makePrivate,\n} from '/imports/api/lists/methods';\n\nmakePrivate.call({\n  listId: list._id\n}, (err, res) =\u003e {\n  if (err) {\n    handleError(err.error);\n  }\n\n  doSomethingWithResult(res);\n});\n```\n\nThe return value of the server-side method is available as the second argument of the method\ncallback.\n\n#### method#\\_execute(context: Object, args: Object)\n\nCall this from your test code to simulate calling a method on behalf of a particular user:\n\n```js\nit('only makes the list public if you made it private', () =\u003e {\n  // Set up method arguments and context\n  const context = { userId };\n  const args = { listId };\n\n  makePrivate._execute(context, args);\n\n  const otherUserContext = { userId: Random.id() };\n\n  assert.throws(() =\u003e {\n    makePublic._execute(otherUserContext, args);\n  }, Meteor.Error, /Lists.methods.makePublic.accessDenied/);\n\n  // Make sure things are still private\n  assertListAndTodoArePrivate();\n});\n```\n\n### Mixins\n\nEvery `ValidatedMethod` can optionally take an array of _mixins_. A mixin is simply a function that takes the options argument from the constructor, and returns a new object of options. For example, a mixin that enables a `schema` property and fills in `validate` for you would look like this:\n\n```js\nfunction schemaMixin(methodOptions) {\n  methodOptions.validate = methodOptions.schema.validator();\n  return methodOptions;\n}\n```\n\nThen, you could use it like this:\n\n```js\nconst methodWithSchemaMixin = new ValidatedMethod({\n  name: 'methodWithSchemaMixin',\n  mixins: [schemaMixin],\n  schema: new SimpleSchema({\n    int: { type: Number },\n    string: { type: String },\n  }),\n  run() {\n    return 'result';\n  }\n});\n```\n\n### Community mixins\n\nIf you write a helpful `ValidatedMethod` mixin, please file an issue or PR so that it can be listed here!\n\n- [tunifight:loggedin-mixin](https://atmospherejs.com/tunifight/loggedin-mixin) : Simple mixin to check if the user is logged in before calling the `run` function.\n- [didericis:permissions-mixin](https://atmospherejs.com/didericis/permissions-mixin) : A permissions mixin to use with mdg:validated-method package.\n- [didericis:callpromise-mixin](https://atmospherejs.com/didericis/callpromise-mixin) : A mixin for the mdg:validated-method package that adds `callPromise`.\n- [lacosta:method-hooks](https://atmospherejs.com/lacosta/method-hooks) : A mixin that adds before and after hooks\n- [ziarno:restrict-mixin](https://atmospherejs.com/ziarno/restrict-mixin) : A mixin to throw errors if condition pass\n- [ziarno:provide-mixin](https://atmospherejs.com/ziarno/provide-mixin) : A mixin to add arguments to the run function\n- [rlivingston:simple-schema-mixin](https://atmospherejs.com/rlivingston/simple-schema-mixin) : A mixin to ease the use of SimpleSchema for validation.\n\n### Ideas\n\n- It could be nice to have a `SimpleSchema` mixin which just lets you specify a `schema` option rather than having to pass a `validator` function into the `validate` option. This would enable the below.\n- With a little bit of work, this package could be improved to allow easily generating a form from a method, based on the schema of the arguments it takes. We just need a way of specifying some of the arguments programmatically - for example, if you want to make a form to add a comment to a post, you need to pass the post ID somehow - you don't want to just have a text field called \"Post ID\".\n\n### Discussion and in-depth info\n\n#### Validation and throwStubExceptions\n\nBy default, using `Meteor.call` to call a Meteor method invokes the client-side simulation and the server-side implementation. If the simulation fails or throws an error, the server-side implementation happens anyway. However, we believe that it is likely that an error in the simulation is a good indicator that an error will happen on the server as well. For example, if there is a validation error in the arguments, or the user doesn't have adequate permissions to call that method, it's often easy to identify that ahead of time on the client.\n\nIf you already know the method will fail, why call it on the server at all? That's why this package turns on a [hidden option](https://forums.meteor.com/t/exception-handling-best-practices/4301) to `Meteor.apply` called `throwStubExceptions`.\n\nWith this option enabled, an error thrown by the client simulation will stop the server-side method from being called at all.\n\nWatch out - while this behavior is good for conserving server resources in the case where you know the call will fail, you need to make sure the simulation doesn't throw errors in the case where the server call would have succeeded. This means that if you have some permission logic that relies on data only available on the server, you should wrap it in an `if (!this.isSimulation) { ... }` statement.\n\n#### ID generation and returnStubValue\n\nOne big benefit of the built-in client-side `Collection#insert` call is that you can get the ID of\nthe newly inserted document on the client right away. This is sometimes listed as a benefit of\nusing allow/deny over custom defined methods. Not anymore!\n\nFor a while now, Meteor has had a [hard-to-find](https://forums.meteor.com/t/how-to-return-value-on-meteor-call-in-client/1277/9) option to `Meteor.apply` called `returnStubValue`. This lets you return a value from a client-side simulation, and use that value immediately on the client. Also, Meteor goes to great lengths to make sure that ID generation on the client and server is consistent. Now, it's easy to take advantage of this feature since this package enables `returnStubValue` by default.\n\nHere's an example of how you could implement a custom insert method, taken from the [Todos example app](https://github.com/meteor/todos/blob/master/packages/lists/methods.js) we are working on for the Meteor Guide:\n\n```js\nconst insert = new ValidatedMethod({\n  name: 'Lists.methods.insert',\n  validate: new SimpleSchema({}).validator(),\n  run() {\n    return Lists.insert({});\n  }\n});\n```\n\nYou can get the ID generated by `insert` by reading the return value of `call`:\n\n```js\nimport {\n  insert,\n} from '/imports/api/lists/methods';\n\n// The return value of the stub is an ID generated on the client\nconst listId = insert.call((err) =\u003e {\n  if (err) {\n    // At this point, we have already redirected to the new list page, but\n    // for some reason the list didn't get created. This should almost never\n    // happen, but it's good to handle it anyway.\n    FlowRouter.go('home');\n    alert('Could not create list.');\n  }\n});\n\nFlowRouter.go('listsShow', { _id: listId });\n```\n\n### Running tests\n\n```\nmeteor test-packages --driver-package practicalmeteor:mocha ./\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmeteor%2Fvalidated-method","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmeteor%2Fvalidated-method","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmeteor%2Fvalidated-method/lists"}