GraphQL simple nested query returning null field - node.js

I'm trying to learn GraphQL (& node.js & MongoDB etc.). I cant get this simple nested query to return results :
query getLocationByPerson {
People {
firstName
lastName
service {
location
}
}
}
The result I get is :
{
"data": {
"People": [
{
"firstName": "John",
"lastName": "DOE",
"service": null
}
]
},
"errors": [
{
"message": "Cannot return null for non-nullable field Service.location.",
"locations": [
{
"line": 6,
"column": 7
}
],
"path": [
"People",
0,
"service",
"location"
]
}
]
}
All the code is available here : https://github.com/fabricezerrouki/graphql-playground
Can anyone have a look and help me to figure out what I'm doing wrong?
Thanks.

The problem lies in the way you're setting the service attribute when you create and update a person. For nested types, you want to pass an object containing the subtypes. Right now you're passing it just a string, so when you try to set Service equal to args.serviceId in your createPerson resolver, it doesn't exist, and therefore sets the value of that field to null in MongoDB. Re-factor like so:
In index.js
type Mutation {
createPerson(Xid: String!, firstName: String */ ... */ service: { id: ID!, name: String!, location: String! }): People!
}
In /resolvers/Mutation.js
const Mutations = {
createPerson: async (parent, args) => {
const newPerson = new People({
Xid: args.Xid,
firstName: args.firstName
/* and so on... */
Service: args.service.id
});
}
}
Also, you may run into problems in the future given the way your saving the Service object to MongoDB as a Capital "S" Service, whereas your variable in the queries is lowercase "s" service. You'll have to remember any time you search the database to now directly destructure the the args.service into your Model.find({service}) method. Kind of a niche problem, but I've ran into that exact problem myself, so figured I would mention it.
Edited in response to OP comment
Yes it is possible to do an SQL join-link operation to return the Service object with the Person query, but you'll need to to that in the query resolver and just store the Service id during the mutation. In that case I guess the reason it's null is your calling args.serviceId but the createPerson() parameter is simply "service" so try changing Mutation.js to Service: args.service.
As for the actual join operation, that's a bit more complicated. I would recommend first returning the serviceId string to make sure that works correctly. Then you'll need to delve into the MongoDB aggregation pipeline. More specifically, the $lookup pipeline operator performs a function similar to an SQL join. I end up banging my head against the wall every time I have to deal with aggregation pipelines. It's a pretty advanced subject with a confusing syntax IMO. But there are plenty of tutorials and examples out there for doing exactly what your wanting.

Related

Search string value inside an array of objects inside an object of the jsonb column- TypeORM and Nest.js

the problem I am facing is as follows:
Search value: 'cooking'
JSON object::
data: {
skills: {
items: [ { name: 'cooking' }, ... ]
}
}
Expected result: Should find all the "skill items" that contain 'cooking' inside their name, using TypeORM and Nest.js.
The current code does not support search on the backend, and I should implement this. I want to use TypeORM features, rather than handling it with JavaScript.
Current code: (returns data based on the userId)
const allItems = this.dataRepository.find({ where: [{ user: { id: userId } }] })
I investigated the PostgreSQL documentation regarding the PostgreSQL functions and even though I understand how to create a raw SQL query, I am struggling to convert this to the TypeORM equivalent.
Note: I researched many StackOverflow issues before creating this question, but do inform me If I missed the right one. I will be glad to investigate.
Can you help me figure out the way to query this with TypeORM?
UPDATE
Let's consider the simple raw query:
SELECT *
FROM table1 t
WHERE t.data->'skills' #> '{"items":[{ "name": "cooking"}]}';
This query will provide the result for any item within the items array that will match exact name - in this case, "cooking".
That's totally fine, and it can be executed as a raw request but it is certainly not easy to maintain in the future, nor to use pattern matching and wildcards (I couldn't find a solution to do that, If you know how to do it please share!). But, this solution is good enough when you have to work on the exact matches. I'll keep this question updated with the new findings.
use Like in Where clause:
servicePoint = await this.servicePointAddressRepository.find({
where: [{ ...isActive, name: Like("%"+key+"%"), serviceExecutive:{id: userId} },
{ ...isActive, servicePointId: Like("%"+key+"%")},
{ ...isActive, branchCode: Like("%"+key+"%")},
],
skip: (page - 1) * limit,
take: limit,
order: { updatedAt: "DESC" },
relations:["serviceExecutive","address"]
});
This may help you! I'm matching with key here.

Why does graphql not accept the parameters that I pass in the query?

