Run Validated Server Mutations with Meteor Methods
Replace direct client collection writes with Meteor Methods that validate input, enforce authorization, and return structured errors over DDP.
21 Jan 2026, 10:01 UTC

Problem: clients can write directly to the database
In a Meteor application, browser code can call collection insert, update, or remove operations unless you explicitly deny them. That direct write path skips server-side authorization and input validation, so a malicious or buggy client can modify records it should not touch. The useful alternative is a Meteor Method: a server-callable function invoked over DDP that acts as a controlled RPC layer for mutations.
Prerequisites
- A Meteor project created with the Meteor tool.
- The
checkandmatchfacilities available for argument validation. - A MongoDB instance reachable by the app, such as a local database during development.
- File system access to create server and client files in the project.
1. Define the method on the server only
Create a file that Meteor loads only on the server, such as imports/api/tasks/methods.js or server/tasks.js. Files under a server/ directory are not sent to the client.
import { Meteor } from 'meteor/meteor';
import { check, Match } from 'meteor/check';
import { Tasks } from '/imports/api/tasks/collection.js';
Meteor.methods({
'tasks.archive'(taskId) {
check(taskId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'Log in to archive tasks.');
}
const task = Tasks.findOne(taskId);
if (!task) {
throw new Meteor.Error('not-found', 'Task does not exist.');
}
if (task.owner !== this.userId) {
throw new Meteor.Error('not-authorized', 'You do not own this task.');
}
Tasks.update(taskId, { $set: { archived: true, archivedAt: new Date() } });
return { archivedId: taskId };
},
});
Meteor.call sends the request over DDP. The server receives this.userId from the authenticated DDP session; never accept a user ID supplied in the method arguments. check rejects malformed arguments before any database code runs. Meteor.Error produces a structured error the client can inspect.
2. Call the method from the client
In client code, invoke the method with Meteor.call. Provide a callback or use the promise-style pattern supported by your Meteor release.
import { Meteor } from 'meteor/meteor';
function archiveTask(taskId) {
Meteor.call('tasks.archive', taskId, (error, result) => {
if (error) {
console.error(`Error ${error.error}: ${error.reason}`, error.details);
return;
}
console.log('Archived:', result.archivedId);
});
}
If you define a client-side function with the same name, Meteor runs it as a stub for latency compensation. The UI updates immediately, then the server result reconciles the state. If the server throws, the optimistic update may conflict with the actual outcome, so handle errors in the callback.
3. Expected checks
- Start the app from the project root:
meteor run(ormeteor, depending on your release). - Open the browser developer tools and call a valid method with a task ID you own. Confirm the record is updated and the callback receives the return value.
- Call the method with an invalid argument type, such as a number instead of a string. Confirm the client callback receives an error object with
error,reason, anddetailsfields, and that no database change occurs. - Call the method while logged out or with another user's task ID. Confirm the server rejects the request with
not-authorized. - Inspect the Network tab for DDP messages. The invocation should appear as a Meteor method call over DDP, not as an unrestricted direct collection write from the client.
4. Recovery when a mutation changes state
Because a method can insert, update, or remove documents, a failed call can leave the client and server out of sync. Use these options:
- Revert optimistic UI: If a client stub updated the interface before the server responded, restore the previous state inside the error callback.
- Retry idempotent operations only: Only retry methods that are safe to run more than once, such as setting a flag. Avoid retrying inserts that may have partially succeeded.
- Manual compensation: Meteor Methods do not automatically wrap multiple collection writes in a transaction. If your method updates several collections and fails midway, write compensating logic to undo completed steps.
Limitations
- Authorization depends on the DDP session and
this.userId. Do not trustuserIdpassed as an argument. - Latency-compensation stubs can show data that the server later rejects.
- Large arguments or many chained methods over DDP can reduce responsiveness. Split bulk work into separate calls or use pagination.
- Behavior varies with Meteor, Node.js, and MongoDB driver versions. Verify compatibility with your installed release before adopting new syntax.
Practical verification
After deploying or changing a method, run the invalid-argument and unauthorized-call checks above in a development environment. Confirm the server logs show the method executed and that the client receives structured errors rather than crashing silently.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.