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' ]
}
}