How to solve GraphQL circular references? - node.js

I'm trying to separate out my types into individual files. By doing so I've found that any type definition that references the usertype I get the below error.
User:
const {
GraphQLObjectType,
GraphQLList,
GraphQLString,
GraphQLBoolean,
GraphQLID,
} = require("graphql");
const UserType = new GraphQLObjectType({
name: "User",
fields: () => ({
_id: { type: GraphQLID },
name: { type: GraphQLString },
password: { type: GraphQLString },
email: { type: GraphQLString },
active: { type: GraphQLBoolean },
emailConfirmed: { type: GraphQLBoolean },
licenses: {
type: new GraphQLList(LicenseType),
async resolve(parent, args) {
return await LicenseController.getUserLicenses({ id: parent._id });
},
},
services: {
type: new GraphQLList(ServiceType),
async resolve(parent, args) {
return await ServiceController.getUserServices({ id: parent._id });
},
},
}),
});
const LicenseType = require("../types/licenseType");
const ServiceType = require("../types/serviceType");
const LicenseController = require("../../controllers/licenseController");
const ServiceController = require("../../controllers/serviceController");
module.exports = UserType;
License:
const {
GraphQLObjectType,
GraphQLInt,
GraphQLString,
GraphQLBoolean,
GraphQLID,
} = require("graphql");
const UserType = require("../types/userType");
const UserController = require("../../controllers/userController");
const LicenseType = new GraphQLObjectType({
name: "License",
fields: () => ({
_id: { type: GraphQLID },
token: { type: GraphQLString },
creationDate: { type: GraphQLString },
expirationDate: { type: GraphQLString },
btcAddress: { type: GraphQLString },
sessions: { type: GraphQLInt },
active: { type: GraphQLBoolean },
user_id: { type: GraphQLID },
user: {
type: UserType,
async resolve(parent, args) {
return await UserController.getSingleUser({ id: parent.user_id });
},
},
}),
});
module.exports = LicenseType;
Error:
Error: One of the provided types for building the Schema is missing a name.
I've tried moving the type/controller definitions above and below the type definitions with no change. How would I provide the user data from the license type?

You can move the require calls inside the fields function -- this way they will not be evaluated until the function is actually called when your schema is constructed.
const LicenseType = new GraphQLObjectType({
name: "License",
fields: () => {
const UserType = require("../types/userType");
const UserController = require("../../controllers/userController");
return {
_id: { type: GraphQLID },
token: { type: GraphQLString },
creationDate: { type: GraphQLString },
expirationDate: { type: GraphQLString },
btcAddress: { type: GraphQLString },
sessions: { type: GraphQLInt },
active: { type: GraphQLBoolean },
user_id: { type: GraphQLID },
user: {
type: UserType,
async resolve(parent, args) {
return await UserController.getSingleUser({ id: parent.user_id });
},
},
}
},
});

Related

Cannot read properties of undefined (reading `fieldname`) GraphQL

