Migrating to Node.js backend, how do I create relationships from an array of IDs? Previously done with route


We are migrating our server from forest-express-mongoose to Node.js. We have not yet figured out a way to create a relationship from an array of IDs, previously done with a route. I will give an example below and also show some code snippets.

Based on the code explanation and code snippets below, the question is:

How do we achieve the same thing as before without adding userId to venue documents?

Setup explanation

We have 3 different collections, Users, Merchants, and Venues.

Users β†’ Merchant (many β†’ 1). Reference = user.manages.merchantId
Venues β†’ Merchant (many β†’ 1). Reference = venue.merchantId

We wish to create a smart relationship from Users to Venues, which would become an array of references to multiple venues that we manually have to calculate.

Solution before migrating, using routes

We created a custom smart relationship between User and Venues, returning an array of references.

router.get('/users/:userId/relationships/venues', async (req, res, next) => {
    const user = await users.findOne({
      _id: mongoose.Types.ObjectId(req.params.userId),

    let venuesList = [];
    const merchant = await merchants.findOne({
      _id: user.manages.merchantId,

    venuesList.push(...(await venues.find({ merchantId: merchant._id })));
    const serializer = new RecordSerializer({ name: 'venues' });

      await serializer.serialize(venuesList, {
        count: venues.length,

How we would like to do it now in the new Node.js version

We get the following error when running the code below: Error Types from 'User.venuesIds' and 'Venue._id' do not match.

.customizeCollection('User', collection => {
    collection.addField('venuesIds', {
      columnType: ['String'],
      dependencies: ['manages@@@merchantId'],
      getValues: async (users, context) => {
        const venues = context.dataSource.getCollection('Venue');

        const relatedVenues = await venues.list(
          { conditionTree: { field: 'merchantId', operator: 'In', value: users.map(m => m['manages@@@merchantId']) } },
          ['_id', 'merchantId'],

        return users.map(record => {
          return relatedVenues.filter(ven => ven.merchantId === record['manages@@@merchantId']).map(ven => ven._id);

    collection.replaceFieldOperator('venuesIds', 'In', async (venueIds, context) => {
      const records = await context.dataSource
        .list({ conditionTree: { field: '_id', operator: 'In', value: venueIds } }, ['_id']);

      return { field: 'venuesIds', operator: 'In', value: records.map(r => r._id) };

    collection.addManyToOneRelation('venues', 'Venue', {
      foreignKey: 'venuesIds',
      foreignKeyTarget: '_id',


  • Project name: PayAtt Internal Admin Portal
  • Team name: All teams
  • Environment name: All environments
  • Agent (old): forest-express-mongoose: ^8.7.9
  • Agent (new): "@forestadmin/agent": "^1.13.4", & "@forestadmin/datasource-mongoose": "^1.4.2",
  • Database type: MongoDB Atlas

Ok so something like this?

merchant = { _id: 'merchant1' }
user = { _id: 'user1', manages: { merchantId: 'merchant1' } };
venues = { _id: 'venue1', merchantId: 'merchant1' };

I don’t think you need to query any intermediary collection.
As I understand, you are just trying to get venues where venue.merchantId === user.manages.merchantId

Are you using flattenMode: 'auto'? If not tell me, we’ll write a more complete answer

agent.customizeCollection('users', collection => {
  collection.addOneToManyRelationship('userVenus', 'venue', {
    originKey: 'merchantId',
    originKeyTarget: 'manages@@@merchantId'  // name of the flattened field, this should show on the autocompletion

Thank you @anon39940173, this solved the issue!

It just takes some time to wrap my head around the new way of working with the system. I’m glad the solution was so easy. :slight_smile: