GraphQL Subscriptions Error: "The \"properties\" argument must be of type Array. Received type object" - graphql

I am trying to implement a simple API with GraphQL. My queries and my mutations are in place and working, but now I'm trying to include subscriptions as well.
I already added the subscription in the schema, I included the event publish in the addUser mutation and defined the subscribe function for the subscription type.
Now, when I am trying to run a subscription query in the graphiql in-browser IDE, I get this error:
"The \"properties\" argument must be of type Array. Received type object"
Attached is the schema object. Did I configured something wrong or am I missing something? Thanks!
P.S I also need to mention that I am using mongoose to store the data on an a mongo instance, hence the entities.
import {
GraphQLFloat,
GraphQLID,
GraphQLInt,
GraphQLList,
GraphQLNonNull,
GraphQLObjectType,
GraphQLSchema,
GraphQLString
} from 'graphql';
// models
import UserType from '../types/user/UserType';
import AccountType from '../types/account/AccountType';
import TransactionType from '../types/transaction/TransactionType';
// entities
import User from '../entities/user/user';
import Account from '../entities/account/account';
import Transaction from '../entities/transaction/transaction';
// subscriptions
import { PubSub } from 'graphql-subscriptions';
// subscriptions
const pubsub = new PubSub();
const USER_CREATED = 'user_created';
// the acceptable starting point of our graph
const RootQueryType = new GraphQLObjectType({
name: 'RootQueryType',
fields: () => ({
// query individual entities in the database
user: {
type: UserType,
description: 'The current user identified by an id',
args: {
id: {
type: GraphQLID
}
},
resolve(parent, args) {
return User.findById(args.id);
}
},
account: {
type: AccountType,
description: 'Details about the account in question identified by an id',
args: {
id: {
type: GraphQLID
}
},
resolve(parent, args) {
return Account.findById(args.id);
}
},
transaction: {
type: TransactionType,
description: 'Details about the transaction in question identified by an id',
args: {
id: {
type: GraphQLID
}
},
resolve(parent, args) {
return Transaction.findById(args.id);
}
},
// query all entities in the database
users: {
type: new GraphQLList(UserType),
resolve: (parent, args) => {
return User.find({});
}
},
accounts: {
type: new GraphQLList(AccountType),
resolve: (parent, args) => {
return Account.find({});
}
},
transactions: {
type: new GraphQLList(TransactionType),
resolve(parent, args) {
return Transaction.find({});
}
}
})
});
const MutationType = new GraphQLObjectType({
name: 'Mutation',
fields: () => ({
addUser: {
type: UserType,
args: {
name: {
type: new GraphQLNonNull(GraphQLString)
},
age: {
type: new GraphQLNonNull(GraphQLInt)
},
email: {
type: new GraphQLNonNull(GraphQLString)
}
},
resolve(parent, args) {
let user = new User({
name: args.name,
age: args.age,
email: args.email
});
pubsub.publish(USER_CREATED, {
newUser: user
});
return user.save();
}
},
addAccount: {
type: AccountType,
args: {
currency: {
type: new GraphQLNonNull(GraphQLString)
},
balance: {
type: new GraphQLNonNull(GraphQLFloat)
},
holderId: {
type: new GraphQLNonNull(GraphQLString)
}
},
resolve(parent, args) {
let account = new Account({
currency: args.currency,
balance: args.balance,
holderId: args.holderId
});
return account.save().then(() => console.log('user created'));
}
},
addTransaction: {
type: TransactionType,
args: {
sourceAccountId: {
type: new GraphQLNonNull(GraphQLString)
},
targetAccountId: {
type: new GraphQLNonNull(GraphQLString)
},
amount: {
type: new GraphQLNonNull(GraphQLFloat)
}
},
resolve(parent, args) {
let transaction = new Transaction({
sourceAccountId: args.sourceAccountId,
tagetAccountId: args.tagetAccountId,
timestamp: new Date(),
amount: args.amount
});
Account.findById(args.sourceAccountId, (err, account) => {
if (!err) {
account.balance -= args.amount;
return account.save();
}
});
Account.findById(args.targetAccountId, (err, account) => {
if (!err) {
account.balance += args.amount;
return account.save();
}
});
return transaction.save();
}
}
})
});
const SubscriptionType = new GraphQLObjectType({
name: 'Subscription',
fields: () => ({
newUser: {
type: UserType,
description: 'This subscription is going to provide information every time a new user creation event fires',
resolve: (payload, args, context, info) => {
console.table(payload, args, context, info); // debugging
return payload;
},
subscribe: () => pubsub.asyncIterator(USER_CREATED)
}
})
});
const schema = new GraphQLSchema({
query: RootQueryType,
mutation: MutationType,
subscription: SubscriptionType
});
export default schema;
I expect that when I run the subscription query, it will run listening for events being published and when from another tab I will run a mutation to add a new user, the first tab will catch the event and return details of the user in the payload.