i am working on a project based on GraphQL API with nodejs And mongoose
so i have this Model Below :
const mongoose = require('mongoose')
const BreakingNewsSchema = new mongoose.Schema({
MainInfo:{
content:{type:String,required:true},
ParentCategory:{
type: mongoose.Schema.Types.ObjectId,
ref: 'ArticleCategory',
required: true
},
category:{
type: mongoose.Schema.Types.ObjectId,
ref: 'ArticleCategory',
required: true
},
},
options:{
clickable:{type:Boolean,required:true},
link:{type:String,required:false},
isActive:{type:Boolean,required:true,default:true}
},
infos:{
createdAt: { type: String, required: true},
updateDate: {type: String, required: false},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true
},
updatedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: false,
}}} ,{
timestamps: true})
module.exports = mongoose.model("BreakingNews", BreakingNewsSchema)
and i have this GraphQL Schema here :
const BreakingType = new GraphQLObjectType({
name: "BreakingNews",
fields: () => ({
id: {
type: GraphQLID
},
MainInfo: {
type: new GraphQLObjectType({
name: "BreakingMainInfo",
fields: () => ({
content: {
type: GraphQLString
},
ParentCategory: {
type: CategoryType,
resolve(parent, args) {
return Category.findById(parent.MainInfo.parentCategory)
}
},
category: {
type: CategoryType,
resolve(parent, args) {
return Category.findById(parent.MainInfo.category)
}
}
})
})
},
options: {
type: new GraphQLObjectType({
name: "BreakingOptions",
fields: () => ({
clickable: {
type: GraphQLBoolean
},
link: {
type: GraphQLString
},
isActive: {
type: GraphQLBoolean
}
})
})
},
})})
For the breakingNews Collection in Mongodb
and below i have the Category Collection ... so here is the Category Model :
const CategorySchema = new mongoose.Schema({
MainInfo:{
title: {
type: String,
required: true,
unique: true
},
slug: {
type: String,
required: false,
unique: true
},
},
seo:{
metaDescription: { type: String, required: false },
metaKeywords: [{
type: String,
required: false
}]
},
options:{
isParent:{type:Boolean,required:true},
isEnded:{type:Boolean,required:true},
parentCategory: {
type: mongoose.Schema.Types.ObjectId,
ref: "ArticleCategory",
required: false,
set: v => v === '' ? null : v
}
},
info:{
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true
},
updatedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: false
},
articleCount:{type:Number,required:false},
oldId: { type: String, required: false }
}}, {
timestamps: true})
module.exports = mongoose.model("ArticleCategory", CategorySchema)
And finally i have the ArticleCategory Schema for GraphQL :
const CategoryType = new GraphQLObjectType({
name: "ArticleCategory",
fields: () => ({
id: {
type: GraphQLID
},
MainInfo: {
type: new GraphQLObjectType({
name: "ArticleCategoryMainInfo",
fields: () => ({
title: {
type: GraphQLString
},
slug: {
type: GraphQLString
}
})
})
},
seo: {
type: new GraphQLObjectType({
name: "ArticleCategorySeo",
fields: () => ({
metaDescription: {
type: GraphQLString
},
metaKeywords: {
type: new GraphQLList(GraphQLString)
}
})
})
},
options: {
type: new GraphQLObjectType({
name: "ArticleCategoryOptions",
fields: () => ({
isParent: {
type: GraphQLBoolean
},
isEnded: {
type: GraphQLBoolean
},
parentCategory: {
type: CategoryType,
resolve(parent, args) {
return Category.findById(parent.options.parentCategory)
}
}
})
})
}
})})
The problem is when i try to execute this query on graphQL:
query{
ActiveBreakingNews{
id
MainInfo{
content
ParentCategory {
id
}
category{
id
}
}
}
}
I get this error Cannot read properties of undefined (reading 'category') or Cannot read properties of undefined (reading 'category')
i find out its a problem find resolve function in the schema ... but i don't know what the wrong and what should i do to fix it ... please Help and thanks in advance

I have this error Unauthorised admin to execute command mongoose + Graphql

