MongoDB aggregate vs Promise cascades, what is better?
Tags: mongodb, database, javascript, typescript
Published: 2020-07-13 17:01:08

I mean, which is faster / more efficient / or easier to go wrong?

Of course, Promises to query for `_id` are more TypeScript-friendly.

```ts
export function getAuthorizedCategories(o: {
  userId: string
  type?: typeof sDictionaryType.type | 'user' | typeof sDictionaryType.type[]
  langFrom?: 'chinese'
  langTo?: string
  _id?: string
}) {
  return DbCategoryModel.find({
    _id: o._id,
    userId: {
      $in: [o.userId, 'shared', 'default'],
    },
    type: o.type
      ? o.type === 'user'
        ? { $exists: false }
        : Array.isArray(o.type)
        ? { $in: o.type }
        : o.type
      : undefined,
    langFrom: o.langFrom,
    langTo: o.langTo as 'english',
  })
}

  function _doSearch({
    userId,
    type,
    langFrom,
    langTo,
    q,
    page,
    perPage,
    limit,
    select,
    exclude,
    randomize,
  }: Parameters<typeof getAuthorizedCategories>[0] & {
    q?: string
    page?: number
    perPage?: number
    limit: number
    select: string[]
    exclude?: string[]
    randomize?: {
      levelMin: number
      levelMax: number
    }
  }) {
    return (async () => {
      let cursor = getAuthorizedCategories({
        userId,
        type,
        langFrom,
        langTo,
      })
      if (!randomize) {
        cursor = cursor.sort('-priority')
      }

      return cursor.select('_id')
    })().then((cs) => {
      if (cs.length) {
        return Promise.all(
          cs.map(async (c) => {
            let cursor = DbItemModel.find({
              $and: [
                q ? { $text: { $search: q, $language: langFrom } } : null,
                { categoryId: c._id },
                { entry: exclude ? { $nin: exclude } : undefined },
              ]
                .filter((el) => el)
                .map((el) => el!),
            })

            if (!randomize) {
              cursor = cursor.sort('-priority -level')
            }

            return cursor.select('_id').then((its) => ({
              itemIds: its.map((it) => it._id),
            }))
          })
        ).then(async (cs) => {
          const ids = cs.reduce(
            (prev, { itemIds }) => [...prev, ...itemIds],
            [] as string[]
          )

          let idsInScope: string[] = []

          if (randomize) {
            idsInScope = [ids[Math.floor(Math.random() * ids.length)]]
          } else {
            const idsPerPage = limit === -1 ? null : perPage || limit
            const idsStart = idsPerPage && page ? (page - 1) * idsPerPage : 0
            const idsEnd = idsPerPage && page ? page * idsPerPage : undefined

            idsInScope = ids.slice(idsStart, idsEnd)
          }

          return {
            result: await DbItemModel.find({
              _id: { $in: idsInScope },
            })
              .select(select.join(' '))
              .then((rs) => {
                const rMap = rs.reduce(
                  (prev, c) => ({ ...prev, [c._id]: c }),
                  {} as Record<string, any>
                )

                return idsInScope.map((id) => rMap[id])
              }),
            count: page ? ids.length : undefined,
          }
        })
      }

      return {
        result: [],
        count: page ? 0 : undefined,
      }
    })
  }
```

I have written this with Aggregate framework, with `$lookup` and `$facet` before, but it crashed, and it seemed hard to debug...

What about ORM's? Is it better to do web-server-side or database-side? What about network latency, or is it time to put database and web-server on the same machine?