Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling Mongoose Populated Fields in GraphQL

How do I represent a field that could be either a simple ObjectId string or a populated Object Entity?

I have a Mongoose Schema that represents a 'Device type' as follows

// assetSchema.js

import * as mongoose from 'mongoose'
const Schema = mongoose.Schema;

var Asset = new Schema({  name : String,
                          linked_device: { type: Schema.Types.ObjectId, 
                                           ref: 'Asset'})

export AssetSchema = mongoose.model('Asset', Asset);

I am trying to model this as a GraphQLObjectType but I am stumped on how to allow the linked_ue field take on two types of values, one being an ObjectId and the other being a full Asset Object (when it is populated)

// graphql-asset-type.js

import { GraphQLObjectType, GraphQLString } from 'graphql'

export var GQAssetType = new GraphQLObjectType({
           name: 'Asset',
           fields: () => ({
               name: GraphQLString,
               linked_device: ____________    // stumped by this
});

I have looked into Union Types but the issue is that a Union Type expects fields to be stipulated as part of its definition, whereas in the case of the above, there are no fields beneath the linked_device field when linked_device corresponds to a simple ObjectId.

Any ideas?

like image 201
viztastic Avatar asked Jan 01 '17 13:01

viztastic


2 Answers

As a matter of fact, you can use union or interface type for linked_device field.

Using union type, you can implement GQAssetType as follows:

// graphql-asset-type.js

import { GraphQLObjectType, GraphQLString, GraphQLUnionType } from 'graphql'

var LinkedDeviceType = new GraphQLUnionType({
  name: 'Linked Device',
  types: [ ObjectIdType, GQAssetType ],
  resolveType(value) {
    if (value instanceof ObjectId) {
      return ObjectIdType;
    }
    if (value instanceof Asset) {
      return GQAssetType;
    }
  }
});

export var GQAssetType = new GraphQLObjectType({
  name: 'Asset',
  fields: () => ({
    name: { type: GraphQLString },
    linked_device: { type: LinkedDeviceType },
  })
});

Check out this excellent article on GraphQL union and interface.

like image 125
Ahmad Ferdous Avatar answered Sep 29 '22 10:09

Ahmad Ferdous


I was trying to solve the general problem of pulling relational data when I came across this article. To be clear, the original question appears to be how to dynamically resolve data when the field may contain either the ObjectId or the Object, however I don't believe it's good design in the first place to have a field store either object or objectId. Accordingly, I was interested in solving the simplified scenario where I keep the fields separated -- one for the Id, and the other for the object. I also, thought employing Unions was overly complex unless you actually have another scenario like those described in the docs referenced above. I figured the solution below may interest others also...

Note: I'm using graphql-tools so my types are written schema language syntax. So, if you have a User Type that has fields like this:

type User {
    _id: ID
    firstName: String
    lastName: String
    companyId: ID
    company: Company
}

Then in my user resolver functions code, I add this:

  User: {   // <-- this refers to the User Type in Graphql
    company(u) {   // <-- this refers to the company field
      return User.findOne({ _id: u.companyId }); // <-- mongoose User type
    },
  }

The above works alongside the User resolver functions already in place, and allow you write GQL queries like this:

query getUserById($_id:ID!) 
    { getUserById(_id:$_id) {
    _id
    firstName
    lastName
    company {
        name
    }
    companyId
    }}

Regards,

S. Arora

like image 33
sarora Avatar answered Sep 29 '22 10:09

sarora