Graphql create relations between two queries.Error cannot access before initialization - mongodb

I have this code:
const ProductType = new GraphQLObjectType({
name: 'Product',
fields: {
id: { type: GraphQLID },
name: { type: GraphQLString },
category: {
type: CategoryType,
resolve: async (parent) => {
return await Category.findOne({_id: parent.category});
}
}
}
});
const CategoryType = new GraphQLObjectType({
name: 'Category',
fields: {
id: { type: GraphQLID },
name: { type: GraphQLString },
products: {
type: ProductType,
resolve: async (parent, args) => {
return await Product.find({category: parent._id});
}
}
}
});
const Query = new GraphQLObjectType({
name: 'Query',
fields: {
Categories: {
type: new GraphQLList(CategoryType),
resolve: async () => {
return await Category.find();
}
}
}
});
When i try to compile i get ReferenceError: Cannot access 'CategoryType' before initialization.
I understand that first of all I should declare and only after that use, but I saw a similar code in one lesson on YouTube, and I think that it should work, but it’s not.

fields can take a function instead of an object. This way the code inside the function won't be evaluated immediately:
fields: () => ({
id: { type: GraphQLID },
name: { type: GraphQLString },
category: {
type: CategoryType,
resolve: (parent) => Category.findOne({_id: parent.category}),
}
})

Related

How to define array type for multiple or nested json data from REST API

I'm new to GraphQL thingy. I have a problem in fetching array data from API using express-graphql server and I find to hard the solution somewhere. Here is the scenario.
I have a GET data from REST API which has the response that similar like this :
{
"id": 1,
"name": "billy",
"foods": [
{
"food":{
"name":"crepes",
"taste": "crunchy"
}
},
{
"food":{
"name":"noodle",
"taste":"spicy"
}
},
]
}
In my schema, I have successfully gets the id and name which I implement like this :
const FoodsType= new GraphQLObjectType({
name: 'foods',
fields: () => ({
id:{ type: GraphQLInt },
name:{ type: GraphQLString},
foods: { type: GraphQLArray}
})
});
As you can see I with my code above, I failed fetching foods data which contains array data because there is no scalar type like GraphQLArray.
My question is how do we get the foods data with containing multiple json data food inside it ?
const ResType= new GraphQLObjectType({
name: 'response',
field:() => ({
name: {type: GraphQLString},
taste: {type: GraphQLString}
})
});
const FoodsType= new GraphQLObjectType({
name: 'foods',
fields: () => ({
id:{ type: GraphQLInt },
name:{ type: GraphQLString},
foods: { type: new GraphQLList(ResType)}
})
});
After some attempt, I finally manage to solve this by define the types of every depth level. Here is my answer for schema file.
const FoodsType = new GraphQLObjectType({
name: 'foods',
fields: () => ({
id:{ type: GraphQLInt },
name: { type: GraphQLString },
foods:{ type: new GraphQLList(FoodType) }
})
});
const FoodType = new GraphQLObjectType({
name: 'food',
fields: () => ({
food: {type: FoodDetail}
})
});
const FoodDetail = new GraphQLObjectType({
name: 'fooddetail',
fields: () => ({
name:{ type: GraphQLString },
taste:{ type: GraphQLString}
})
});
And here is the resolver that I get from API
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
foods: {
type: FoodsType,
args: {
id: { type: GraphQLInt }
},
resolve(parent, args) {
return axios
.get(`https://hereisanapi/${args.id}`)
.then(res => {
return res.data;
});
}
}
}
});
Then in GraphQL Query I put this.
{
foods(id:1){
food{
name,
taste
}
}
}

Why graphql type relations returns null values?

