How to dynamically set key values in a JS function? - javascript

I'm working with the following code:
var list = ['product', 'city', 'village'];
const foobar = new GraphQLObjectType({
name: 'foobarType',
fields: () => ({
product :{ // <== NOTE THAT PRODUCT IS IN THE LIST
type: new GraphQLList(foobarType),
args: {
certainty:{
type: GraphQLFloat,
description: "tester"
}
},
resolve(root,args){
return "foobar"
}
}
})
As you can see I have a list with three items. In the fields function, you can see that I have product.
How can I iterate over the list to dynamically set the fields functions during runtime so the result will be:
var list = ['product', 'city', 'village'];
const foobar = new GraphQLObjectType({
name: 'foobarType',
fields: () => ({
product :{ // <== RESULT FROM LIST
type: new GraphQLList(foobarType),
args: {
certainty:{
type: GraphQLFloat,
description: "tester"
}
},
resolve(root,args){
return "foobar"
}
},
city :{ // <== RESULT FROM LIST
type: new GraphQLList(foobarType),
args: {
certainty:{
type: GraphQLFloat,
description: "tester"
}
},
resolve(root,args){
return "foobar"
}
},
village :{ // <== RESULT FROM LIST
type: new GraphQLList(foobarType),
args: {
certainty:{
type: GraphQLFloat,
description: "tester"
}
},
resolve(root,args){
return "foobar"
}
}
})
})

You can iterate through your list inside the function and add each item with object[item] = value; syntax.
fields: () => {
var fields = {};
list.forEach(item => {
fields[item] = {
type: new GraphQLList(foobarType),
args: {
certainty:{
type: GraphQLFloat,
description: "tester"
}
},
resolve(root,args){
return "foobar"
}
}
});
return fields;
}
This kind of nesting is ugly code, though. I'd prefer if you instead saved fields in a variable before passing it.
const fields = {};
list.forEach(item => {
fields[item] = {
type: new GraphQLList(foobarType),
args: {
certainty:{
type: GraphQLFloat,
description: "tester"
}
},
resolve(root,args){
return "foobar"
}
}
});
const foobar = new GraphQLObjectType({
name: 'foobarType',
fields: () => fields
})

You can use the array's map function and computed property names (bracket) syntax to do this:
fields: () => list.map(item => ({
[item]: {
type: new GraphQLList(foobarType),
args: {
certainty:{
type: GraphQLFloat,
description: "tester"
}
},
resolve(root,args){
return "foobar"
}
}
}))

Related

GraphQL mutation type thows error when try to set type

I got this error:
"Mutation fields must be an object with field names as keys or a function which returns such an object."
I try to add user but I don't know how to make this work.
const mutation = new GraphQLObjectType({
name: "Mutation",
fileds: {
addUser: {
type: UserType,
args: {
firstName: { type: new GraphQLNonNull(GraphQLString) },
age: { type: new GraphQLNonNull(GraphQLInt) },
companyId: { type: GraphQLString }
},
resolve(parentValue, { firstName, age }) {
return axios.post("http://localhost:3000/users", { firstName, age }).then(r => r.data);
}
}
}
})
UserType is defined as follows:
const UserType = new GraphQLObjectType({
name: "User",
fields: {
id: { type: GraphQLString },
firstName: { type: GraphQLString },
age: { type: GraphQLInt },
company: {
type: CompanyType,
resolve(parentValue, args) {
return axios.get("http://localhost:3000/companies/" + parentValue.companyId).then(res => res.data)
}
}
}
})
You have a typo: "fileds" should be "fields".

Avoid Code Duplication with GraphQL Cursor based Pagination

I've been looking all over for an answer to this and I've been banging my head on the wall. I wrote a cursor based pagination example that works well with graphql and the thing is I thought I would do the same thing with authors, that I did with books and the only way I can figure out how to do this is to completely duplicate everything. On the root query there is quite a long chunk of code handling the pagination and I would hate to do that all over for the authors endpoint but I can't seem to find a way to do this while reusing the code
Here is the code
const express = require('express')
const { graphqlHTTP } = require('express-graphql')
const {
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLList,
GraphQLInt,
GraphQLNonNull
} = require('graphql')
const {
PageType,
convertNodeToCursor,
convertCursorToNodeId
} = require('./pagination')
const app = express()
const authors = [
{ id: 1, name: "Author 1"},
{ id: 2, name: "Author 2"},
{ id: 3, name: "Author 3"}
]
const books = [
{ id: 1, title: "Book 1", authorId: 1 },
{ id: 2, title: "Book 2", authorId: 1 },
{ id: 3, title: "Book 3", authorId: 1 },
{ id: 4, title: "Book 4", authorId: 2 },
{ id: 5, title: "Book 5", authorId: 2 },
{ id: 6, title: "Book 6", authorId: 2 },
{ id: 7, title: "Book 7", authorId: 3 },
{ id: 8, title: "Book 8", authorId: 3 },
{ id: 9, title: "Book 9", authorId: 3 }
]
const Book = new GraphQLObjectType({
name: 'Book',
description: 'this is a book',
fields: () => ({
id: { type: GraphQLNonNull(GraphQLInt) },
title: { type: GraphQLNonNull(GraphQLString) },
authorId: { type: GraphQLNonNull(GraphQLInt) },
author: {
type: Author,
resolve: ({authorId}) => {
return authors.find(author => author.id === authorId)
}
}
})
})
const Author = new GraphQLObjectType({
name: 'Author',
description: 'this represents the author of a book',
fields: () => ({
id: { type: GraphQLNonNull(GraphQLInt) },
name: { type: GraphQLNonNull(GraphQLString) },
books: {
type: GraphQLList(Book),
resolve: ({id}) => {
return books.filter(book => book.authorId === id)
}
}
})
})
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
description: 'this is the root query',
fields: () => ({
book: {
type: Book,
description: 'a single book',
args: {
id: { type: GraphQLInt }
},
resolve: (_, { id }) => {
return books.find(book => book.id === id)
}
},
author: {
type: Author,
description: 'a single author',
args: {
id: { type: GraphQLInt },
},
resolve: (_, { id }) => {
return authors.find(author => author.id === id)
}
},
books: {
type: PageType(Book),
description: 'a list of books',
args: {
first: { type: GraphQLInt },
afterCursor: { type: GraphQLString }
},
resolve: (_, { first, afterCursor }) => {
let afterIndex = 0
if (typeof afterCursor === 'string') {
let nodeId = convertCursorToNodeId(afterCursor)
let nodeIndex = books.findIndex(book => book.id === nodeId)
if (nodeIndex >= 0) {
afterIndex = nodeIndex + 1
}
}
const slicedData = books.slice(afterIndex, afterIndex + first)
console.log('sliced data: ', slicedData)
const edges = slicedData.map(node => ({
node,
cursor: convertNodeToCursor(node)
}))
let startCursor = null
let endCursor = null
if (edges.length > 0) {
startCursor = convertNodeToCursor(edges[0].node)
endCursor = convertNodeToCursor(edges[edges.length - 1].node)
}
let hasNextPage = books.length > afterIndex + first
return {
totalCount: books.length,
edges,
pageInfo: {
startCursor,
endCursor,
hasNextPage
}
}
}
}
})
})
const schema = new GraphQLSchema({
query: RootQuery
})
app.use('/graphql', graphqlHTTP({
schema,
graphiql: true
}))
app.listen(3000, () => console.log('app running at http://localhost:3000/graphql'))
and I handle the pagination in another file here:
const {
GraphQLString,
GraphQLInt,
GraphQLBoolean,
GraphQLObjectType,
GraphQLList,
} = require('graphql')
const Edge = (itemType) => {
return new GraphQLObjectType({
name: 'EdgeType',
fields: () => ({
node: { type: itemType },
cursor: { type: GraphQLString }
})
})
}
const PageInfo = new GraphQLObjectType({
name: 'PageInfoType',
fields: () => ({
startCursor: { type: GraphQLString },
endCursor: { type: GraphQLString },
hasNextPage: { type: GraphQLBoolean }
})
})
const PageType = (itemType) => {
return new GraphQLObjectType({
name: 'PageType',
fields: () => ({
totalCount: { type: GraphQLInt },
edges: { type: new GraphQLList(Edge(itemType)) },
pageInfo: { type: PageInfo }
})
})
}
const convertNodeToCursor = (node) => {
// Encoding the cursor value to Base 64 as suggested in GraphQL documentation
return Buffer.from((node.id).toString()).toString('base64')
}
const convertCursorToNodeId = (cursor) => {
// Decoding the cursor value from Base 64 to integer
return parseInt(Buffer.from(cursor, 'base64').toString('ascii'))
}
module.exports = {
PageType,
convertNodeToCursor,
convertCursorToNodeId
}
Now if I copy and paste the books endpoint and change it to authors, and change the type to PageType(Author) then I get another error:
Schema must contain uniquely named types but contains multiple types named "PageType".
So this clearly isn't a solution either
You cannot have one EdgeType that contains Authors and another EdgeType that contains Books. Instead, you will need one AuthorEdge and one BookEdge type.
The same holds for the PageType - there can't be two different types with different fields but the same name.
The solution is relatively simple though - if you dynamically generated these types in a function, also name them dynamically:
const Edge = (itemType) => {
return new GraphQLObjectType({
name: itemType.name + 'Edge',
// ^^^^^^^^^^^^^^^^^^^^^^
fields: () => ({
node: { type: itemType },
cursor: { type: GraphQLString }
})
})
}
const PageInfo = new GraphQLObjectType({
name: 'PageInfo',
fields: () => ({
startCursor: { type: GraphQLString },
endCursor: { type: GraphQLString },
hasNextPage: { type: GraphQLBoolean }
})
})
const PageType = (itemType) => {
return new GraphQLObjectType({
name: itemType.name + 'sPage',
// ^^^^^^^^^^^^^^^^^^^^^^^
fields: () => ({
totalCount: { type: GraphQLInt },
edges: { type: new GraphQLList(Edge(itemType)) },
pageInfo: { type: PageInfo }
})
})
}