I used mongoose and Graphql to send my queries to the database but for some reason it doesn't let me create documents. I have tried creating a new user with full admin privileges it hasn't worked I tried changing the default user password but it didn't work.
I rechecked my mongoose model no errors so what might be the problem.
FYI the problem arose with the return (author.save()) and the database connects normally
Author Model
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const authorSchema = new Schema({
name: String,
age: Number
});
module.exports = mongoose.model('Author', authorSchema);
schema.js
const graphql = require('graphql');
const Book = require('../models/book');
const Author = require('../models/Author');
const _ = require('lodash');
const {
GraphQLObjectType,
GraphQLString,
GraphQLSchema,
GraphQLID,
GraphQLInt,
GraphQLList
} = graphql;
const BookType = new GraphQLObjectType({
name: 'Book',
fields: ( ) => ({
id: { type: GraphQLID },
name: { type: GraphQLString },
genre: { type: GraphQLString },
author: {
type: AuthorType,
resolve(parent, args){
//return _.find(authors, { id: 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){
//return _.filter(books, { authorId: parent.id });
}
}
})
});
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
book: {
type: BookType,
args: { id: { type: GraphQLID } },
resolve(parent, args){
//return _.find(books, { id: args.id });
}
},
author: {
type: AuthorType,
args: { id: { type: GraphQLID } },
resolve(parent, args){
//return _.find(authors, { id: args.id });
}
},
books: {
type: new GraphQLList(BookType),
resolve(parent, args){
//return books;
}
},
authors: {
type: new GraphQLList(AuthorType),
resolve(parent, args){
//return authors;
}
}
}
});
const Mutation = new GraphQLObjectType({
name: 'Mutation',
fields: {
addAuthor: {
type: AuthorType,
args: {
name: { type: GraphQLString },
age: { type: GraphQLInt }
},
resolve(parent, args){
let author = new Author({
name: args.name,
age: args.age
});
return (author.save())
}
}
}
});
module.exports = new GraphQLSchema({
query: RootQuery,
mutation: Mutation
})
;
error message
(node:31482) MongoError: (Unauthorized) not authorized on admin to execute command {
insert: "authors", documents: [[{name gyfdgyiszukjfheusdzyih} {age 88} {_id
ObjectID("60af9c682215ea7afad86f4c")} {__v 0}]], ordered: false, writeConcern: { w:
"majority" }
Found this issue, after trying practice by GraphQL tutorial on Youtube.
To solve it, you need to update your mongoose model to the last version.

Why does the child's resolve function not contain the result of the parent's resolve function?

I've tried to isolate this example and I hope it's ok. I know, this isn't great code, but I hope you get the drift.
For the time being the resolvers return a static result object.
Here's my problem:
The result of the company resolve function should be passed on the user's resolve function. But that ain't happenin' and I wonder what I am missing.
const GraphQL = require('graphql');
const UserType = new GraphQL.GraphQLObjectType({
name: 'User',
fields: {
givenName: { type: GraphQL.GraphQLString },
familyName: { type: GraphQL.GraphQLString },
city: { type: GraphQL.GraphQLString },
},
});
const CompanyType = new GraphQL.GraphQLObjectType({
name: 'Company',
fields: {
legalName: { type: GraphQL.GraphQLString },
city: { type: GraphQL.GraphQLString },
employees: { type: new GraphQL.GraphQLList(UserType) },
},
});
const queryDef = new GraphQL.GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: UserType,
args: {
id: { type: GraphQL.GraphQLID },
givenName: { type: GraphQL.GraphQLString },
familyName: { type: GraphQL.GraphQLString },
city: { type: GraphQL.GraphQLString },
},
resolve: (parent, args, context, info) => {
console.log('parent should provide company object', parent);
// currentyl parent is undefined
return {
id: 10,
givenName: 'test',
};
},
},
company: {
type: CompanyType,
args: {
id: { type: GraphQL.GraphQLID },
},
resolve: (parent, args, context, info) => {
return {
id: 3,
legalName: 'legal test name',
city: 'company location',
};
},
},
},
});
const schema = new GraphQL.GraphQLSchema({ query: queryDef });
const companyQuery = `
{
company(id: 1) {
city
employees {
familyName
}
}
}`;
GraphQL.graphql(schema, companyQuery).then( (companyResult) => {
console.log(companyResult);
} ).catch( (err) => {
console.error(err);
});

How to refactor GraphQL schema using "graphql-compose-elasticsearch" to tranform GraphQL query into Elasticsearch query?