I'm doing a practice with this library https://www.npmjs.com/package/json-graphql-server, I'm just trying to have the correct queries to be able to make a crud from the frontend but I don't understand how to add a new post since it does not accept it as a parameter of the query variables. apparently it does not recognize the post parameter that I am sending it, but on the right side of the documentation I have put in that variable all the fields that are required
mutation addPst($post: PostInput!){
createPost(post: $post) {
title
}
}
Query variables:
{
"post": {
"id": "100",
"title": "Curso de GraphQL",
"views": 0,
"user_id": 123
}
}
Example:
https://stackblitz.com/edit/json-graphql-server-nmctdj
Your mutation doesn't seem to be in line with the schema documentation you have posted.
The schema shows that the createPost mutation takes title, views and ID fields whereas you're passing in a "post" object.
Try rewriting the mutation as:
mutation addPst($title: String!, $views: Int!, $user_id: ID!){
createPost(title: $title, views: $views, user_id: $user_id) {
title
}
}

MongoDB nested array update multiple documents [duplicate]

I am trying to update a value in the nested array but can't get it to work.
My object is like this
{
"_id": {
"$oid": "1"
},
"array1": [
{
"_id": "12",
"array2": [
{
"_id": "123",
"answeredBy": [], // need to push "success"
},
{
"_id": "124",
"answeredBy": [],
}
],
}
]
}
I need to push a value to "answeredBy" array.
In the below example, I tried pushing "success" string to the "answeredBy" array of the "123 _id" object but it does not work.
callback = function(err,value){
if(err){
res.send(err);
}else{
res.send(value);
}
};
conditions = {
"_id": 1,
"array1._id": 12,
"array2._id": 123
};
updates = {
$push: {
"array2.$.answeredBy": "success"
}
};
options = {
upsert: true
};
Model.update(conditions, updates, options, callback);
I found this link, but its answer only says I should use object like structure instead of array's. This cannot be applied in my situation. I really need my object to be nested in arrays
It would be great if you can help me out here. I've been spending hours to figure this out.
Thank you in advance!
General Scope and Explanation
There are a few things wrong with what you are doing here. Firstly your query conditions. You are referring to several _id values where you should not need to, and at least one of which is not on the top level.
In order to get into a "nested" value and also presuming that _id value is unique and would not appear in any other document, you query form should be like this:
Model.update(
{ "array1.array2._id": "123" },
{ "$push": { "array1.0.array2.$.answeredBy": "success" } },
function(err,numAffected) {
// something with the result in here
}
);
Now that would actually work, but really it is only a fluke that it does as there are very good reasons why it should not work for you.
The important reading is in the official documentation for the positional $ operator under the subject of "Nested Arrays". What this says is:
The positional $ operator cannot be used for queries which traverse more than one array, such as queries that traverse arrays nested within other arrays, because the replacement for the $ placeholder is a single value
Specifically what that means is the element that will be matched and returned in the positional placeholder is the value of the index from the first matching array. This means in your case the matching index on the "top" level array.
So if you look at the query notation as shown, we have "hardcoded" the first ( or 0 index ) position in the top level array, and it just so happens that the matching element within "array2" is also the zero index entry.
To demonstrate this you can change the matching _id value to "124" and the result will $push an new entry onto the element with _id "123" as they are both in the zero index entry of "array1" and that is the value returned to the placeholder.
So that is the general problem with nesting arrays. You could remove one of the levels and you would still be able to $push to the correct element in your "top" array, but there would still be multiple levels.
Try to avoid nesting arrays as you will run into update problems as is shown.
The general case is to "flatten" the things you "think" are "levels" and actually make theses "attributes" on the final detail items. For example, the "flattened" form of the structure in the question should be something like:
{
"answers": [
{ "by": "success", "type2": "123", "type1": "12" }
]
}
Or even when accepting the inner array is $push only, and never updated:
{
"array": [
{ "type1": "12", "type2": "123", "answeredBy": ["success"] },
{ "type1": "12", "type2": "124", "answeredBy": [] }
]
}
Which both lend themselves to atomic updates within the scope of the positional $ operator
MongoDB 3.6 and Above
From MongoDB 3.6 there are new features available to work with nested arrays. This uses the positional filtered $[<identifier>] syntax in order to match the specific elements and apply different conditions through arrayFilters in the update statement:
Model.update(
{
"_id": 1,
"array1": {
"$elemMatch": {
"_id": "12","array2._id": "123"
}
}
},
{
"$push": { "array1.$[outer].array2.$[inner].answeredBy": "success" }
},
{
"arrayFilters": [{ "outer._id": "12" },{ "inner._id": "123" }]
}
)
The "arrayFilters" as passed to the options for .update() or even
.updateOne(), .updateMany(), .findOneAndUpdate() or .bulkWrite() method specifies the conditions to match on the identifier given in the update statement. Any elements that match the condition given will be updated.
Because the structure is "nested", we actually use "multiple filters" as is specified with an "array" of filter definitions as shown. The marked "identifier" is used in matching against the positional filtered $[<identifier>] syntax actually used in the update block of the statement. In this case inner and outer are the identifiers used for each condition as specified with the nested chain.
This new expansion makes the update of nested array content possible, but it does not really help with the practicality of "querying" such data, so the same caveats apply as explained earlier.
You typically really "mean" to express as "attributes", even if your brain initially thinks "nesting", it's just usually a reaction to how you believe the "previous relational parts" come together. In reality you really need more denormalization.
Also see How to Update Multiple Array Elements in mongodb, since these new update operators actually match and update "multiple array elements" rather than just the first, which has been the previous action of positional updates.
NOTE Somewhat ironically, since this is specified in the "options" argument for .update() and like methods, the syntax is generally compatible with all recent release driver versions.
However this is not true of the mongo shell, since the way the method is implemented there ( "ironically for backward compatibility" ) the arrayFilters argument is not recognized and removed by an internal method that parses the options in order to deliver "backward compatibility" with prior MongoDB server versions and a "legacy" .update() API call syntax.
So if you want to use the command in the mongo shell or other "shell based" products ( notably Robo 3T ) you need a latest version from either the development branch or production release as of 3.6 or greater.
See also positional all $[] which also updates "multiple array elements" but without applying to specified conditions and applies to all elements in the array where that is the desired action.
I know this is a very old question, but I just struggled with this problem myself, and found, what I believe to be, a better answer.
A way to solve this problem is to use Sub-Documents. This is done by nesting schemas within your schemas
MainSchema = new mongoose.Schema({
array1: [Array1Schema]
})
Array1Schema = new mongoose.Schema({
array2: [Array2Schema]
})
Array2Schema = new mongoose.Schema({
answeredBy": [...]
})
This way the object will look like the one you show, but now each array are filled with sub-documents. This makes it possible to dot your way into the sub-document you want. Instead of using a .update you then use a .find or .findOne to get the document you want to update.
Main.findOne((
{
_id: 1
}
)
.exec(
function(err, result){
result.array1.id(12).array2.id(123).answeredBy.push('success')
result.save(function(err){
console.log(result)
});
}
)
Haven't used the .push() function this way myself, so the syntax might not be right, but I have used both .set() and .remove(), and both works perfectly fine.

Mongoose.js - I can't get the ID of a Mongoose.Model instance (MongoDB Document)

I'm doing a simple Mongoose.Model instance find() which returns a a random document from an employee collection. The thing is, I can't seem to be able to get it's ID. When I see the document contents with MongoLab, I get the following:
{
"_id": {
"$oid": "554cef2fb6bb56cc421dd47c"
},
"name": "Bill Gates",
"role": "Software Engineer Associate",
"age": 21,
"hardware": [],
"__v": 0
}
But when I try to get the ID with commands on the employee instance that came back like employee._doc._id I get the following:
Object {_bsontype: "ObjectID", id: "ULï/¶»VÌBÔ|"}
What's going on? I've debugged my code and tried to find that _id property but it's just nowhere to be found.
You should probably include more code snippets in your questions so that it's a bit easier to identify where your flaw lies, but here is an example of how your code should look.
Your model should be exported from the schema:
var Employee = Storage.model('Employee', EmployeeSchema);
And your query should look something like this:
Employee.findOne(<--your query here-->).exec(function(err, employee) {
if (err) {
//always handle errors here :)
}
if (employee) {
//You can access the objectId of the employee like
console.log(employee._id);
}
});

Adding objects to a deep array in MongoDB

I've just started building a little application using MongoDB and can't seem to find any examples where I can add objects to a deep array that I can then find on an individual basis.
Let me illustrate by the following set of steps I take as well as the code I've written.
I create a simple object in MongoDB like so:
testing = { name: "s1", children: [] };
db.data.save(testing);
When I query it everything looks nice and simple still:
db.data.find();
Which outputs:
{
"_id" : ObjectId("4f36121082b4c129cfce3901"),
"name" : "s1",
"children" : [ ]
}
However, after I update the "children" array by "pushing" an object into it, I get into all sorts of problems.
First the update command that I run:
db.data.update({ name:"s1" },{
$push: {
children: { name:"r1" }
}
});
Then when I query the DB:
db.data.find({
children: { name: "r1" }
});
Results in:
{
"_id" : ObjectId("4f36121082b4c129cfce3901"),
"children" : [ { "name" : "r1" } ],
"name" : "s1"
}
Which doesn't make any sense to me, since I would have expected the following:
{
"name": "r1"
}
Is there a better way of inserting data into MongoDB so that when I run queries I extract individual objects rather than the entire tree? Or perhaps a better way of writing the "find" query?
By default mongodb find retrieve all the fields(like * from in sql). You can extract the particular field by specifying the field name
.
db.data.find({ "children.name": "r1" },'children.name');
Why would you expect ot to return only part of a document? It returns the whole document unless you tell it which fields you want to explicitly include or exclude. See http://www.mongodb.org/display/DOCS/Retrieving+a+Subset+of+Fields

Resources