Implement Real-Time Data Sync with Meteor Publish/Subscribe
Step-by-step guide to publish a MongoDB collection from a Meteor server and subscribe on the client so that data changes appear instantly in all connected UIs.
08 Jun 2026, 20:29 UTC

Desired outcome
Any insert, update, or delete operation performed on a MongoDB collection on the server is instantly reflected in the user interface of every connected client, without requiring a manual page refresh.
Prerequisites
- A Meteor project initialized with
meteor create my-app(Meteor version ≥ 1.0). - MongoDB is bundled with Meteor; no external database setup is required.
- The
meteorcommand-line tool installed and accessible from your terminal. - Basic familiarity with the Meteor folder structure (
server/,client/,imports/) and JavaScript/TypeScript syntax.
Focused procedure
-
Define a server publication
Create a file under
server/publications.js(or anyserver/**/*.jsfile) that returns a cursor for the collection you want to sync.import { Meteor } from 'meteor/meteor'; import { Tasks } from '../imports/api/tasks.js'; Meteor.publish('tasks.public', function () { // Limit fields to avoid over-fetching and protect sensitive data return Tasks.find({ userId: this.userId }, { fields: { _id: 1, text: 1, checked: 1 } }); });Replace
Taskswith your collection name and adjust the query/fields as needed. -
Subscribe on the client
In client code (e.g.,
client/main.jsor a React/Blaze component), callMeteor.subscribeand keep the subscription handle to manage its lifecycle.import { Meteor } from 'meteor/meteor'; import { useTracker } from 'meteor/react-meteor-data'; import { Tasks } from '../imports/api/tasks.js'; export function TasksList() { const subscription = Meteor.subscribe('tasks.public'); const tasks = useTracker(() => Tasks.find({}).fetch(), [subscription.ready()]); if (!subscription.ready()) { return <div>Loading…</div>; } return ( <ul> {tasks.map(t => ( <li key={t._id}>{t.text} {t.checked && '(done)'}</li> ))} </ul> ); }For Blaze, you would use
Meteor.subscribeinsideTemplate.onCreatedand a helper returningTasks.find(). -
Render reactive data
Use a reactive data source (Blaze helper, React
useTracker, Vue tracker, etc.) so that the UI re-runs whenever the underlying cursor changes.The example above already uses
useTracker; any change to the published collection on the server triggers a new cursor, causing the tracker to rerun and update the list. -
Modify data via Meteor methods
To ensure server-side validation and proper publication updates, change the collection through a Meteor method rather than directly on the client.
import { Meteor } from 'meteor/meteor'; import { Tasks } from '../imports/api/tasks.js'; Meteor.methods({ 'tasks.insert'(text) { if (!this.userId) { throw new Meteor.Error('not-authorized'); } Tasks.insert({ text, userId: this.userId, checked: false, createdAt: new Date() }); }, 'tasks.setChecked'(id, checked) { if (!this.userId) { throw new Meteor.Error('not-authorized'); } Tasks.update(id, { $set: { checked } }); }, });Call these methods from the client with
Meteor.call.
Expected checks
- Open the app in two browser windows (or two devices). Verify that the initial task list appears in both windows after the subscription becomes ready.
- In one window, invoke a method (e.g., add a task via a form that calls
Meteor.call('tasks.insert', ...)). Observe that the new task appears in the other window without a manual refresh. - Repeat with updates and deletions to confirm bidirectional reactivity.
- Open the browser console and look for any subscription error callbacks; they should only fire when you intentionally stop the server or simulate a network failure.
- In DevTools → Network, inspect the WebSocket/DDP traffic after the initial load. Confirm that only the fields you published (
_id,text,checked) are transmitted for subsequent changes.
Recovery options
- Handle subscription errors: Provide a second argument to
Meteor.subscribethat receives an error object. Use it to show a UI fallback or retry logic. - Client-side fallback: If the subscription fails, you can temporarily fetch data via a Meteor method and display it, then resume subscribing when the connection is restored.
- Limit published fields: Always specify a
fieldsselector in your publication to avoid sending unnecessary or sensitive data. This also reduces network load. - Manage subscription handles: Store the handle returned by
Meteor.subscribeand callhandle.stop()when the component unmounts or when you need to switch to a different publication.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.