I was hosting my data using json server on localhost:3000(REST api) and using GraphQL to fetch data, and now I'd like to move data into a Elastic Search server, and I still want to use GraphQL as API gateway. I tried this graphql-compose-elasticsearch library to use GraphQL as a proxy for ElasticSearch.
https://github.com/graphql-compose/graphql-compose-elasticsearch
In my original GraphQL schema, I defined types, root query, resolvers. The code is like this:
const graphql = require('graphql');
const axios = require('axios');
const {
GraphQLObjectType,
GraphQLList,
GraphQLID,
GraphQLInt,
GraphQLString,
GraphQLBoolean,
GraphQLSchema,
GraphQLNonNull
} = graphql;
const RoomType = new GraphQLObjectType({
name: 'RoomType',
fields: () => ({
id: { type: GraphQLID },
roomName: { type: GraphQLString },
roomNumber: { type: GraphQLString },
floorId: { type: GraphQLInt },
hasImages: { type: GraphQLBoolean },
hasGLTF: { type: GraphQLBoolean },
hasPhotogrammetry: { type: GraphQLBoolean },
hasPointClouds: { type: GraphQLBoolean },
roomDescription: { type: GraphQLString },
floor: {
type: FloorType,
resolve(parentValue, args) {
return axios
.get(`http://localhost:3000/floors/${parentValue.floorId}`)
.then((resp) => resp.data);
}
},
assets: {
type: new GraphQLList(AssetType),
resolve(parentValue, args) {
return axios
.get(`http://localhost:3000/rooms/${parentValue.id}/assets`)
.then((resp) => resp.data);
}
}
})
});
const FloorType = new GraphQLObjectType({
name: 'FloorType',
fields: () => ({
id: { type: GraphQLID },
floorName: { type: GraphQLString },
floorDescription: { type: GraphQLString },
rooms: {
type: new GraphQLList(RoomType),
resolve(parentValue, args) {
return axios
.get(`http://localhost:3000/floors/${parentValue.id}/rooms`)
.then((resp) => resp.data);
}
}
})
});
const AssetType = new GraphQLObjectType({
name: 'AssetType',
fields: () => ({
id: { type: GraphQLID },
category: { type: GraphQLString },
assetName: { type: GraphQLString },
assetNumber: { type: GraphQLString },
roomId: { type: GraphQLString },
location: { type: GraphQLString },
isHeritageAsset: { type: GraphQLBoolean },
hasImages: { type: GraphQLBoolean },
hasGLTF: { type: GraphQLBoolean },
hasPhotogrammetry: { type: GraphQLBoolean },
hasPointClouds: { type: GraphQLBoolean },
assetDescription: { type: GraphQLString },
room: {
type: RoomType,
resolve(parentValue, args) {
return axios
.get(`http://localhost:3000/rooms/${parentValue.roomId}`)
.then((resp) => resp.data);
}
}
})
});
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
getRoom: {
type: RoomType,
args: { id: { type: new GraphQLNonNull(GraphQLID) } },
resolve(parentValue, { id }) {
return axios.get(`http://localhost:3000/rooms/${id}`).then((resp) => resp.data);
}
},
getFloor: {
type: FloorType,
args: { id: { type: new GraphQLNonNull(GraphQLID) } },
resolve(parentValue, { id }) {
return axios.get(`http://localhost:3000/floors/${id}`).then((resp) => resp.data); //to make it compatible between axios and graphql, a workaround
}
},
getAsset: {
type: AssetType,
args: { id: { type: new GraphQLNonNull(GraphQLID) } },
resolve(parentValue, { id }) {
return axios.get(`http://localhost:3000/assets/${id}`).then((resp) => resp.data); //to make it compatible between axios and graphql, a workaround
}
},
getAllRooms: {
type: new GraphQLList(RoomType),
resolve() {
return axios.get(`http://localhost:3000/rooms`).then((resp) => resp.data);
}
},
getAllAssets: {
type: new GraphQLList(AssetType),
resolve() {
return axios.get(`http://localhost:3000/assets`).then((resp) => resp.data);
}
},
getAllFloors: {
type: new GraphQLList(FloorType),
resolve() {
return axios.get(`http://localhost:3000/floors`).then((resp) => resp.data);
}
}
}
});
//expose this to the rest of the application
module.exports = new GraphQLSchema({
query: RootQuery
});
For Elasticsearch, the version is 7.0. I'm running on localhost:9200, and I have 3 indices, rooms, floors, and assets, and each of the index have a mapping. I tried to code like this:
const graphql = require('graphql');
const graphql_compose_elasticsearch = require('graphql-compose-elasticsearch');
const { elasticApiFieldConfig, composeWithElastic } = graphql_compose_elasticsearch;
const { GraphQLSchema, GraphQLObjectType } = graphql;
const elasticsearch = require('elasticsearch');
// Mapping obtained from ElasticSearch server
const floor_mapping = {
properties: {
floorId: {
type: 'text',
fields: {
keyword: {
type: 'keyword',
ignore_above: 256
}
}
},
hasGLTF: {
type: 'boolean'
},
hasImages: {
type: 'boolean'
},
hasPhotogrammetry: {
type: 'boolean'
},
hasPointClouds: {
type: 'boolean'
},
roomDescription: {
type: 'text',
fields: {
keyword: {
type: 'keyword',
ignore_above: 256
}
}
},
roomName: {
type: 'text',
fields: {
keyword: {
type: 'keyword',
ignore_above: 256
}
}
},
roomNumber: {
type: 'text',
fields: {
keyword: {
type: 'keyword',
ignore_above: 256
}
}
}
}
};
const room_mapping = {
//similar
};
const asset_mapping = {
//similar
};
const Room = composeWithElastic({
graphqlTypeName: 'RoomType',
elasticIndex: 'rooms',
elasticType: '_doc',
elasticMapping: room_mapping,
elasticClient: new elasticsearch.Client({
host: 'http://localhost:9200',
apiVersion: '7.0'
})
});
const Floor = composeWithElastic({
graphqlTypeName: 'FloorType',
elasticIndex: 'floors',
elasticType: '_doc',
elasticMapping: floor_mapping,
elasticClient: new elasticsearch.Client({
host: 'http://localhost:9200',
apiVersion: '7.0'
})
});
const Asset = composeWithElastic({
graphqlTypeName: 'AssetType',
elasticIndex: 'assets',
elasticType: '_doc',
elasticMapping: asset_mapping,
elasticClient: new elasticsearch.Client({
host: 'http://localhost:9200',
apiVersion: '7.0'
})
});
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
roomSearch: Room.getResolver('search').getFieldConfig(),
roomSearchConnection: Room.getResolver('searchConnection').getFieldConfig(),
elastic70: elasticApiFieldConfig({
host: 'http://localhost:9200',
apiVersion: '7.0'
})
}
})
});
module.default = { schema };
server.js is like this:
const graphqlHTTP = require('express-graphql');
const { schema } = require('./schema/schema_es');
// const schema = require('./schema/schema');
var cors = require('cors');
const server = require('express')();
//allow cross-origin
server.use(
'/',
cors(),
graphqlHTTP({
schema: schema,
graphiql: true,
formatError: (error) => ({
message: error.message,
stack: error.stack.split('\n')
})
})
);
const PORT = process.env.PORT || 6060;
server.listen(PORT, () => console.log(`Server started on port ${PORT}`));
I restarted the servers, and here is the error I got
invariant.esm.js:31 [GraphQL error]: Message: GraphQL middleware options must contain a schema., Location: undefined, Path: undefined
I have no idea how to how to transfrom graphql schema, rootquery..etc into ES query using this graphql-compose-elasticsearch, any help is appreciated!

