skip to Main Content

I have a model Book with a field "tags" which is of type array of String / GraphQLString.

Currently, I’m able to query the tags for each book.

{
    books {
        id
        tags
    }
}

and I get the result:

{
  "data": {
    "books": [
      {
        "id": "631664448cb20310bc25c89d",
        "tags": [
          "database",
          "middle-layer"
        ]
      },
      {
        "id": "6316945f8995f05ac71d3b22",
        "tags": [
          "relational",
          "database"
        ]
      },
    ]
  }
}

I want to write a RootQuery where I can fetch all unique tags across all books. This is how far I am (which is not too much):

  tags: {
    type: new GraphQLList(GraphQLString),
    resolve(parent, args) {
        Book.find({}) // CAN'T FIGURE OUT WHAT TO DO HERE
        return [];
    }
  }

Basically, I’m trying to fetch all books and then potentially merge all tags fields on each book.

I expect that if I query:

{
    tags
}

I would get

["relational", "database", "middle-layer"]

I am just starting with Mongoose, MongoDB, as well as GraphQL, so not 100% sure what keywords to exactly look fo or even what the title of this question should be.

Appreciate the help.

2

Answers


  1. MongoDb + JavaScript Solution

    tags = Book.aggregate([
      {
        $project: {
          tags: 1,
          _id: 0,
        }
      },
    ])
    

    This returns an array of objects that contain only the tags value. $project is staging this item in the aggregation pipeline by selecting keys to include, denoted by 1 or 0. _id is added by default so it needs to be explicitly excluded.

    Then take the tags array that looks like this:

    [
      {
        "tags": [
          "database",
          "middle-layer"
        ]
      },
      {
        "tags": [
          "relational",
          "database"
        ]
      }
    ]
    

    And reduce it to be one unified array, then make it into a javascript Set, which will exclude duplicates by default. I convert it back to an Array at the end, if you need to perform array methods on it, or write back to the DB.

    let allTags = tags.reduce((total, curr) => [...total, ...curr.tags], [])
    allTags = Array.from(new Set(allTags))
    
    const tags = [
      {
        "tags": [
          "database",
          "middle-layer"
        ]
      },
      {
        "tags": [
          "relational",
          "database"
        ]
      }
    ]
    
    let allTags = tags.reduce((total, curr) => [...total, ...curr.tags], [])
    allTags = Array.from(new Set(allTags))
    
    console.log(allTags)

    Pure MongoDB Solution

    Book.aggregate([
      {
        $unwind: "$tags"
      },
      {
        $group: {
          _id: "_id",
          tags: {
            "$addToSet": "$tags"
          }
        }
      },
      {
        $project: {
          tags: 1,
          _id: 0,
          
        }
      }
    ])
    
    Steps in Aggregation Pipeline
    • $unwind

      • Creates a new Mongo Document for each tag in tags
    • $group

      • Merges the individual tags into a set called tags
        • Sets are required to be have unique values and will exclude duplicates by default
      • _id is a required field
        • _id will be excluded from the final aggregation so it doesn’t matter what it is
    • $project

      • Chooses which fields to pull from the previous step in the pipeline
      • Using it here to exclude _id from the results

    Output

    [
      {
        "tags": [
          "database",
          "middle-layer",
          "relational"
        ]
      }
    ]
    

    Mongo Playground Demo

    While this solution gets the result with purely Mongo queries, the resulting output is nested and still requires traversal to get to desired fields. I do not know of a way to replace the root with a list of string values in an aggregation pipeline. So at the end of the day, JavaScript is still required.

    Login or Signup to reply.
  2. You want to $unwind the arrays so they’re flat, at that point we can just use $group to get unique values. like so:

    db.collection.aggregate([
      {
        "$unwind": "$data.books"
      },
      {
        "$unwind": "$data.books.tags"
      },
      {
        $group: {
          _id: "$data.books.tags"
        }
      }
    ])
    

    Mongo Playground

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search