GraphQL NodeJS Error: One of the provided types for building the Schema is missing a name

i am learning GraphQL with NodeJS but getting the error.
i have develop a mongoDB and NodeJS GraphQL REST API for Understanding and Learning purpose
Here is my Code :
const graphql = require('graphql');
const user = require('../models/User');
const post = require('../models/Post');
const { GraphQLObjectType, GraphQLID, GraphQLString } = graphql;
const UserType = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: { type: GraphQLID },
fname: { type: GraphQLString },
lname: { type: GraphQLString },
email: { type: GraphQLString },
posts: {
type: PostType,
resolve(parent, args) {
return 1;
}
}
})
});
const PostType = new GraphQLObjectType({
name: 'Post',
fields: () => ({
id: { type: GraphQLID },
Userid: { type: GraphQLID },
title: { type: GraphQLString },
date: { type: GraphQLString },
})
});
const RootQueryType = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
user: {
type: UserType,
args: { id: { type: GraphQLID } },
resolve(parent, args) {
return user.findById({ id: args.id });
}
},
post: {
type: PostType,
args: { id: { type: GraphQLID } },
resolve(parent, args) {
return post.findById({ id: args.id });
}
}
}
});
module.exports = new graphql.GraphQLSchema({
query: RootQueryType,
});
Please Help me to find the Solution.
Answer me to resolve this error.