GraphQL.js Node/Express: How to pass object as GraphQL query argument

My goal is to be able to pass an object as an argument in a GraphQL query.
Goal:
{
accounts (filter:
{"fieldName": "id",
"fieldValues":["123"],
"filterType":"in"}){
id
}
}
Error:
"message": "filterType fields must be an object with field names as keys or a function which returns such an object."
I've tried a few different approaches but this seems to be the closest to the potential solution.
Schema:
const filterType = new GraphQLObjectType ({
name: 'filterType',
fields: {
fieldName: { type: GraphQLString },
fieldValues: { type: GraphQLString },
filterType: { type: GraphQLString },
}
})
const QueryType = new GraphQLObjectType({
name: 'Query',
fields: () => ({
accounts: {
type: new GraphQLList(accountType),
args: {
filter: { type: new GraphQLInputObjectType(filterType) },
},
resolve: (root, args, { loaders }) => loaders.account.load(args),
},
}),
});
I have found the solution here.
https://github.com/mugli/learning-graphql/blob/master/7.%20Deep%20Dive%20into%20GraphQL%20Type%20System.md#graphqlinputobjecttype
Schema:
const filterType = new GraphQLInputObjectType({
name: 'filterType',
fields: {
fieldName: { type: GraphQLString },
fieldValues: { type: GraphQLString },
filterType: { type: GraphQLString },
}
})
const QueryType = new GraphQLObjectType({
name: 'Query',
fields: () => ({
accounts: {
type: new GraphQLList(accountType),
args: {
filter: { type: filterType },
},
resolve: (root, args, { loaders }) => {
return loaders.account.load(args)},
},
}),
});
Problem was in the query, I had the both the keys and values as strings in the object argument.
Correct Query:
{
accounts(filter: {fieldName: "id", fieldValues: "123", filterType: "in"}) {
id
}
}
You don't define filterType as an object type then wrap it in an input type, you literally create it as an input type:
const filterType = new GraphQLInputObjectType({
name: 'filterType',
fields: {
fieldName: { type: GraphQLString },
fieldValues: { type: GraphQLString },
filterType: { type: GraphQLString },
}
})
const QueryType = new GraphQLObjectType({
name: 'Query',
fields: () => ({
accounts: {
type: new GraphQLList(accountType),
args: {
filter: { type: filterType },
},
resolve: (root, args, { loaders }) => loaders.account.load(args),
},
}),
});
You'll also want to declare its type at query time, as illustrated in #piotrbienias's answer.

Resources