Related

GraphQL Array Error - "One of the provided types for building the Schema is missing a name"

I'm getting an error in my graphql resolvers file and it appears to be caused when I add the [ GraphQLID ]. Is this the correct way to add an array of GraphQLID's as I'm not entirely sure what I'm doing wrong.
const tvshowArgs = {
name: { type: GraphQLString },
composers: { type: [ GraphQLID ] },
}
const Mutation = {
addTvShow: {
type: TvShowType,
args: {
...tvshowArgs
},
resolve: async (parent, args, ctx) => {
const { user_id } = ctx.me
const doesExist = await TvShow.find({ tmdbId: args.tmdbId })
if (doesExist.length > 0) {
throw new GraphQLError('This TV Show already exists')
}
const tvshow = new TvShow({
name: args.name,
composers: args.composers,
})
return tvshow.save()
}
}
}

Modularizing GraphQL Types into separate files

I have a GraphQL implementation with a single monolithic types/index.js file that currently contains two type definitions:
const graphql = require('graphql');
const Book = require('../../../models/book');
const Author = require('../../../models/author');
const {
GraphQLObjectType,
GraphQLString,
GraphQLSchema,
GraphQLID,
GraphQLInt,
GraphQLList,
GraphQLNonNull,
} = graphql;
const BookType = new GraphQLObjectType({
name: 'Book',
fields: () => ({
id: { type: GraphQLID },
name: { type: GraphQLString },
genre: { type: GraphQLString },
author: {
type: AuthorType,
resolve: (parent, args) => {
// code to get data from db
return Author.findById(parent.authorId);
},
},
}),
});
const AuthorType = new GraphQLObjectType({
name: 'Author',
fields: () => ({
id: { type: GraphQLID },
name: { type: GraphQLString },
age: { type: GraphQLInt },
books: {
type: new GraphQLList(BookType),
resolve: (parent, args) => {
// code to get data from db
return Book.find({authorId: parent.id});
},
},
}),
});
module.exports = {BookType, AuthorType};
This is the file I import into my schema.js file where it's used by root queries and mutations:
const {
GraphQLObjectType,
GraphQLString,
GraphQLSchema,
GraphQLID,
GraphQLInt,
GraphQLList,
GraphQLNonNull,
} = require('graphql');
const Book = require('../../../models/book');
const Author = require('../../../models/author');
const {BookType, AuthorType} = require('../types');
// QUERIES
//------------------------------------------------------------------------------------------------------
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
book: {
type: BookType,
args: { id: { type: GraphQLID } },
resolve: (parent, args) => {
// code to get data from db
return Book.findById(args.id);
},
},
author: {
type: AuthorType,
args: { id: { type: GraphQLID } },
resolve: (parent, args) => {
// code to get data from db
return Author.findById(args.id);
},
},
books: {
type: new GraphQLList(BookType),
resolve: (parent, args) => {
// code to get data from db
return Book.find({});
},
},
authors: {
type: new GraphQLList(AuthorType),
resolve: (parent, args) => {
// code to get data from db
return Author.find({});
}
},
},
});
// MUTATIONS
//------------------------------------------------------------------------------------------------------
const Mutation = new GraphQLObjectType({
name: 'Mutation',
fields: {
addAuthor: {
type: AuthorType,
args: {
name: { type: new GraphQLNonNull(GraphQLString) },
age: { type: new GraphQLNonNull(GraphQLInt) }
},
resolve(parent, args) {
let author = new Author({
name: args.name,
age: args.age
});
return author.save();
}
},
addBook: {
type: BookType,
args: {
name: { type: new GraphQLNonNull(GraphQLString) },
genre: { type: new GraphQLNonNull(GraphQLString) },
authorId: { type: new GraphQLNonNull(GraphQLID) },
},
resolve(parent, args) {
let book = new Book({
name: args.name,
genre: args.genre,
authorId: args.authorId,
});
return book.save();
},
},
}
});
module.exports = new GraphQLSchema({
query: RootQuery,
mutation: Mutation,
});
But as the project grows, I am anticipating dozens of types with tons of two-way relationships. So I'd like to modularize all my types into individual files, such as types/BookType.js, types/AuthorType.js, etc. rather than a single types/index.js as I have right now. What's the best way to accomplish this given the two-way relationships?
While segregating the types into separate files, you'll need to handle two-way relationships. In this case, AuthorType needs BookType and vice-versa. So you'll need to import AuthorType in types/BookTypes.js and BookType in types/AuthorType.js but this will introduce a classic circular dependency issue (before AuthorType exports it demands BookType and vice-versa) which is common in node projects. You can read more about it here. To handle this, shift your require calls at the end of the file in both types. So your code looks somewhat like this:
types/BookType.js
const graphql = require('graphql');
const Book = require('../../../models/book');
const Author = require('../../../models/author');
const {
GraphQLObjectType,
GraphQLString,
GraphQLSchema,
GraphQLID,
GraphQLInt,
GraphQLList,
GraphQLNonNull,
} = graphql;
const BookType = new GraphQLObjectType({
name: 'Book',
fields: () => ({
id: { type: GraphQLID },
name: { type: GraphQLString },
genre: { type: GraphQLString },
author: {
type: AuthorType,
resolve: (parent, args) => {
// code to get data from db
return Author.findById(parent.authorId);
},
},
}),
});
module.exports = BookType;
// This is here to prevent circular dependencies problem which will lead to the formation of infinite loop
const AuthorType = require("./AuthorType");
types/AuthorType.js
const graphql = require('graphql');
const Book = require('../../../models/book');
const Author = require('../../../models/author');
const {
GraphQLObjectType,
GraphQLString,
GraphQLSchema,
GraphQLID,
GraphQLInt,
GraphQLList,
GraphQLNonNull,
} = graphql;
const AuthorType = new GraphQLObjectType({
name: 'Author',
fields: () => ({
id: {
type: GraphQLID
},
name: {
type: GraphQLString
},
age: {
type: GraphQLInt
},
books: {
type: new GraphQLList(BookType),
resolve: (parent, args) => {
// code to get data from db
return Book.find({
authorId: parent.id
});
},
},
}),
});
module.exports = AuthorType;
// This is here to prevent circular dependencies problem which will lead to the formation of infinite loop
const BookType = require("./BookType");
Also, it is better to have a types/index.js which will act as a handler for import/exports. You export every type to index.js and take whatever you want from it anywhere. This saves you from a lot of messy code because now you can do something like this:
const { BookType, AuthorType, OtherType } = require("../types/index");

