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");
Related
I am learning graph via a tutorial for work. I think the tutorial is a little out of date but its got some good info so I am trying to keep with it. However my queries dont seem to work the way the video is showing them. Can someone take a look and tell me whats wrong here?
Here is my Schema:
const graphql = require('graphql');
const _ = require('lodash')
const {
GraphQLObjectType,
GraphQLString,
GraphQLInt,
GraphQLSchema
} = graphql
const users = [
{ id: '23', firstName: 'Matt', age: 33 },
{ id: '47', firstName: 'Alexis', age: 28 }
]
const UserType = new GraphQLObjectType({
name: 'User',
fields: {
id: { type: GraphQLString } ,
firstName: { type: GraphQLString },
age: { type: GraphQLInt }
}
});
const RootQuery = new GraphQLObjectType({
name: "RootQueryType",
fields: {
user: {
type: UserType,
args: { id: { type: GraphQLString } },
resovle(parentValue, args) {
return _.find(users, { id: args.id } );
}
}
}
});
module.exports = new GraphQLSchema({
query: RootQuery
});
Here is my query in GraphiQL:
To cover all bases, here is the server.js
const express = require('express');
const expressGraphQL = require('express-graphql').graphqlHTTP;
const schema = require('./schema/schema');
const app = express();
app.use('/graphql', expressGraphQL({
schema,
graphiql: true
}));
app.listen(4000, () => {
console.log('Listening');
});
I've just started to learn GraphQL recently and have decided to implement it in a react based polling app where users can create and vote on polls.
I've created a mongoose model that looks like this https://github.com/luckyrose89/Voting-App/blob/master/backend/models/poll.js.
I'm facing an issue with adding upvotes to a poll option while writing Graphql mutations. So far my schema looks like this:
const AnswerType = new GraphQLObjectType({
name: "Answer",
fields: () => ({
id: { type: GraphQLID },
option: { type: GraphQLString },
votes: { type: GraphQLInt }
})
});
const QuestionType = new GraphQLObjectType({
name: "Question",
fields: () => ({
id: { type: new GraphQLNonNull(GraphQLID) },
question: { type: GraphQLString },
answer: { type: GraphQLList(AnswerType) }
})
});
const AnswerTypeInput = new GraphQLInputObjectType({
name: "AnswerInput",
fields: () => ({
option: { type: GraphQLString },
votes: { type: GraphQLInt }
})
});
const QuestionTypeInput = new GraphQLInputObjectType({
name: "QuestionInput",
fields: () => ({
question: { type: new GraphQLNonNull(GraphQLString) },
answer: { type: new GraphQLNonNull(GraphQLList(AnswerTypeInput)) }
})
});
const Mutation = new GraphQLObjectType({
name: "Mutation",
fields: {
addPoll: {
\\\\ code here
},
deletePoll: {
\\\\\ code here
},
upvotePoll: {
type: QuestionType,
args: { id: { type: new GraphQLNonNull(GraphQLID) } },
resolve(parent, args) {}
}
}
});
So I've defined my types and I can add and delete polls and access a single poll(I've skipped my queries section here). But I don't understand how to access a single poll's AnswerType object without retrieving unnecessary data and use it to write my upVote mutation.
I hope someone can guide me with this
I have 2 custom graphql types, entityType and containerType, where 1 container can have multiple entities.
So, I am binding all the entities with their respective containers via this code:
const graphql = require('graphql')
const _ = require('lodash')
const UserType = require('./userSchema')
const ContainerType = require('./containerSchema')
const Container = require('../models/container')
const Entity = require('../models/entity')
const {
GraphQLObjectType,
GraphQLString,
GraphQLBoolean,
GraphQLSchema,
GraphQLInt,
GraphQLID,
GraphQLList,
GraphQLNonNull,
GraphQLUnionType
} = graphql
const EntityType = new GraphQLObjectType({
name: "Entity",
fields: () => ({
name: { type: new GraphQLNonNull(GraphQLString) },
container: {
type: ContainerType,
resolve: function(parent, args) {
return Container.findById(parent.containerId)
}
},
type: { type: new GraphQLNonNull(GraphQLString) },
detail: { type: new GraphQLNonNull(GraphQLString) },
start: { type: GraphQLString },
end: { type: GraphQLString }
})
})
module.exports = { EntityType }
I am quite sure that containerType is working, because I am using it is being used on other places and is working well. Here is the code for Container type:
const graphql = require('graphql')
const _ = require('lodash')
const UserType = require('./userSchema')
const { EntityType } = require('./entitySchema')
const User = require('../models/user')
const Container = require('../models/container')
const Entity = require('../models/entity')
const {
GraphQLObjectType,
GraphQLString,
GraphQLBoolean,
GraphQLSchema,
GraphQLInt,
GraphQLID,
GraphQLList,
GraphQLNonNull
} = graphql
const ContainerType = new GraphQLObjectType ({
name: 'Container',
fields: () => ({
id: { type: GraphQLID },
name: { type: GraphQLString },
user: {
type: UserType,
resolve: function (parent, args) {
return User.findById(parent.userId)
}
},
parent: {
type: ContainerType,
resolve: function (parent, args) {
return Container.findById(parent.parentContainer)
}
},
detail: { type: GraphQLString },
start: { type: GraphQLString },
end: { type: GraphQLString },
createdAt: { type: GraphQLString },
category: { type: GraphQLString },
status: { type: GraphQLString },
entities: {
type: GraphQLList(EntityType),
resolve: async function(parent, args) {
return await Entity.find({ containerId: parent.id })
}
}
})
})
module.exports = ContainerType
no error is shown on the terminal, but the Graphiql console shows following error when loading up:
{
"errors": [
{
"message": "The type of Entity.container must be Output Type but got: {}."
},
{
"message": "Expected GraphQL named type but got: {}."
}
]
}
I figured out that the problem was with something called "module cycles". this post was very helpful to resolve it.
Finally, I ended up adding both EntitySchema and ContainerSchema in a single file, here is the code:
const graphql = require('graphql')
const _ = require('lodash')
const UserType = require('./userSchema')
const User = require('../models/user')
const Container = require('../models/container')
const Entity = require('../models/entity')
const {
GraphQLObjectType,
GraphQLString,
GraphQLBoolean,
GraphQLInt,
GraphQLID,
GraphQLList,
GraphQLNonNull
} = graphql
const EntityType = new GraphQLObjectType ({
name: "Entity",
fields: () => ({
name: { type: new GraphQLNonNull(GraphQLString) },
container: {
type: ContainerType,
resolve: (parent, args) => {
return Container.findById(parent.containerId)
}
},
type: { type: new GraphQLNonNull(GraphQLString) },
detail: { type: new GraphQLNonNull(GraphQLString) },
start: { type: GraphQLString },
end: { type: GraphQLString }
})
})
const ContainerType = new GraphQLObjectType ({
name: 'Container',
fields: () => ({
id: { type: GraphQLID },
name: { type: GraphQLString },
user: {
type: UserType,
resolve: function (parent, args) {
return User.findById(parent.userId)
}
},
parent: {
type: ContainerType,
resolve: function (parent, args) {
return Container.findById(parent.parentContainer)
}
},
detail: { type: GraphQLString },
start: { type: GraphQLString },
end: { type: GraphQLString },
createdAt: { type: GraphQLString },
category: { type: GraphQLString },
status: { type: GraphQLString },
entities: {
type: GraphQLList(EntityType),
resolve: async function(parent, args) {
return await Entity.find({ containerId: parent.id })
}
}
})
})
module.exports = {
EntityType,
ContainerType
}
This code was working until I added the Resources part into the code. It is similar to the other two so I am not sure why isn't it working.
Thanks in advance
Update:-
After using the debugger, I came to understand that the problem is in the RootQueryType.js and files associated with it as the error pops up when I am trying to export the schema and exactly at the query:RootQueryType place but still can't pinpoint at the error.
Update:-
I have put the schema.js file too in the end
resourceType.js
const graphql = require("graphql");
const UserType = require("./userType");
const User = require("../models/User");
const Project = require("../models/Project");
const Resource = require("../models/Resource");
const ProjectType = require("./projectType");
const {
GraphQLObjectType,
GraphQLString,
GraphQLList,
} = graphql;
const ResourceType = new GraphQLObjectType({
name: "ResourceType",
fields: () => ({
id: { type: GraphQLString },
title: { type: GraphQLString },
url: { type: GraphQLString },
project:{
type:ProjectType,
resolve(parentValues,args){
return Project.findById(parentValues.id).populate("resources")
}
}
})
});
module.exports=ResourceType;
RootQueryType.js
const mongoose = require('mongoose');
const graphql = require('graphql');
const { GraphQLObjectType, GraphQLList, GraphQLID, GraphQLNonNull } = graphql;
const ProjectType = require('./../types/projectType');
const UserType = require('./../types/userType');
const Project=require("../models/Project");
const User=require("../models/User");
const RootQuery=new GraphQLObjectType({
name:"RootQueryType",
fields: () => ({
projects:{
type:new GraphQLList(ProjectType),
resolve(parentValues,args,request){
return Project.find().populate("contributors").populate("resources");
}
},
project:{
type:ProjectType,
args:{id:{type:new GraphQLNonNull(GraphQLID)}},
resolve(parentValue,args,request){
return Project.findById(args.id).populate("contributors").populate("resources");
}
},
users:{
type:new GraphQLList(UserType),
resolve(parentValues,args,request){
return User.find().populate("projects");
}
},
user:{
type:UserType,
args:{id:{type:new GraphQLNonNull(GraphQLID)}},
resolve(parentValue,args,request){
return User.findById(args.id).populate("projects")
}
},
})
})
module.exports = RootQuery;
Mutation.js
const mongoose = require("mongoose");
const ProjectType = require("./../types/projectType");
const UserType = require("./../types/userType");
const graphql = require("graphql");
const {
GraphQLObjectType,
GraphQLList,
GraphQLID,
GraphQLNonNull,
GraphQLString
} = graphql;
const Project = require("../models/Project");
const User = require("../models/User");
const mutation = new GraphQLObjectType({
name: "mutation",
fields: () => ({
addUser: {
type: UserType,
args: {
name: { type: new GraphQLNonNull(GraphQLString) },
username: { type: new GraphQLNonNull(GraphQLString) },
password: { type: new GraphQLNonNull(GraphQLString) },
email: { type: new GraphQLNonNull(GraphQLString) },
githubProfile: { type: new GraphQLNonNull(GraphQLString) }
},
resolve(parentValues, args, request) {
return User.create(args);
}
},
addProject: {
type: ProjectType,
args: {
name: { type: new GraphQLNonNull(GraphQLString) },
description: { type: new GraphQLNonNull(GraphQLString) },
image: { type:GraphQLString },
// contributor:{ type: new GraphQLNonNull(new GraphQLList(GraphQLID))
contributor:{ type: new GraphQLNonNull(GraphQLID)
},
},
resolve(parentValues, args, request) {
return Project.create(args);
}
}
})
});
module.exports = mutation;
I am positive it isnt the problem with other types because they were working earlier and I only added the .populate("resources") property to the resolve function of both of them. But just in case I am adding the code for them too.
userType.js
const graphql = require("graphql");
const ProjectType = require("./projectType");
const { GraphQLObjectType, GraphQLString, GraphQLList } = graphql;
const UserType = new GraphQLObjectType({
name: "User",
fields: () => ({
id: { type: GraphQLString },
name: { type: GraphQLString },
githubProfile: { type: GraphQLString },
username: { type: GraphQLString },
password: { type: GraphQLString },
email: { type: GraphQLString },
projects: {
type: new GraphQLList(ProjectType),
resolve(parentValues, args) {
return User.findById(parentValues.id).populate("projects");
}
}
})
});
module.exports = UserType;
and the other is
projectType.js
const graphql = require("graphql");
const UserType = require("./userType");
const ResourceType = require("./resourceType");
const User = require("../models/User");
const Project = require("../models/Project");
const {
GraphQLObjectType,
GraphQLString,
GraphQLList,
} = graphql;
const ProjectType = new GraphQLObjectType({
name: "ProjectType",
fields: () => ({
id: { type: GraphQLString },
name: { type: GraphQLString },
description: { type: GraphQLString },
image: { type: GraphQLString },
contributors: {
type: new GraphQLList(UserType),
resolve(parentValues, args, request) {
return Project.findContributors(parentValues.id);
}
},
resources:{
type: new GraphQLList(ResourceType),
resolve(parentValues, args, request) {
return Project.findResources(parentValues.id);
}
}
})
});
module.exports=ProjectType;
schema.js
const graphql = require("graphql");
const RootQuery = require("./RootQueryType");
const Mutation = require("./Mutation");
const { GraphQLSchema } = graphql;
console.log(RootQuery,Mutation);
module.exports = new GraphQLSchema({
query: RootQuery,
mutation: Mutation
});
Update:- I have removed the resources files and I am still getting the same error so it must be between user and project types
Update:-I finally tracked down the bug to being in the type files of both project and user type and it is being caused by the new GraphQLList, I still havent solved the error but removing it seems to make the error go away, no idea why.
Finally solved the problem, it was as #cito said, because of circular dependencies that was the cause of the error, that is as my UserType is dependent on ProjectType and likewise and hence I was getting this error, this was solved by
const graphql = require("graphql");
const User = require("../models/User");
const Project = require("../models/Project");
const {
GraphQLObjectType,
GraphQLString,
GraphQLList,
} = graphql;
const ProjectType = new GraphQLObjectType({
name: "ProjectType",
fields: () => ({
id: { type: GraphQLString },
name: { type: GraphQLString },
description: { type: GraphQLString },
image: { type: GraphQLString },
contributors: {
type: new GraphQLList(UserType),
resolve(parentValues, args, request) {
return Project.findContributors(parentValues.id);
}
},
resources:{
type: new GraphQLList(ResourceType),
resolve(parentValues, args, request) {
return Project.findResources(parentValues.id);
}
}
})
});
module.exports=ProjectType;
// This is here to prevent circular dependencies problem which will lead to the formation of infinite loop
const UserType = require("./userType");
const ResourceType = require("./resourceType");
that is requiring the files at the bottom
Your problem is the cyclic dependency between the userType and projectType module. Therefore the const value is still an empty object when it is passed to GraphQLList.
As a solution, you can move all the types into one module. Or, when exporting your classes, set them as properties of module.exports. This will work since you defined the fields properly as thunks.
By the way, you don't need the new when creating a GraphQLList or GraphQLNonNull wrapper. But that's not why you're getting the error.
Another way to go is:
resources:{
// require directly without creating a new variable
type: new GraphQLList(require("./resourceType")),
resolve(parentValues, args, request) {
return Project.findResources(parentValues.id);
}
}
I solved the problem. Just have both objectType in the same file.
I works
All the examples I find have a query top level object, then a list of queries, which then return types to go deeper.
Since I have a large number of queries, I would like to group them up, this is what I tried:
const AppType = new GraphQLObjectType({
name: 'App',
description: 'Generic App Details',
fields: () => ({
name: { type: GraphQLString },
appId: { type: GraphQLInt },
}),
});
const MyFirstQuery = {
type: new GraphQLList(AppType),
args: {
appId: { type: GraphQLInt },
},
resolve: (root, args) => fetchApp(args.appId),
};
/* snip MySecondQuery, MyThirdQuery, MyFourthQuery */
const MyFirstGroupQuery = new GraphQLObjectType({
name: 'myFirstGroup',
description: 'the first group of queries',
fields: () => ({
myFirstQuery: MyFirstQuery,
mySecondQuery: MySecondQuery,
myThirdQuery: MyThirdQuery,
myFourthQuery: MyFourthQuery,
}),
});
/* snip MySecondGroupQuery, MyThirdGroupQuery and their types */
const QueryType = new GraphQLObjectType({
name: 'query',
description: 'read-only query',
fields: () => ({
myFirstGroup: MyFirstGroupQuery,
mySecondGroup: MySecondGroupQuery,
myThirdGroup: MyThirdGroupQuery,
}),
});
const Schema = new GraphQLSchema({
query: QueryType,
});
Why can't I make MyFirstGroupQuery like I did QueryType to make more nesting levels? The code works fine if I put all queries in QueryType, but my MyFirstGroupQuery produces errors:
Error: query.myFirstGroup field type must be Output Type but got: undefined.
How do I accomplish what I want? I really don't want to just prefix all my queries.
Error query.myFirstGroup field type must be Output Type but got: undefined. means that you haven't provided the type for myFirstGroup
you have to provide the type using type field
myFirstGroup: {
type: MyFirstGroupQuery,
resolve: () => MyFirstGroupQuery,
},
and if the type MyFirstGroupQuery each field must have the type defined such as GraphQLInt, GraphQLString, GraphQLID even if it's tht customtype like MyFirstGroupQuery
In GraphQLSchema constructor function you provide your RootQuery which is QueryType, It's a GraphQLSchema it only accepts the rootQuery with the GraphQLObjectType whose fields must have the type defined
GraphQL is strictly type based, every field you declared must have the type defined
https://github.com/graphql/graphql-js
https://github.com/graphql/graphql-js/blob/master/src/type/schema.js#L32
const {
GraphQLID,
GraphQLInt,
GraphQLString,
GraphQLObjectType,
GraphQLSchema,
GraphQLList,
} = require('graphql');
const AppType = new GraphQLObjectType({
name: 'App',
description: 'Generic App Details',
fields: () => ({
name: { type: GraphQLString },
appId: { type: GraphQLInt },
}),
});
// const MyFirstQuery = {
// type: new GraphQLList(AppType),
// args: {
// appId: { type: GraphQLInt },
// },
// resolve: (root, args) => fetchApp(args.appId),
// };
const myFirstQuery = new GraphQLObjectType({
name: 'First',
fields: () => ({
app: {
type: new GraphQLList(AppType),
args: {
appId: { type: GraphQLInt },
},
resolve: (root, args) => fetchApp(args.appId),
},
}),
});
/* snip MySecondQuery, MyThirdQuery, MyFourthQuery */
const MyFirstGroupQuery = new GraphQLObjectType({
name: 'myFirstGroup',
description: 'the first group of queries',
fields: () => ({
myFirstQuery: {
type: myFirstQuery,
resolve: () => [], // promise
},
// mySecondQuery: {
// type: MySecondQuery,
// resolve: () => //data
// }
// myThirdQuery: {
// type: MyThirdQuery,
// resolve: () => // data
// }
// myFourthQuery: {
// type: MyFourthQuery,
// resolve: () => //data
// }
}),
});
/* snip MySecondGroupQuery, MyThirdGroupQuery and their types */
const QueryType = new GraphQLObjectType({
name: 'query',
description: 'read-only query',
fields: () => ({
myFirstGroup: {
type: MyFirstGroupQuery,
resolve: () => MyFirstGroupQuery,
},
// mySecondGroup: {
// type: MySecondGroupQuery,
// resolve: MySecondGroupQuery
// }
// myThirdGroup: {
// type: MyThirdGroupQuery,
// resolve: MyThirdGroupQuery
// }
}),
});
const Schema = new GraphQLSchema({
query: QueryType,
});
module.exports = Schema;
GraphiQL