i have this schema.js of SHIPS
// Ships
const ShipType = new GraphQLObjectType({
name: "Ships",
fields: () => ({
ship_id: { type: GraphQLString },
ship_name: { type: GraphQLString },
home_port: { type: GraphQLString },
ship_type: { type: GraphQLString },
year_built: { type: GraphQLInt },
position: { type: ShipPositionType },
active: { type: GraphQLBoolean }
})
})
// Ship positions
const ShipPositionType = new GraphQLObjectType({
name: "ShipPositions",
fields: () => ({
latitude: { type: GraphQLFloat },
longitude: { type: GraphQLFloat }
})
});
at ShipType i have added a new field "position" which will return ShipPositionType
this is what i return so far
// get ships
ships: {
type: new GraphQLList(ShipType),
resolve(parent, args) {
return axios.get('https://api.spacexdata.com/v3/ships')
.then(res => res.data);
}
}
The position field inside the shipType need to have a resolver, inside the resolver you return the ship data based on the ShipType ship_id matching the ShipPositionType ship_id. i.e(return data.find(ship => ship.shid_id === parent.ship_id )) . So first add a ship_id field to the ShipPositionType Object.
Something like this
// Ships
const ShipType = new GraphQLObjectType({
name: "Ships",
fields: () => ({
ship_id: { type: GraphQLString },
ship_name: { type: GraphQLString },
home_port: { type: GraphQLString },
ship_type: { type: GraphQLString },
year_built: { type: GraphQLInt },
position: {
type: ShipPositionType,
resolve(parent,args) {
return data.find(data => data.ship_id === parent.ship_id)}
},
active: { type: GraphQLBoolean }
})
})
//You need to add a ship_id field here too
// Ship positions
const ShipPositionType = new GraphQLObjectType({
name: "ShipPositions",
fields: () => ({
ship_id: {type: {GraphQLString},
latitude: { type: GraphQLFloat },
longitude: { type: GraphQLFloat }
})
});

how to return token from graphql mutation

This is my code of mutation in which i use the type of User which has name,email,password and i make two mutations for the register the user and login the user. I have searched all the docs about the graphql and read all the blogs related to authentication but can't got the answer to return the token from the mutation
const mutation = new GraphQLObjectType({
name: "Mutation",
fields: {
addUser: {
type: UserType,
args: {
name: { type: GraphQLString },
email: { type: GraphQLString },
password: { type: GraphQLString },
avatar: { type: GraphQLString }
},
resolve(parentValue, args) {
const avatar = gravatar.url(args.email);
return bcrypt
.hash(args.password, 10)
.then(hash => {
args.password = hash;
const newUser = new User({
name: args.name,
email: args.email,
password: args.password,
avatar
});
return newUser
.save()
.then(user => user)
.catch(e => e);
})
.catch(e => e);
}
},
login: {
name: "Login",
type: UserType,
args: {
email: { type: GraphQLString },
password: { type: GraphQLString }
},
resolve(parentValue, args, context) {
return User.findOne({ email: args.email })
.then(user => {
if (user) {
return bcrypt
.compare(args.password, user.password)
.then(isValid => {
if (!isValid) {
throw new Error({ message: "password Incrrect" });
} else {
const token = jwt.sign(
{ name: user.name, id: user.id },
"mySecret"
);
return user;
}
})
.catch(e => e);
} else {
throw new Error({ message: "email Incorrect" });
}
})
.catch(e => e);
}
}
}
});
This is my User Type
const UserType = new GraphQLObjectType({
name: "User",
fields: {
id: { type: GraphQLString },
name: { type: GraphQLString },
email: { type: GraphQLString },
password: { type: GraphQLString },
avatar: { type: GraphQLString }
}
});
I would advise you to update your UserType by removing the password field and add a token field like:
const UserType = new GraphQLObjectType({
name: "User",
fields: {
id: { type: GraphQLString },
name: { type: GraphQLString },
email: { type: GraphQLString },
avatar: { type: GraphQLString },
token: { type: GraphQLString }
}
});
The reason is that UserType is a return type of the mutation, so it's "public", and maybe we should not send a password to public(as we are authenticating on the server side), but JWT is public, so we can send it back.
And in your login mutation add the token into the user object, something like:
login: {
name: "Login",
type: UserType,
args: { email: { type: GraphQLString }, password: { type: GraphQLString } },
resolve(parentValue, args, context) {
return User.findOne({ email: args.email })
.then(user => {
.........
const token = jwt.sign(
{ name: user.name, id: user.id },
"mySecret"
);
user.token = token;
return user;
}
........
}
}
You can create different type for mutation like this without changing the existing UserType.
const UserMutationType = new GraphQLObjectType({
name: "User",
fields: {
id: { type: GraphQLString },
name: { type: GraphQLString },
email: { type: GraphQLString },
avatar: { type: GraphQLString },
token: { type: GraphQLString }
}
});
And add this type in mutation
login: {
name: "Login",
type: UserMutationType,
args: {},
resolver(parent,args,context){},
}

GraphQL Schema for mongoose Mixed type (Schema.Types.Mixed)

I have a Mongoose schema with the following structure:
import mongoose from 'mongoose';
const PropertySchema = new mongoose.Schema({
name: {
type: String
},
description: {
type: String
},
value: {
type: mongoose.Schema.Types.Mixed
},
unit: {
type: String
},
});
export default mongoose.model('Property', PropertySchema);
I need to build a GraphQL query for the given data. How do I handle the Mixed type for the value property ?
Here is my try:
import NodeInterface from '../interfaces';
import PropertyModel from '../../models/Property';
const fields = {
id: {
type: new GraphQLNonNull(GraphQLID),
resolve: (obj) => dbIdToNodeId(obj._id, "Property")
},
name: {
type: GraphQLString
},
description: {
type: GraphQLString
},
value: {
type: <<< What to use here ?
},
unit: {
type: GraphQLString
}
};
export const PropertyType = new GraphQLObjectType({
name: 'Property',
description: 'Property',
interfaces: () => [NodeInterface],
isTypeOf: (value) => value instanceof PropertyModel,
fields: fields
});

Simple-schema wont update

I'm trying to update a MongoDB collection using $set. The field within the collection doesn't exist. When I try to add the field, the collection briefly stores the data and then the data disappears and returns an error ClientError: name.first is not allowed by the schema. I have no idea what I'm doing wrong here and I've been searching google for hours.
I'm using:
Meteor
Simple-Schema (NPM)
meteor-collection2-core
Path: Simple-Schema
const ProfileCandidateSchema = new SimpleSchema({
userId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
createdAt: {
type: Date,
},
name: { type: Object, optional: true },
'name.first': { type: String },
});
Path: Method.js
import { Meteor } from 'meteor/meteor';
import { ProfileCandidate } from '../profileCandidate';
import SimpleSchema from 'simpl-schema';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
export const updateContactDetails = new ValidatedMethod({
name: 'profileCandidate.updateContactDetails',
validate: new SimpleSchema({
'name.first': { type: String },
}).validator({ clean: true }),
run(data) {
ProfileCandidate.update({userId: this.userId},
{$set:
{
name: {'first': "Frank"},
}
}, (error, result) => {
if(error) {
console.log("error: ", error)
}
});
}
});
UPDATE
Path: call function
updateContactDetails.call(data, (error) => {
if (error) {
console.log("err: ", error);
console.log("err.error: ", error.error);
}
});
Can you try replacing:
validate: new SimpleSchema({
'name.first': { type: String },
}).validator({ clean: true }),
in Method.js by:
validate: new SimpleSchema({
name: {
type: new SimpleSchema({
first: String,
}),
optional: true,
}).validator({ clean: true }),