GraphQL mutation structure

I am trying to create a Node.js graphql server in Typescript. I am using Express and express-graphql. I have some issues with how to structure my mutation when I want to create a new User.
My goal is to be able to use a mutation like this:
mutation {
user {
create(
data: {
name: "Foo Bar"
}
) {
id,
name
}
}
}
Here is my User types:
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLBoolean,
GraphQLString,
GraphQLInputObjectType
} from 'graphql';
export const UserType = new GraphQLObjectType({
name: 'User',
description: 'A user of the application',
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLString),
description: 'The id of the user',
},
name: {
type: new GraphQLNonNull(GraphQLString),
description: 'The name of the user',
}
})
});
export const UserInputType = new GraphQLInputObjectType({
name: 'UserInputType',
description: 'User payload definition',
fields: () => ({
name: { type: new GraphQLNonNull(GraphQLString) }
})
});
Here is my attempt at defining the mutation on the server:
// ../user/user-mutations.ts
export const userMutations = {
user: {
type: new GraphQLObjectType({
name: 'CreateUser',
fields: {
create: {
type: UserType,
args: {
data: {
type: new GraphQLNonNull(UserInputType),
}
},
resolve: async (rootValue, { data }) => {
return Object.assign(data, {
id: '123'
});
}
}
}
})
}
};
My errors/output:
{
"errors": [
{
"message": "Cannot convert undefined or null to object",
"locations": [
{
"line": 36,
"column": 3
}
],
"path": [
"user"
]
}
],
"data": {
"user": null
}
}
Question 1: Is this way of structuring a mutation not optimal? Should I rather do something like:
mutation {
createUser(
name: "Foo Bar"
) {
id,
name
}
}
Question 2: If my first structure is fine, how can I fix the structure of my mutation on the server to create my user and return the values requested?
Edit: Here is my top level schema:
import { userQueries } from '../user/user-queries';
export const queries = {
...userQueries
};
import { userMutations } from '../user/user-mutations';
export const mutations = {
...userMutations
};
const rootQuery = new GraphQLObjectType({
name: 'RootQuery',
fields: queries
});
const rootMutation = new GraphQLObjectType({
name: 'RootMutation',
fields: mutations
});
export const schema = new GraphQLSchema({
query: rootQuery,
mutation: rootMutation
});

