Search code examples
node.jsmongodbmongoosegraphqlgraphql-tools

GraphQL nested query returning null


When attempting to run the following query I get this error: "Cannot return null for non-nullable field User.first_name.". I am not expecting a null result.

Query:
{
  site(site_reference: 123456789) {
    site_name
    site_managers {
      _id
      first_name
    }
  }
}

What I expect to see is:

{
  "data": {
    "site": {
      "site_name": "Test Site",
      "site_managers": [
        {
          "_id": "5bbcd55ff7bd643be4d490fa",
          "first_name": "Claire"
        },
        {
          "_id": "5b9fa2e1f66fb32164f4d547",
          "first_name": "John"
        }
      ]
    }
  }
}

My MongoDB stores the _id in an array which it is able to return, however anything else from my user type returns null.

User Schema:

type User {
    _id: String!,
    first_name: String!,
    last_name: String!,
    role_id: Int!
    email: String!,
    password: String,
    created_date: String!,
    updated_date: String!,
    last_login_date: String,
    reset_password_token: String
  }
  type Query {
    user(_id: String!): User!
    users(role_id: Int): [User!]!
  }

User Resolver:

  Query: {
    user: (parent, args) => User.findById(args._id),
    users: (parent, args) => {
      if (args.role_id) {
        return User.find({ role_id: args.role_id })
      }
      return User.find({})
    },
  },

Site Schema:

  type Site {
    site_name: String!,
    use_policy: String!,
    redirect_url: String!,
    intro_text: String!,
    logo: String!,
    address_line_1: String!,
    address_line_2: String,
    address_city: String!,
    address_county: String!,
    address_postcode: String!,
    phone: String!,
    site_reference: Int!,
    site_managers: [User],
    business_id: String!,
    connect_duration: Int!,
    created_date: String!,
    updated_date: String!,
  }

  type Query {
    site(site_reference: Int!): Site!
    sites(business_id: Int!): [Site!]!
  }

Site Resolver:

  Query: {
    site: (parent, args) =>
      Site.findOne({ site_reference: args.site_reference }),
    sites: (parent, args) => Site.find({ business_id: args.business_id }),
  },

I imagine I need to do more in my resolver for the site query but I am unsure of what exactly. I have already tried using mongoose.populate but to no avail, the furthest I got was returning an empty array with the populate method.

Thanks in advance for your time.

Edit: Here's my mongoose schemas

User Schema

const UserSchema = new mongoose.Schema({
  first_name: { type: String, required: true },
  last_name: { type: String, required: true },
  role_id: { type: Number, required: true },
  email: { type: String, required: true },
  password: { type: String },
  created_date: { type: Date, default: Date.now, required: true },
  updated_date: { type: Date, default: Date.now, required: true },
  last_login_date: { type: Date },
  reset_password_token: { type: String },
})

Site Schema:

const SiteSchema = new mongoose.Schema({
  site_name: { type: String, required: true },
  use_policy: {
    type: String,
    required: true,
    default: config.defaultUsePolicy,
  },
  redirect_url: {
    type: String,
    required: true,
    default: config.defaultRedirect,
  },
  intro_text: {
    type: String,
    required: true,
    default: config.defaultIntroText,
  },
  logo: { type: String, required: true, default: config.defaultLogo },
  address_line_1: { type: String, required: true },
  address_line_2: String,
  address_city: { type: String, required: true },
  address_county: { type: String, required: true },
  address_postcode: { type: String, required: true },
  phone: { type: String, required: true },
  site_reference: { type: String, required: true },
  site_managers: {type:[mongoose.Schema.Types.ObjectId], required: true}
  business_id: { type: mongoose.Schema.Types.ObjectId, required: true },
  connect_duration: { type: Number, required: true, default: 0 },
  created_date: { type: Date, default: Date.now, required: true },
  updated_date: { type: Date, default: Date.now, required: true },
})

Solution

  • If you're using MongoDB and mongoose, you will want to use populate to join your collections. In order to do so, in your schema, the field you're populating needs to not only have a type, but also a ref property that tells mongoose which Model to use to populate the field, for example:

    // Note the actual ref should be whatever name you passed to mongoose.model
    site_managers: [{type:mongoose.Schema.Types.ObjectId, ref: 'User'}]
    

    Now, use populate inside your resolver:

    site: (parent, { site_reference }) => {
      return Site.findOne({ site_reference }).populate('site_managers').exec()
    }
    

    See the docs for more detailed examples.