pushing an object into an array of objects

i want to push my retrieved results which is an inline string result to my array object item
Here is my code :
arrayObject.push({ type: "type", item: itemArray });
arrayObject.forEach((elementItem) => {
global.forEach((element) => {
const { items } = element;
for (const item in items) {
const title = items[item].title;
elementItem.item.push({ title });
}
});
});
And here is my json file that i retrieve from global, items and title
global: [
{
items: {
xxx: {
title: 'result1',
}
},
}
]
The result i want is like this :
[ { type: 'xxx', item: [ {name: result1 } ] } ]
Here i've used reduce and object.values to produce your expected outcome.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Object/values
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
const global = [{
way: 'type1',
items: {
get: {
title: 'result1',
},
post: {
title: 'result2',
},
put: {
title: 'result3',
},
},
},
{
way: 'type2',
items: {
get: {
title: 'test1',
},
post: {
title: 'test2',
},
put: {
title: 'test3',
},
},
},
]
function mapJsonToTypes(arr) {
const typeAndTitles = (acc, {items, way: type}) => {
return [...acc, {type, item: getTitlesFromItems(items)}]
}
return arr.reduce(typeAndTitles, []);
}
function getTitlesFromItems(items = {}) {
return Object.values(items).map(({ title }) => title)
}
console.log(mapJsonToTypes(global));

