I'm combining a GraphQL app with my existing Firebase project and am having a lot of problems getting the queries to correctly get data from the firestore().
So far I have the mutations working correctly, but when I go to query the data I can't get the firestore().get() snapshot into a form that graphQL will recognize.
so far it looks like this:
const {GraphQLObjectType,
GraphQLString,
GraphQLBoolean,
GraphQLFloat,
GraphQLSchema,
GraphQLID,
GraphQLList,
GraphQLNonNull} = require("graphql");
const admin = require('firebase-admin');
const functions = require('firebase-functions');
admin.initializeApp(functions.config().firebase);
//Models
const Room = admin.firestore().collection('room');
const Position = admin.firestore().collection('position');
const Plant = admin.firestore().collection('plant');
const PlantInfo = admin.firestore().collection('plantInfo');
const RoomType = new GraphQLObjectType({
name: "Room",
fields: () => ({
id: { type: GraphQLID },
name: { type: GraphQLString },
description: { type: GraphQLString },
floor: { type: GraphQLString },
building: { type: GraphQLString },
positions: {
type: new GraphQLList(PositionType),
resolve(parent, arg) {
//return _.filter(positions, {inRoomId:parent.id})
return Position.orderByChild('inRoomId').equalTo(parent.id);
}
}
})
});
const PositionType = new GraphQLObjectType({
name: "Position",
fields: () => ({
id: { type: GraphQLID },
name: { type: GraphQLString },
description: { type: GraphQLString },
exposure: { type: GraphQLString },
size: { type: GraphQLString },
inRoom: {
type: RoomType,
resolve(parent, args) {
//return _.find(rooms, {id:parent.inRoomId})
return Room.child(parent.inRoomId);
}
}
})
});
const RootQuery = new GraphQLObjectType({
name: "RootQueryType",
fields: {
room: {
type: RoomType,
args: { id: { type: GraphQLID } },
resolve(parent, args) {
//code to get data from db/othersourse
//return _.find(rooms, {id: args.id});
return Room.child(args.id);
}
},
position: {
type: PositionType,
args: { id: { type: GraphQLID } },
resolve(parent, args) {
//code to get data from db/othersourse
//return _.find(positions, {id: args.id})
return Position.child(args.id);
}
},
rooms: {
type: new GraphQLList(RoomType),
resolve(parent, args) {
//return rooms
return Room.get().then(snapshot => {snapshot.forEach(doc => {return doc})})
}
},
positions: {
type: new GraphQLList(PositionType),
resolve(parent, args) {
//return positions
return Position.get().then(doc => console.log(doc)).catch(err => console.log('Error getting document', err));
}
}
}
});
const Mutation = new GraphQLObjectType({
name: "Mutation",
fields: {
addRoom: {
type: RoomType,
args: {
name: { type: new GraphQLNonNull(GraphQLString) },
floor: { type: new GraphQLNonNull(GraphQLString) },
building: { type: new GraphQLNonNull(GraphQLString) }
},
resolve(parent, args) {
let room = {
name: args.name,
floor: args.floor,
building: args.building
};
return Room.add(room);
}
},
addPosition: {
type: PositionType,
args: {
name: { type: new GraphQLNonNull(GraphQLString) },
exposure: { type: new GraphQLNonNull(GraphQLString) },
size: { type: new GraphQLNonNull(GraphQLString) },
inRoomId: { type: new GraphQLNonNull(GraphQLString) }
},
resolve(parent, args) {
let position = {
name: args.name,
exposure: args.exposure,
size: args.size,
inRoomId: args.inRoomId
};
return Position.add(position);
}
}
}
});
module.exports = new GraphQLSchema({
query: RootQuery,
mutation: Mutation
});
Under the RootQuery -> Rooms I'm trying to get a graphQL query to return all the rooms in my 'room' collection. I have been able to get it to console.log() a list of documents using:
return Room.get()
.then(snapshot => {
snapshot.forEach(doc => {
console.log(doc.id, " => ", doc.data());
But getting this into an array has so far eluded me. Any help is really appreciated.
Seeing as no one was able to answer this, I ended up figuring it out for myself :p
So resolve functions relating to getting a collection of related data for example positions. the following works:
first you need a function to convert the snapshots into an array as this is what graphQL is expecting. This also allows your to seperate the id and add it in with the array item:
const snapshotToArray = (snapshot) => {
var returnArr = [];
snapshot.forEach((childSnapshot)=> {
var item = childSnapshot.data();
item.id = childSnapshot.id;
returnArr.push(item);
});
return returnArr;
};
Next when getting the data you use .get() which returns a promise (and error) which can be passed into the snapshotToArray().
return Position.get().then((snapshot) => {
return snapshotToArray(snapshot);
})
For resolve functions that only call on one dataset for example inRoom. Its similar to the first one except using .where() and seperating the id and data() in the snapshot functions:
return Room.doc(parent.inRoomId).get().then((snapshot) => {
var item = snapshot.data();
item.id = snapshot.id;
return item;
})
Just incase someone else runs into the same problem :)
Related
I am new to GraphQL/Relay and I have a problem with a small project. I have a collection of documents containing a field of type "array". Please tell me which type of GraphQL to use for working with arrays?
I tried to use GraphQLList but got some errors like
"Expected GraphQL named type but got: [function GraphQLList]."
and other.
Will be very grateful for any help!
Here is the schema:
const mongoose = require('mongoose');
mongoose.set('useFindAndModify', false);
const Schema = mongoose.Schema;
const houseSchema = new Schema({
name: {
type: String,
required: true
},
events: {
type: Array,
default: []
}
});
var houseModel = mongoose.model("House", houseSchema);
module.exports = {
getHouses: () => {
return houseModel.find({}).limit(10).sort({_id:-1})
.then(houses => {
return houses.map(house => {
return {
...house._doc,
id: house.id
};
});
})
.catch(err => {
throw err;
});
},
getHouse: id => {
return houseModel.findOne({ _id: id });
},
createHouse: house => {
return houseModel(house).save();
},
removeHouse: id => {
return houseModel.findByIdAndRemove(id);
},
updateHouse: (id, args) => {
return houseModel.findByIdAndUpdate(
id,
{
name: args.name,
events: args.events //-----------------
},
{ new: true }
);
}
};
Type for 'house':
const {
GraphQLList,
GraphQLObjectType,
GraphQLString
} = require('graphql');
const { globalIdField, connectionDefinitions } = require('graphql-relay');
const { nodeInterface } = require('../nodes');
const House = new GraphQLObjectType({
name: "House",
description: "lkjlkjlkjlkjlk",
interfaces: [nodeInterface],
fields: () => ({
id: globalIdField(),
name: {
type: GraphQLString,
description: "Name of House"
},
events: {
type: GraphQLList,
description: "Events list"
}
})
});
const { connectionType: HouseConnection } = connectionDefinitions({
nodeType: House
});
module.exports = { House, HouseConnection };
Mutation:
const {
GraphQLList,
GraphQLObjectType,
GraphQLNonNull,
GraphQLString,
GraphQLBoolean
} = require('graphql');
const { fromGlobalId, mutationWithClientMutationId } = require('graphql-relay');
const { House } = require('./types/house');
const houseModel = require('./models/house');
const CreateHouseMutation = mutationWithClientMutationId({
name: "CreateHouse",
inputFields: {
name: { type: new GraphQLNonNull(GraphQLString) },
events: { type: new GraphQLNonNull(GraphQLList) }
},
outputFields: {
house: {
type: House
}
},
mutateAndGetPayload: args => {
return new Promise((resolve, reject) => {
houseModel.createHouse({
name: args.name,
events: args.events
})
.then(house => resolve({ house }))
.catch(reject);
});
}
});
const UpdateHouseMutation = mutationWithClientMutationId({
name: "UpdateHouse",
inputFields: {
id: { type: new GraphQLNonNull(GraphQLString) },
name: { type: new GraphQLNonNull(GraphQLString) },
events: { type: new GraphQLNonNull(GraphQLList) }
},
outputFields: {
updated: { type: GraphQLBoolean },
updatedId: { type: GraphQLString }
},
mutateAndGetPayload: async (args) => {
const { id: productId } = fromGlobalId(args.id);
const result = await houseModel.updateHouse(productId, args);
return { updatedId: args.id, updated: true };
}
});
const RemoveHouseMutation = mutationWithClientMutationId({
name: "RemoveHouse",
inputFields: {
id: { type: new GraphQLNonNull(GraphQLString) },
},
outputFields: {
deleted: { type: GraphQLBoolean },
deletedId: { type: GraphQLString }
},
mutateAndGetPayload: async ({ id }, { viewer }) => {
const { id: productId } = fromGlobalId(id);
const result = await houseModel.removeHouse(productId);
return { deletedId: id, deleted: true };
}
});
const Mutation = new GraphQLObjectType({
name: "Mutation",
description: "kjhkjhkjhkjh",
fields: {
createHouse: CreateHouseMutation,
removeHouse: RemoveHouseMutation,
updateHouse: UpdateHouseMutation
}
});
module.exports = Mutation;
GraphQLList is a wrapper type just like GraphQLNonNull. It wraps another type. You use it just like GraphQLNonNull -- by invoking the constructor and passing in the type you want to wrap.
new GraphQLList(GraphQLString)
Both wrapper types can wrap each other, so you can do something like this as well:
new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLString)))
This question already has answers here:
How to deal with cyclic dependencies in Node.js
(16 answers)
Closed 3 years ago.
So i have two objecttypes and i'm trying to include them to make relationships, one of them works and one of them just returns an empty object and i can't figure out why.
this one works, it console logs the ranktype and works fine
const Rank = require('../model/RankModel')
const { RankType } = require('./rank')
console.log(RankType)
/**
* Defines Branch Type
*/
const BranchType = new GraphQLObjectType({
name: "Branch",
fields: {
id: { type: GraphQLID },
name: { type: GraphQLString },
color: { type: GraphQLString },
ranks: {
type: GraphQLList(RankType),
resolve: async (branch) => {
return await Rank.find({branch: branch.id})
}
}
}
})
module.exports.BranchType = BranchType
this is the one thats breaking
const Rank = require('../model/RankModel')
const Branch = require('../model/BranchModel')
const { BranchType } = require('./branch')
console.log(BranchType)
/**
* Defines Rank Type
*/
const RankType = new GraphQLObjectType({
name: "Rank",
fields: {
id: { type: GraphQLID },
name: { type: GraphQLString },
shortHand: { type: GraphQLString },
branch: {
type: BranchType,
resolve: async (rank) => {
return await Branch.findById(rank.branch)
}
}
}
})
module.exports.RankType = RankType
this givs me an error of "message": "The type of Rank.branch must be Output Type but got: undefined."
Models/Relations:
BranchModel:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
let branchSchema = new Schema({
name: {
type: String,
required: true,
unique: true
},
color: {
type: String,
required: true,
unique: true
},
ranks: [{
type: Schema.Types.ObjectId,
ref: 'Rank'
}]
});
module.exports = mongoose.model('Branch', branchSchema)
RankModel
const mongoose = require('mongoose')
const Schema = mongoose.Schema
let rankSchema = new Schema({
name: {
type: String,
required: true,
unique: true
},
shortHand: {
type: String,
required: true,
unique: true
},
branch: {
type: Schema.Types.ObjectId,
ref: 'Branch'
}
});
module.exports = mongoose.model('Rank', rankSchema);
Answer!!!!!!
/**
* Defines Rank Type
*/
const RankType = new GraphQLObjectType({
name: "Rank",
fields: () => ({
id: { type: GraphQLID },
name: { type: GraphQLString },
shortHand: { type: GraphQLString },
branch: {
type: require('./branch').BranchType,
resolve: async (rank) => {
console.log(rank.branch)
return await Branch.findById(rank.branch)
}
}
})
})
module.exports.RankType = RankType
Looks to me like you need to destructure BranchType like you did when requiring RankType, based what I can see from your module.exports
change
const BranchType = require('./branch')
to
const { BranchType } = require('./branch')
I think I am missing something in the docs but I am not sure how to handle objects as the type withing a new GraphQLObjectType. I am looking to set up the queries for weather data from this sample data, and I am not sure how to handle the nested objects. I currently have:
// Creating a Type for the Weather Object
const WeatherType = new GraphQLObjectType({
name: 'Weather',
fields: () => ({
weather: { type: GraphQLObject? },
})
});
I am looking to get specific with the queries and set up the structure to specify more select data like:
// Creating a Type for the Weather Object
const WeatherType = new GraphQLObjectType({
name: 'Weather',
fields: () => ({
weather: {
main: { type: GraphQLString },
// And so on
},
})
});
Are there any references to examples of this?
When constructing a schema with nested custom types, you just set the type of the field to a reference of your other created type:
const WeatherType = new GraphQLObjectType({
name: 'Weather',
fields: {
id: {
type: GraphQLInt,
}
main: {
type: GraphQLString,
}
description: {
type: GraphQLString,
}
icon: {
type: GraphQLString,
}
}
})
const MainType = new GraphQLObjectType({
name: 'Main',
fields: {
temp: {
type: GraphQLFloat,
}
pressure: {
type: GraphQLFloat,
}
humidity: {
type: GraphQLFloat,
}
tempMin: {
type: GraphQLFloat,
resolve: (obj) => obj.temp_min
}
tempMax: {
type: GraphQLFloat,
resolve: (obj) => obj.temp_max
}
}
})
const WeatherSummaryType = new GraphQLObjectType({
name: 'WeatherSummary',
fields: {
weather: {
type: new GraphQLList(WeatherType),
}
main: {
type: MainType,
}
}
})
Be careful when molding existing JSON responses into GraphQL schemas -- it's easy to get burned by differences in structure. For example, the main field in your sample response is an object, but the weather field is actually an array, so we have to wrap it in GraphQLList when specifying the type for the field.
I'm creating a Reddit clone and I'm setting up the backend first, but having trouble creating relational data.
When I use this query:
query {
subreddit(id: 1) {
name
posts {
title
}
}
}
I expect:
{
"data": {
"subreddit": {
"name": "javascript"
"posts": [
{
"title": "JS Post"
}
]
}
}
}
What I get:
{
"data": null,
"errors": [
{
"message": "Cannot return null for non-nullable field Subreddit.posts.",
"locations": [
{
"line": 4,
"column": 5
}
],
"path": [
"subreddit",
"posts"
]
}
]
}
Here's the schema:
type Query {
subreddits: [Subreddit!]!
subreddit(id: ID!): Subreddit!
posts: [Post!]!
post(id: ID!): Post!
}
type Mutation {
createSubreddit(
name: String!
description: String!
contentType: String!
ageRestriction: Boolean!
): Subreddit!
}
type Subreddit {
id: ID!
name: String!
description: String!
contentType: String!
ageRestriction: Boolean!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
body: String!
subredditId: ID!
# userId: ID!
}
Here is server/index.js:
const { GraphQLServer } = require('graphql-yoga');
let dummySubreddits = [
{
name: 'javascript',
description: 'all things javascript',
contentType: 'any',
ageRestriction: false,
id: 1
},
{
name: 'react',
description: 'all things react',
contentType: 'any',
ageRestriction: false,
id: 2
},
{
name: 'primsa',
description: 'all things prisma',
contentType: 'any',
ageRestriction: false,
id: 3
}
];
let idCountSubreddit = dummySubreddits.length;
let dummyPosts = [
{ title: 'JS Post', body: 'Body of post one', id: 1, subredditId: 1 },
{ title: 'React Post', body: 'Body of post two', id: 2, subredditId: 2 },
{
title: 'Prisma Post',
body: 'Body of post three',
id: 3,
subredditId: 3
}
];
let idCountPost = dummyPosts.length;
const resolvers = {
Query: {
subreddits: () => dummySubreddits,
subreddit: (parent, args) => {
return dummySubreddits.find(obj => obj.id == args.id);
},
posts: () => (parent, args) => {
return dummyPosts.find(obj => obj.subredditId == parent.id);
},
post: (parent, args) => {
return dummyPosts.find(obj => obj.id == args.id);
}
},
Mutation: {
createSubreddit: (parent, args) => {
let subreddit = {
id: idCountSubreddit++,
name: args.name,
description: args.description,
contentType: args.contentType,
ageRestriction: args.ageRestriction
};
return subreddit;
}
}
};
const server = new GraphQLServer({ typeDefs: './schema.graphql', resolvers });
server.start(() => console.log('Server is running on localhost:4000'));
I'm using the GraphQL desktop app for querying and I do not have grapql-yoga config file.
Where am I going wrong? I'd like to be pointed in the right direction so I can figure it out myself. This is my first time working with GraphQL alone, after doing some tutorials on YouTube, however they used graphql-express and I'm using graphql-yoga.
Move the resolver you have written for Query's posts into Subreddit to resolve the posts field there. If your resolver does not comply to the default resolver implementation:
(parent) => parent[fieldName]
Like in your case
(parent) => parent.posts
You have to specify it yourself. If your field posts on Query should display all the posts you might want to go for the following implementations:
const resolvers = {
Query: {
subreddits: () => dummySubreddits,
subreddit: (parent, args) => {
return dummySubreddits.find(obj => obj.id == args.id);
},
posts: () => dummyPosts,
post: (parent, args) => {
return dummyPosts.find(obj => obj.id == args.id);
}
},
Subreddit: {
posts: () => (parent, args) =>
dummyPosts.filter(obj => obj.subredditId == parent.id),
},
Mutation: {
createSubreddit: (parent, args) => {
let subreddit = {
id: idCountSubreddit++,
name: args.name,
description: args.description,
contentType: args.contentType,
ageRestriction: args.ageRestriction
};
return subreddit;
}
}
};
I had to add a resolver for subreddit to deal with posts.
const resolvers = {
Query: {
subreddits: () => dummySubreddits,
subreddit: (parent, args) => {
return dummySubreddits.find(obj => obj.id == args.id);
},
posts: (parent, args) => {
return dummyPosts;
},
post: (parent, args) => {
return dummyPosts.find(obj => obj.id == args.id);
}
},
Mutation: {
createSubreddit: (parent, args) => {
let subreddit = {
id: idCountSubreddit++,
name: args.name,
description: args.description,
contentType: args.contentType,
ageRestriction: args.ageRestriction
};
return subreddit;
}
},
// This resolver was needed
Subreddit: {
posts: subreddit =>
dummyPosts.filter(obj => obj.subredditId == subreddit.id)
}
};
I want to use mutation in Relay to change an array (not connection). The array is typed GraphQLList in the GraphQL side. The graphql side worked perfectly, but relay side needs dataID for each item in an array. And when I am inserting new item or modifying existing item in the array, there are no dataID provided? What is the right way to do this? By the way, I am using redux to maintain the list, and submit changes via relay at the end.
The schema:
let widgetType = new GraphQLInputObjectType({
name: 'Widget',
fields: () => ({
label: {
type: GraphQLString
},
type: {
type: GraphQLString
},
list: {
type: new GraphQLList(GraphQLString)
},
description: {
type: GraphQLString
},
required: {
type: GraphQLBoolean
}
})
});
let modifyFormMutation = mutationWithClientMutationId({
name: 'ModifyForm',
inputFields: {
id: {
type: new GraphQLNonNull(GraphQLString)
},
name: {
type: new GraphQLNonNull(GraphQLString)
},
userId: {
type: new GraphQLNonNull(GraphQLString)
},
widgets: {
type: new GraphQLList(widgetType)
}
},
outputFields: {
formEdge: {
type: formConnection.edgeType,
resolve: (obj) => {
return {
node: {
id: obj.id,
name: obj.name,
userId: obj.userId,
widgets: obj.widgets
},
cursor: obj.id
};
}
},
app: {
type: appType,
resolve: () => app
}
},
mutateAndGetPayload: ({
id, name, userId, widgets
}) => {
db.collection('forms').findOneAndUpdate({
_id: new ObjectID(id)
}, {
name, userId, widgets, createAt: Date.now()
});
return {
id, name, userId, widgets
};
}
})
Relay mutation:
export default class ModifyFormMutation extends Mutation {
getMutation () {
return Relay.QL`mutation{modifyForm}`;
}
getFatQuery() {
return Relay.QL`
fragment on ModifyFormPayload {
formEdge
app { forms }
}
`;
}
getCollisionKey() {
return `check_${this.props.app.id}`;
}
getConfigs() {
return [{
type: 'FIELDS_CHANGE',
fieldIDs: {
formEdge: {node: this.props.node},
app: this.props.app.id
}
}];
}
getVariables() {
return {
name: this.props.node.name,
id: this.props.node.id,
userId: this.props.node.userId,
widgets: this.props.node.widgets
};
}
getOptimisticResponse() {
return {
formEdge: {
name: this.props.node.name,
id: this.props.node.id,
userId: this.props.node.userId,
widgets: this.props.node.widgets
}
};
}
}
And error message from browser:
"Variable "$input_0" got invalid value
{"name":"asdfasdfsa","id":"57e790cec252f32aa805e38d","userId":"57e10a02da7e1116c0906e40","widgets":[{"dataID":"client:618507132","label":"sdfas","type":"text","list":[],"description":"","required":true},{"label":"sfasdfasaaa","list":[],"type":"number","description":"","required":"false"}],"clientMutationId":"0"}.↵In
field "widgets": In element #0: In field "dataID": Unknown field."