Issue in querying graphQL relay calls (.then is not a function)

I have a graphql server running which I am using for query one of the object that gets instantiated when server starts (kind of in-memory db). Here company object is created every time schema is loaded or say server is started which I am using in QueryType object to resolve.
Here is the graphQL Schema
const {
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLInt,
GraphQLList
} = require('graphql');
const {
connectionDefinitions,
connectionArgs,
connectionFromArray,
connectionFromPromisedArray
} = require('graphql-relay');
//**************************** In-Memory Data ********************************//
var company = {
id:'123456',
customFieldDefinitions:[
{
name: 'cfm1',
id: '123'
},
{
name: 'cfm2',
id: '1234'
}
]
};
//**************************** In-Memory Code Ends *********************************//
const CustomFieldDefinitionType = new GraphQLObjectType({
name: 'Common_CustomFieldDefinitionsConnection',
fields: {
id: {
type: GraphQLString,
resolve: (cfd) => cfd.id
},
name: {
type: GraphQLString,
resolve: (cfd) => cfd.name
}
}
});
const { connectionType: CustomFieldDefinitionConnection } =
connectionDefinitions({
name: 'Common_CustomFieldDefinition',
nodeType: CustomFieldDefinitionType
});
const CompanyType = new GraphQLObjectType({
name: 'Company',
fields: {
id: {
type: GraphQLString,
resolve: (obj) => obj.id
},
customFieldDefinitions: {
type: CustomFieldDefinitionConnection,
description: 'A list of Custom Fields',
args: connectionArgs,
resolve: (obj, args) => connectionFromPromisedArray(obj.customFieldDefinitions, args)
}
}
});
const QueryType = new GraphQLObjectType({
name: 'Query',
fields: {
company: {
args: {
id: { type: GraphQLString },
},
type: CompanyType,
resolve: (_, args) => company
}
}
});
const mySchema = new GraphQLSchema({
query: QueryType
});
module.exports = mySchema;
Now when I try to query as below on graphiQL editor
query{
company{
customFieldDefinitions {
edges {
node {
id
}
}
}
}
}
I get error as below.
{
"data": {
"company": {
"customFieldDefinitions": null
}
},
"errors": [
{
"message": "dataPromise.then is not a function",
"locations": [
{
"line": 3,
"column": 5
}
],
"path": [
"company",
"customFieldDefinitions"
]
}
]
}
How can I identify the problem?
It looks like the error is with this line:
resolve: (obj, args) => connectionFromPromisedArray(obj.customFieldDefinitions, args)
The problem is that the connectionFromPromisedArray function imported from graphql-relay expects a promise that returns an array and obj.customFieldDefinitions is a normal array. Just to trace it back, obj is the parent element of that resolver which in this case is what you return from the company resolver on the Query type.
To fix it, either change connectionFromPromisedArray to connectionFromArray or change your company object to this:
var company = {
id:'123456',
customFieldDefinitions: Promise.resolve([
{
name: 'cfm1',
id: '123'
},
{
name: 'cfm2',
id: '1234'
}
])
};