Writing Nested GraphQL Mutations

I am looking for examples of writing nested mutations. I am making a mutation for a recipe object and the schema looks like this:
const RecipeType = new GraphQLObjectType({
name: "Recipe",
fields: () => ({
id: { type: GraphQLID },
name: { type: GraphQLString },
dateCreated: { type: GraphQLString },
authorID: { type: GraphQLID },
prepTime: { type: PrepTimeType },
cookTime: { type: CookTimeType },
ingredients: { type: new GraphQLList(IngredientType) },
steps: { type: new GraphQLList(StepType) }
})
});
const PrepTimeType = new GraphQLObjectType({
name: "PrepTime",
fields: () => ({
quantity: { type: GraphQLFloat },
unit: { type: GraphQLString }
})
});
const CookTimeType = new GraphQLObjectType({
name: "CookTime",
fields: () => ({
quantity: { type: GraphQLFloat },
unit: { type: GraphQLString }
})
});
const IngredientType = new GraphQLObjectType({
name: "Ingredients",
fields: () => ({
name: { type: GraphQLString },
quantity: { type: GraphQLFloat },
unit: { type: GraphQLString }
})
});
const StepType = new GraphQLObjectType({
name: "Ingredients",
fields: () => ({
details: { type: GraphQLString },
estimatedTime: { type: GraphQLFloat },
unit: { type: GraphQLString }
})
});
I am looking to write a mutation that creates an entire object for this item. The mutation looks like the following:
createRecipe: {
type: RecipeType,
args: {
// Required Args
name: { type: new GraphQLNonNull(GraphQLString) },
authorID: { type: new GraphQLNonNull(GraphQLID) },
ingredients: { type: new GraphQLList(IngredientType) },
steps: { type: new GraphQLList(StepType) },
// Not required args
prepTime: { type: PrepTimeType },
cookTime: { type: CookTimeType },
},
resolve(parent, args) {
let recipe = new Recipe({
name: args.name,
dateCreated: new Date().getTime(),
authorID: args.authorID,
ingredients: args.ingredients,
steps: args.steps
});
// Check for optional args and set to recipe if they exist
args.prepTime ? recipe.prepTime = args.prepTime : recipe.prepTime = null;
args.cookTime ? recipe.cookTime = args.cookTime : recipe.cookTime = null;
return recipe.save();
}
}
I am not sure how to create one mutation that creates the entire object. and then updating will be a further challenge. Does anyone have any examples or links to the docs that support this? From what I can tell GraphQL hasn't covered this in a helpful manner.
I am currently getting the following errors:
{
"errors": [
{
"message": "The type of Mutation.createRecipe(ingredients:) must be Input Type but got: [Ingredients]."
},
{
"message": "The type of Mutation.createRecipe(steps:) must be Input Type but got: [Steps]."
},
{
"message": "The type of Mutation.createRecipe(prepTime:) must be Input Type but got: PrepTime."
},
{
"message": "The type of Mutation.createRecipe(cookTime:) must be Input Type but got: CookTime."
}
]
}
Any support would be greatly appreciated.
Cheers,
I figured this out. I needed to create input types for each subdocument. I already had the object types but for the mutations, I had to add new ones. From there I placed it into the mutation as such.
createRecipe: {
type: RecipeType,
args: {
// Required Args
name: { type: new GraphQLNonNull(GraphQLString) },
authorID: { type: new GraphQLNonNull(GraphQLID) },
ingredients: { type: new GraphQLList(IngredientInputType) },
steps: { type: new GraphQLList(StepInputType) },
// Not required args
prepTime: { type: PrepTimeInputType },
cookTime: { type: CookTimeInputType },
},
resolve(parent, args) {
let recipe = new Recipe({
name: args.name,
dateCreated: new Date().getTime(),
authorID: args.authorID,
ingredients: args.ingredients,
steps: args.steps
});
// Check for optional args and set to recipe if they exist
args.prepTime ? recipe.prepTime = args.prepTime : recipe.prepTime = null ;
args.cookTime ? recipe.cookTime = args.cookTime : recipe.cookTime = null ;
return recipe.save();
}
},

Categories

Resources