This forum has moved, please join us on github discussions. We will keep these old posts available for reference. Thank you!

Apostrophe-headless 2.7.0: support for `distinct` values to populate your filters

The apostrophe-headless module adds a REST API to Apostrophe’s pieces and pages. It is useful when you want to build a native app, React app, Vue app, etc. while using ApostropheCMS as a backend, or just interface ApostropheCMS with another CMS.

Starting with version 2.7.0, in addition to fetching actual pieces, you can now obtain information about the distinct tags that may exist on those pieces, as well as information about the distinct objects that are joined to them.

This is useful to populate your filters. For instance, to allow the user to filter the results by tag without prefetching every result in the database to scan for tags, you must know what tags exist.

Thanks to Michelin for making this work possible via Apostrophe Enterprise Support.

To add information about distinct tags to the response, first configure your module to allow it:

// in lib/modules/products/index.js
'products': {
  extend: 'apostrophe-pieces',
  name: 'product',
  restApi: {
    safeDistinct: [ 'tags' ]
  }
}

Without safeDistinct, developers would be able to cause a denial of service by requesting all distinct values at once for fields like _id that are always different.

Now, you may access URLs like this:

/api/v1/products?distinct=tags

The response will look like:

{
  results: [ ... pieces here ],
  distinct: {
    tags: [ 
      {
        label: 'Free',
        value: 'Free'
      },
      {
        label: 'Paid',
        value: 'Paid'
      }
    ]
  }
}

Now we can display the labels to our users, and if they pick one, send back the value in the tags query parameter:

/api/v1/products?tags=Paid

Since the distinct values are intended for use as filters, use of safeDistinct implies safeFilter as well. You don’t have to specify both for the same filter.

You can pass multiple values for tags, with or without the familiar [], syntax, for example: tags[]=one&tags[]=two
You’ll get results that include at least one of the tags.

Distinct values for joins

Now let’s assume there is a joinByOne schema field called _specialist that joins our product piece with a specialist piece. We can fetch distinct values here too. In this case, the value property will be the _id:

// in lib/modules/products/index.js

'products': {
  name: 'product',
  extend: 'apostrophe-pieces',
  addFields: [
    {
      type: 'joinByOne',
      name: '_specialist'
    }
  ],
  restApi: {
    safeDistinct: [ '_specialist' ]
  }
}

Then we can access:

/api/v1/products?distinct=_specialist

The response will look like:

{
  results: [ ... pieces here ],
  distinct: {
    _specialist: [ 
      {
        label: 'Jane Doe',
        value: '_cyyyy'
      },
      {
        label: 'Joe Smith',
        value: '_czzzz'
      }
    ]
  }
}

Once again we can display the labels to our users, and if they pick one, send back the value in the _specialist query parameter:

/api/v1/products?_specialist=_cyyyy

We send the value, NOT the label. Again, you can send more than one by passing more than one _specialist query parameter. You’ll get results that include at least one of the specialists.

Adding counts for each distinct value

Want to show the user how many items are tagged Free as part of your filter interface? You can do that by using distinct-counts in place of distinct. Keep in mind that the answer will still be in the distinct object; however, each choice will now have a count property in addition to label and value.

Example request:

/api/v1/products?distinct-counts=tags

Example response:

{
  results: [ ... pieces here ],
  distinct: {
    tags: [
      {
        label: 'Free',
        value: 'Free',
        count: 5
      },
      ... More tags here
    ]
  }
}

Distinct values for more than one filter

Yes, this is supported. Just use comma-separated field names when passing distinct or counts in your URL.

For example, you might make this request:

/api/v1/products?distinct=_specialist,tags

In which case the distinct property of the response will have both _specialist and tags subproperties.

Make sure both _specialist and tags are configured as safeDistinct:

// in lib/modules/products/index.js

'products': {
  name: 'product',
  extend: 'apostrophe-pieces',
  addFields: [
    {
      type: 'joinByOne',
      name: '_specialist'
    }
  ],
  restApi: {
    safeDistinct: [ '_specialist', 'tags' ]
  }
}