What is the correct type to use for an ObjectId field across mongoose and GraphQL?

Viewed 9876

Following this tutorial, I have a mongoose model: (I'm using the term "Account" instead of "Todo", but it's the same thing)

const Account = mongoose.model('Account', new mongoose.Schema({
  id: mongoose.Schema.Types.ObjectId,
  name: String
}));

and a GraphQLObjectType:

const AccountType = new GraphQLObjectType({
  name: 'account',
  fields: function () {
    return {
      id: {
        type: GraphQLID
      },
      name: {
        type: GraphQLString
      }
    }
  }
});

and a GraphQL mutation to create one of these:

const mutationCreateType = new GraphQLObjectType({
  name: 'Mutation',
  fields: {
    add: {
      type: AccountType,
      description: 'Create new account',
      args: {
        name: {
          name: 'Account Name',
          type: new GraphQLNonNull(GraphQLString)
        }
      },
      resolve: (root, args) => {
        const newAccount = new Account({
          name: args.name
        });

        newAccount.id = newAccount._id;

        return new Promise((resolve, reject) => {
          newAccount.save(err => {
            if (err) reject(err);
            else resolve(newAccount);
          });
        });
      }
    }
  }
})

After running the query:

mutation {
  add(name: "Potato")
  {
    id,
    name
  }
}

in GraphiQL, I get the response:

{
  "errors": [
    {
      "message": "ID cannot represent value: { _bsontype: \"ObjectID\", id: <Buffer 5b 94 eb ca e7 4f 2d 06 43 a6 92 20> }",
      "locations": [
        {
          "line": 33,
          "column": 5
        }
      ],
      "path": [
        "add",
        "id"
      ]
    }
  ],
  "data": {
    "add": {
      "id": null,
      "name": "Potato"
    }
  }
}

The creation of the object was successful, and I can see it in MongoDB Compass:

From MongoDB Compass showing created object

but there seems to be a problem reading the value.

How compatible are GraphQLID and mongoose.Schema.Types.ObjectId ? If they are not compatible, am I misunderstanding the tutorial, particularly it's use of:

newAccount.id = newAccount._id;

? I can't tell if the error is being thrown by GraphQL, or MongoDB, or Mongoose, or something else entirely.

EDIT

Any information on the error

ID cannot represent value: { _bsontype: \"ObjectID\", id: }

would be very helpful. I feel it's telling me it couldn't serialize a BSON object .. but then it displays it serialized. Even knowing what tech (mongo? mongoose? graphql?) was generating the error would help. I'm not having any luck with Google.

EDIT 2

This was a caused by a change to the graphql package introduced recently, and there is a PR awaiting merge which resolves it.

3 Answers

I didn't find an issue and ran this code with one of my existing code bases. Except I wrapped the mutation in the GraphQLObjectType.

const Mutation = new GraphQLObjectType({
    name: 'Mutation',
    fields: {
        addAccount: {
            type: AccountType,
            description: 'Create new account',
            args: {
                name: {
                    name: 'Account Name',
                    type: new GraphQLNonNull(GraphQLString)
                }
            },
            resolve: (root, args) => {
                const newAccount = new Account({
                    name: args.name
                });

                newAccount.id = newAccount._id;

                return new Promise((resolve, reject) => {
                    newAccount.save(err => {
                        if (err) reject(err);
                        else resolve(newAccount);
                    });
                });
            }
        }
    });

To get the working example: Clone the repo. In this repo, the app uses v0.13.2 and you are using v14.0.2 installed via npm i graphql. Downgrade graphql to v0.13.2.

I used ID and it works fine! cause of your problem is not id's type! it's becuase you provide it with wrong value: ObjectID('actuall id')

In order to fix this issue, call toJson function for each fetched data, or simply add a virtual id like this:

YourSchema.virtual('id').get(function() {
    return this.toJSON()._id
}

So what I just found is that _id is of type ObjectID but seems to implicitly cast to String. So if you define your mongoose model id type to be String instead of mongoose.Schema.Types.ObjectId then it should work. Using your current code (from the compose.com tutorial) that copies _id to id, the result will be that, in Mongo (after saving), the _id will be of type ObjectID and your model id will be of type string.

In other words, instead of this

const Account = mongoose.model('Account', new mongoose.Schema({
  id: mongoose.Schema.Types.ObjectId,
  name: String
}));

Do this

const Account = mongoose.model('Account', new mongoose.Schema({
  id: String,
  name: String
}));
Related