GraphQL: How do you pass args to to sub objects

I am using GraphQL to query an object that will be composed from about 15 different REST calls. This is my root query in which I pass in in the ID from the query. This works fine for the main student object that resolves correctly. However, I need to figure out how to pass the ID down to the address resolver. I tried adding args to the address object but I get an error that indicates that the args are not passed down from the Student object. So my question is: How do I pass arguments from the client query to sub objects in a GraphQL server?
let rootQuery = new GraphQLObjectType({
name: 'Query',
description: `The root query`,
fields: () => ({
Student : {
type: Student ,
args: {
id: {
name: 'id',
type: new GraphQLNonNull(GraphQLString)
}
},
resolve: (obj, args, ast) => {
return Resolver(args.id).Student();
}
}
})
});
export default rootQuery;
This is my primary student object that I link the other objects. In this case I have attached the ADDRESS object.
import {
GraphQLInt,
GraphQLObjectType,
GraphQLString,
GraphQLNonNull,
GraphQLList
} from 'graphql';
import Resolver from '../../resolver.js'
import iAddressType from './address.js'
let Student = new GraphQLObjectType({
name: 'STUDENT',
fields: () => ({
SCHOOLCODE: { type: GraphQLString },
LASTNAME: { type: GraphQLString },
ACCOUNTID: { type: GraphQLInt },
ALIENIDNUMBER: { type: GraphQLInt },
MIDDLEINITIAL: { type: GraphQLString },
DATELASTCHANGED: { type: GraphQLString },
ENROLLDATE: { type: GraphQLString },
FIRSTNAME: { type: GraphQLString },
DRIVERSLICENSESTATE: { type: GraphQLString },
ENROLLMENTSOURCE: { type: GraphQLString },
ADDRESSES: {
type: new GraphQLList(Address),
resolve(obj, args, ast){
return Resolver(args.id).Address();
}}
})
});
Here is my address object that is resolved by a second REST call:
let Address = new GraphQLObjectType({
name: 'ADDRESS',
fields: () => ({
ACTIVE: { type: GraphQLString },
ADDRESS1: { type: GraphQLString },
ADDRESS2: { type: GraphQLString },
ADDRESS3: { type: GraphQLString },
CAMPAIGN: { type: GraphQLString },
CITY: { type: GraphQLString },
STATE: { type: GraphQLString },
STATUS: { type: GraphQLString },
TIMECREATED: { type: GraphQLString },
TYPE: { type: GraphQLString },
ZIP: { type: GraphQLString },
})
});
export default Address;
These are my resolver
var Resolver = (id) => {
var options = {
hostname: "myhostname",
port: 4000
};
var GetPromise = (options, id, path) => {
return new Promise((resolve, reject) => {
http.get(options, (response) => {
var completeResponse = '';
response.on('data', (chunk) => {
completeResponse += chunk;
});
response.on('end', () => {
parser.parseString(completeResponse, (err, result) => {
let pathElements = path.split('.');
resolve(result[pathElements[0]][pathElements[1]]);
});
});
}).on('error', (e) => { });
});
};
let Student= () => {
options.path = '/Student/' + id;
return GetPromise(options, id, 'GetStudentResult.StudentINFO');
}
let Address= () => {
options.path = '/Address/' + id + '/All';
return GetPromise(options, id, 'getAddressResult.ADDRESS');
};
return {
Student,
Address
};
}
export default Resolver;
ADDRESSES: {
type: new GraphQLList(Address),
resolve(obj, args, ast){
return Resolver(args.id).Address();
}
}
args passed to ADDRESSES are arguments passed to ADDRESSES field at query time. In the resolve method, obj should be the student object and if you have an id property on it, all you need to do is: return Resolver(obj.id).Address();.

Resources