GraphQLNonNull opposite - node.js

i`m trying implements graphql and i have problem.
I did type for graphql:
export const menuItemDataType = new GraphQL.GraphQLObjectType({
name: 'MenuItemData',
fields: () => ({
staticUrl: {
type: GraphQL.GraphQLString
},
page: {
type: new GraphQL.GraphQLNonNull(menuItemType),
resolve(MenuItemData) {
return PageRepository.getPageById(MenuItemData.page).exec();
}
},
menu: {
type: new GraphQL.GraphQLNonNull(menuType),
resolve(MenuItemData) {
return MenuRepository.getMenuById(MenuItemData.menu).exec();
}
}
})
})
and in this GraphQLObjectType i have page and menu.
I use mongoDB with mongoose. page and menu are nullable in model. When i query in graphql on this property so its chance that can be return null, but this is not compatible with GraphQL.GraphQLNonNull. Return error with "message": "Cannot return null for non-nullable field MenuItemData.page."
My question is: "Is any opposite for GraphQLNonNull. Like GraphQL?". I didnt found it.
Thank you

Don't use GraphQLNonNull if the type is nullable. GraphQL fields can have no value by default.
type: new GraphQL.GraphQLNonNull(menuType)
becomes
type: menuType

Related

Retrieving single and multiple objects via GraphQL

I'm less than a couple of weeks into using Apollo and GraphQL, and I'd like to retrieve multiple objects via GraphQL, but it won't allow me to.
With the query as:
const GET_ALL_PURCHASES_QUERY = (statusOfPurchase) => {
return gql`
query {
getAllPurchases(statusOfPurchase: "${statusOfPurchase}") {
id
customerInformation {
customerName
customerEmailAddress
}
createdAt
updatedAt
}
}
`
}
... and in the schema:
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
getAllPurchases: {
type: PurchaseType,
args: {
statusOfPurchase: {
type: new GraphQLNonNull(GraphQLString)
}
},
resolve(parent, args) {
return PurchasesModel.schemaForPurchases.find({
statusOfPurchase: args.statusOfPurchase
}).limit(10)
.then(purchases => {
console.log('Schema:getAllPurchases()', purchases)
return purchases
})
}
}
}
})
Result in Node via the Terminal is:
Schema:getAllPurchases() [
{
_id: 60351a691d3e5a70d63eb13e,
customerInformation: [ [Object] ],
statusOfPurchase: 'new',
createdAt: 2021-02-23T15:08:25.230Z,
updatedAt: 2021-02-23T15:08:25.230Z,
__v: 0
},
{
_id: 60351b966de111716f2d8a6d,
customerInformation: [ [Object] ],
statusOfPurchase: 'new',
createdAt: 2021-02-23T15:13:26.552Z,
updatedAt: 2021-02-23T15:13:26.552Z,
__v: 0
}
]
Correct.
But in the application within Chrome, it's a single object with null as the value of each field.
With the query as:
const GET_ALL_PURCHASES_QUERY = () => {
return gql`
query {
getAllPurchases {
id
customerInformation {
customerName
customerEmailAddress
}
createdAt
updatedAt
}
}
`
}
... and with the appropriate changes to the schema, the result is the same as before, where I see two objects in Node but a failed single object in Chrome.
If I change: return purchases to: return purchases[0] I see the first object in Chrome with the correct values.
How am I supposed to return more than one object?
Your type for the getAllPurchases field is set to PurchaseType in the schema. You want to use new GraphQLList(PurchaseType) to have the return type be a list of purchases. That's why when you try to use the schema, it returns null if the types are bad, but correctly returns a purchase if you do return a single element.
See the graphql docs for an example of this.

how to have a field that conditionally MUST be null?

This isn't quite a "sometimes required" field, but a little different.
I need to have a field in a Mongoose document that, depending on other data in the document, must NEVER be populated.
Is there a way to define the schema such that if field a is populated, that field b MUST be null?
I would strongly prefer to NOT solve this with mongoose hooks...
It can be done using custom validators, this example is for mongoose version 5.x.
new Schema({
a: {
type: String
},
b: {
type: String,
default: null,
validate: {
validator: function (v) {
if (this.a && v === null) {
return true
}
return false
},
message: (props) => `${props.value} should be null!`
}
}
})

How to manage GraphQL child objectType that can be nullable in an output type?

I'm setting up a nodeJS GraphQL API and I'm experimenting a blocking point regarding one of my resource output type.
The feature is a form that contain three different level :
Level 1- formTemplate
Level 2- formItems (templateId, type (video, image, question) - 1-N relation with formTemplate)
Level 3- formQuestions (0-1 relation with formItem if and only if formItems.type is 'question')
My GraphQL resource is returning all the templates in the database so it's an array that for each template is returning all his items and each item of type "question" needs to return an array containing the associated question.
My problem is : I really don't know how to return an empty object type for the formItems where type is different from "question" or if there is a better approach for this kind of situation
I've tried to look at GraphQL directives and inline fragments but I think it really needs to be manage by the backend side because it's transparent for the API consumer.
const formTemplate = new GraphQLObjectType({
name: 'FormTemplate',
fields: () => {
return {
id: {
type: new GraphQLNonNull(GraphQLInt)
},
authorId: {
type: new GraphQLNonNull(GraphQLInt)
},
name: {
type: new GraphQLNonNull(GraphQLString)
},
items: {
type: new GraphQLList(formItem),
resolve: parent => FormItem.findAllByTemplateId(parent.id)
}
}
}
})
const formItem = new GraphQLObjectType({
name: 'FormItem',
fields: () => {
return {
id: {
type: new GraphQLNonNull(GraphQLInt)
},
templateId: {
type: new GraphQLNonNull(GraphQLInt)
},
type: {
type: new GraphQLNonNull(GraphQLString)
},
question: {
type: formQuestion,
resolve: async parent => FormQuestion.findByItemId(parent.id)
}
}
}
})
const formQuestion= new GraphQLObjectType({
name: 'FormQuestion',
fields: () => {
return {
id: {
type: new GraphQLNonNull(GraphQLInt)
},
itemId: {
type: new GraphQLNonNull(GraphQLInt)
},
type: {
type: new GraphQLNonNull(GraphQLString)
},
label: {
type: new GraphQLNonNull(GraphQLString)
}
}
}
})
My GraphQL request :
query {
getFormTemplates {
name
items {
type
question {
label
type
}
}
}
}
What I'm expected is
{
"data": {
"getFormTemplates": [
{
"name": "Form 1",
"items": [
{
"type": "question",
"question": {
"label": "Question 1",
"type": "shortText"
},
{
"type": "rawContent"
"question": {}
}
]
}
]
}
}
I'd design your "level 2" items so that the "type" property corresponded to actual GraphQL types, implementing a common interface. Also, in general, I'd design the schema so that it had actual links to neighboring items and not their identifiers.
So if every form item possibly has an associated template, you can make that be a GraphQL interface:
interface FormItem {
id: ID!
template: FormTemplate
}
Then you can have three separate types for your three kinds of items
# Skipping VideoItem
type ImageItem implements FormItem {
id: ID!
template: FormTemplate
src: String!
}
type QuestionItem implements FormItem {
id: ID!
template: FormTemplate
questions: [FormQuestion!]!
}
The other types you describe would be:
type FormTemplate {
id: ID!
author: Author!
name: String!
items: [FormItem!]!
}
type FormQuestion {
id: ID!
question: Question
type: String!
label: String!
}
The other tricky thing is, since not all form items are questions, you have to specifically mention that you're interested in questions in your query to get the question-specific fields. Your query might look like
query {
getFormTemplates {
name
items {
__typename # a GraphQL builtin that gives the type of this object
... on Question {
label
type
}
}
}
}
The ... on Question syntax is an inline fragment, and you can similarly use it to pick out the fields specific to other kinds of form items.
Thank you David for your answer !
I've figured it out how to solve my problem using inline fragments and UnionTypes that seems to be the most adapted for this use case. Here is the code :
const formItemObjectType = new GraphQLUnionType({
name: 'FormItemObject',
types: [formItemContent, formItemQuestion],
resolveType(parent) {
switch (parent.type) {
case ('question'): return formItemQuestion
default: return formItemContent
}
}
})
and the GraphQL query using inline fragment:
query {
getFormTemplates {
name
items {
...on FormItemContent {
type,
meta
}
...on FormItemQuestion {
type,
meta,
question {
label
}
}
}
}
}

GraphQL showing Value as null [duplicate]

This question already has answers here:
Why does a GraphQL query return null?
(6 answers)
Closed 3 years ago.
I am learning GraphQL and I have two Object types.
Say, they look like this
Say, The book type looks like this
const BookType = new GraphQLObjectType({
name: 'Book',
fields: () => ({
id: { type: GraphQLID},
name: { type: GraphQLString},
genre: { type: GraphQLString },
author: {
type: authorType,
resolve(parents, args) {
Author.findOne(
{
name: parents.authorName
}, function(err, result) {
console.log(result)
return result
})
}
}
})
})
and Author Type looks like this
const authorType = new GraphQLObjectType({
name: 'author',
fields: () => ({
id: { type: GraphQLID},
name: { type: GraphQLString},
age: { type: GraphQLInt },
books: {
type: new GraphQLList(BookType),
resolve(parent, args) {
}
}
})
})
Now, I am adding data through Mutation (Not sharing it because I think it is irrelevant) and then run query in graphql to add data in Book Type. It correctly displays data for name, genre, id but for authorType it is showing the data as null while the console].log results log something like this in console
//This is console log in terminal
{ age: 'none',
_id: 5bcaf8904b31d50a2148b60d,
name: 'George R Martin',
__v: 0 }
THis is the query I am running in graphiql
mutation{
addBooks(
name: "Game of Thrones",
genre: "Science Friction",
authorName: "George R Martin"
) {
name,
genre,
author {
name
}
}
}
My entire schema is available here
Can someone please-please help me figure out what could I be doing wrong?
A resolver must return either some value or a Promise that will resolve in a value -- if it doesn't the field being resolved will return null. So there's two things off about your code. One, you don't return either a value or a Promise. Two, you return something inside a callback, but that's not actually doing anything, since most libraries disregard the return value of a callback function anyway.
You can wrap a callback in a Promise, but that is going to be overkill here because mongoose already provides a way to return a Promise -- just omit the callback entirely.
resolve(parent, args) {
return Author.findOne({name: parent.authorName)
}
Your mutation resolver works because you return the value returned by calling save(), which actually returns a Promise that will resolve to the value of the model instance being saved.

How to add a validator to an existing collection via node.js mongodb driver?

Here is a code where I'm trying to add a validator to an existing collection.
const { MongoClient } = require("mongodb")
const schema = {
$jsonSchema: {
bsonType: "object",
additionalProperties: false,
required: ["name"],
properties: {
_id: {
bsonType: "objectId"
},
name: {
bsonType: "string"
}
}
}
}
const main = async () => {
const client = await MongoClient.connect(
"mongodb://localhost",
{ useNewUrlParser: true }
)
const db = client.db("t12")
// await db.createCollection("test", { validator: schema })
await db.createCollection("test")
await db.admin().command({ collMod: "test", validator: schema })
await db.collection("test").createIndex({ name: 1 }, { unique: true })
await db.collection("test").insertOne({ name: "t1" })
await db.collection("test").insertOne({ value: "t2" }) // should fail
const all = await db
.collection("test")
.find({})
.toArray()
console.log(all)
await client.close()
}
main().catch(err => console.error(err))
It fails:
max7z#mbp t12__npm__mongodb (master)*$ node test/1.js
{ MongoError: ns does not exist
at /Users/max7z/projects/t/t12__npm__mongodb/node_modules/mongodb-core/lib/connection/pool.js:581:63
at authenticateStragglers (/Users/max7z/projects/t/t12__npm__mongodb/node_modules/mongodb-core/lib/connection/pool.js:504:16)
at Connection.messageHandler (/Users/max7z/projects/t/t12__npm__mongodb/node_modules/mongodb-
ok: 0,
errmsg: 'ns does not exist',
code: 26,
codeName: 'NamespaceNotFound',
name: 'MongoError',
[Symbol(mongoErrorContextSymbol)]: {} }
^C
If I create the collection with that schema it works, but when I'm trying to add a vatidator via collMod, it fails.
How to add a validator to an existing collection via collMod command?
I created a function like
const updateValidator = async (collectionName, newValidator) => {
return db.command({
collMod: collectionName,
validator: newValidator,
validationLevel: "moderate",
validationAction: "warn"
});
}
The problem with db.command is that is replaces the whole validation schema. Therefore you need access to the current schema of the collection. As I did not found the function db.getCollectionInfos in the nodejs library I added the posibility of passing it as a parameter.
In my case I get it from other migration module with a require. E.g.
const currentValidator = require("migration-file-where-I-defined-the-previous-schema").schema.validator;
In the required file I have some initial schema like:
module.exports.schema = {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["name"],
properties: {
name: {
bsonType: "string",
maxLength: 300,
minLength: 3,
description: "Must be a string and is required"
},
created: {
bsonType: "date",
description: "Date when it was created"
},
deleted: {
bsonType: "date",
description: "Date when it was deleted"
}
}
},
}
};
Then I create a merge of the new schema and that will be enough. E.g.
const updatedValidator = Object.assign({}, currentValidator);
updatedValidator.$jsonSchema.properties.new_attribX = {
enum: ["type1", "type2"],
description: "State of the tenant related to its life cycle"
};
updatedValidator.$jsonSchema.required.push('new_attribX');
updateValidator("mycollection", updatedValidator)
.then(next)
.catch(console.error);
This will replace the previous schema with the new one which has the changes applied. For me, this was enough, but bear in mind that when you have existing data that needs to be updated with new data, then you need to update them with something like
collection.updateMany({'new_attribX': {$exists : false}}, {$set: {'new_attribX': 'type1'}});
Which for that data which has not that attribute (new_attribX) it should add them kind of this initial default value: type1.
I hope it helps.
The problem was in that line:
await db.admin().command({ collMod: "test", validator: schema })
The right way to do it:
await db.command({ collMod: "test", validator: schema })

Resources