A question about publications

I want to implement search using text indexes. Minimongo doesn’t support those queries, so I wonder…

Can I create a publication where I only return the relevant search results, then filter them further on the client side if needed?

Meteor.publish("search", function ({ keyword }) {
  check(keyword, String);

  if (keyword === "") return [];

  return MyCollection.find({
    status: "published",
    $text: { $search: keyword },

Then I subscribe to that publication in the client:

  const results = useTracker(() => {
    const handler = Meteor.subscribe("search", { keyword });

    if (!handler. Ready()) return [];

    return MyCollection.find().fetch();
  }, [keyword]);

Is that the proper usage? I don’t quite understand how a publication links to a collection. I assume MiniMongo catches the publication and sets the collection to equal the publication results (the correct search results in this case), but I’m not sure.

Maybe MiniMongo just appends the results to the in-memory representation and I still have to filter it on the client side using regex find?

This looks fine (assuming the $text query works - never used that before). You should probably just omit the [keyword] dependency in the useTracker hook.

When you have several subscriptions for a mongo collection, all the results get synced into the same mini-mongo collection. If you have only one subscription with a changeable parameter (like your keyword) you will still have overlapping results with each change of the parameter. If you change the keyword you will get the new results added before the old results get removed. This may or may not be noticeable in the ui. If you are sure you only have on subscription active at any time and over-fetching on parameter change doesn’t bother you, then there’s no problem.

Quite often you will have to filter the data client-side to prevent this over-fetching. So if you can do it easily, just do it. If you have several subscriptions and you need to keep the results of them apart and you can’t filter the results in mini-mongo then you can set up the publications to use separate local collections in the client. I use tunguska:reactive-aggregate quite a bit (and aggregation-pipelines are way beyond mini-mongo), and I always set up client-collections for those publications.

Hi, I am not sure search makes a lot of sense with a publication. Unless perhaps you search in 1-2 fields for something like a last name or so.
A proper text search in Mongo requires a search index instead of a DB index. It happens that this has been published just the day before yesterday: Analysis Paralysis: Building the Right Search Index for Your App - YouTube

Hi, thanks for the replies! I see now that publications just add new data in the client, so you still have to filter client-side somehow.

Methods are used for mutations, so the only way to get data is using publications, right?

You should probably just omit the [keyword] dependency in the useTracker hook.

The keyword comes from the query string, and it changes on form submit or just loading the page, which is what I want. Not sure how to trigger the search on form submit otherwise, given I cannot use methods to retrieve data (or maybe I should?)

Thanks again :slight_smile:

Methods are used for both. Search, in general, is done through methods.

Ah! I see! Thanks for the clarification. When should you use methods to fetch data and when should you use publications then?

Use publications only when you need reactive data, for everything else methods are best suited.


Ah I see, makes sense :slight_smile: Thanks!

You could filter the MiniMongo on the client using a RegExp search. Given that it’s just a filter pass over the local database, it should still be reasonably efficient. Something like:

return MyCollection.find({
  $or: keyword.split().map((word) => {textField: new RegExp(word)})

This doesn’t implement all features of $text: $search, nor its actual splitting algorithm, nor does it properly escape word. But if you decide to go with subscriptions, it’s an approach…

@gosukiwi One way to go with search on subscriptions is to publish the results to a client only collection (e.x collectionName-search) and then on the client you can just use a find().fetch() on this collection. This way you do the search and won’t have to filter the results on the client.

You could do that by using the Mongo.Collection._publishCursor method.

The downside of going with methods here is that if you still want to use minimongo to manage the results, you will need to handle the insertion/update/remove yourself (or use a package).

There’s a nifty trick that we’re using to magically make Elasticsearch work MongoDB and it looks like this:

  example() {
    return Collection.find(anyQueryWillWorkHere, {
      fields: {
        // Add new fields that are not in the database.
        //   They can be static:
        magicFieldThatWillAppearOnTheClient: { $literal: 123456 },

        //   They can depend on the document values:
        anotherOneButFancy: {
          $switch: {
            branches: [
              { case: { $eq: ['$someField', 'this string'] }, then: 1 },
              { case: { $eq: ['$someField', 'now another'] }, then: 2 },
            default: 0,

        // Because we added some fields, we have to list every single one
        // of them. Unfortunately, there's no workaround for that.
        foo: 1,
        bar: 1,
        qux: 1,

By doing that, you can safely distinguish the documents coming from this publication (i.e., the $text search) without any support on the Minimongo side nor extremely inefficient $regex operator.

(If you’d like to hear more about the Elasticsearch part of it, let me know.)