Nested Mongoose queries data not showing on GraphQL query - node.js

I'm trying to create an API using Apollo-server with GraphQL and Mongoose.
My problem is that the query to Mongoose does return the data, but the GraphQL model shows as null when I test.
I've tried different methods and using promises.
type Articulo {
id: ID
nombre: String
codigoDeBarras: String
codigo: String
}
type EntradaInventario {
id: ID
articulo: Articulo
inventario: Inventario
cantidad: Float
}
type Almacen {
id: ID
nombre: String
}
type Inventario {
id: ID
almacen: Almacen
nombre: String
}
type Query {
articulo(codigoDeBarras: String): Articulo
entradaInventario(inventario: String): [EntradaInventario]
}
type Mutation {
addEntradaInventario(idArticulo: String, idInventario: String, cantidad: Float): EntradaInventario
addAlmacen(nombre: String): Almacen
addInventario(idAlmacen: String, nombre: String): Inventario
}
const EntradaInventarioModel = Mongoose.model("EntradaInventario", {
_id: Schema.Types.ObjectId,
idArticulo: {type: Mongoose.Schema.Types.ObjectId, ref: 'Articulo'},
idInventario: {type: Mongoose.Schema.Types.ObjectId, ref: 'Inventario'},
cantidad: Number
}, "entradainventarios");
Query: {
articulo: (_, args) => ArticuloModel.findOne({
'codigoDeBarras': args.codigoDeBarras
}).exec(),
entradaInventario: (_, args) =>
EntradaInventarioModel.find({idInventario: args.inventario})
.populate('idArticulo')
.exec(),
}

You shouldn't use the populate on the Parent model. You should instead define how to query the nested model in the EntradaInventario resolvers.
Inventory should look something like this:
Inventory : {
articulo: async (parent, args, { }) => {
return await Articulo.findOne({
_id: parent.idArticulo,
});
},
}
Here is a repo that does just that and is a good example https://github.com/the-road-to-graphql/fullstack-apollo-express-mongodb-boilerplate

Related

Why this code is creating two similar object in mongodb?

Node js
The method used to post the comment data
const { type, movieId, userId, review, timestamp } = req.body;
const movie = await Movie.findOneAndUpdate(
{ movieId: movieId, type: type },
{
$push: {
movieReview: { ... },
},
},
{ upsert: true, new: true },
(err, info) => {
...
}
);
Reactjs
The method used to submit the comment
const submit_comment = async () => {
....
const file = {
movieId: id,
type: type,
userId: userInfo._id,
comment: comment,
};
if (!commentFlag) {
const { data } = await axios.post("/api/movie/add-comment", file, config);
...
};
Mongoose Schema
const movieSchema = new mongoose.Schema({
movieId: String,
type: String,
...
comment: [{ type: mongoose.Schema.Types.ObjectId, ref: "Comment" }],
});
After I run my submit function it posts two objects with the same object _id in mongoDB

Joining collections in mongodb with node js

I have two collections in mongodb "components" and "airframes" which I am trying to join together (with a one to many relationship). I have the following code which gets the airframe and component data separately from the database, however after days of effort, I cannot figure out how to join the two together. I assume I need to use $lookup to achieve the desired result but any assistance in constructing the code would be greatly appreciated.
my models are as follows and I am trying to join all the component records under the associated Airframe. the airframe field on the Component holds the related Airframes' id.
const airframeSchema = mongoose.Schema({
name: { type: String, required: true },
sNumber: { type: String, required: true },
aStatus: { type: String, required: true },
components: [ {
type: mongoose.Schema.Types.ObjectId,
ref: 'Component' } ]
});
module.exports = mongoose.model('Airframe', airframeSchema);
const componentSchema = mongoose.Schema({
name: { type: String, required: true },
serial: { type: String, required: true },
type: { type: String, required: true },
airFrame: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'Airframe'},
});
module.exports = mongoose.model('Component', componentSchema);
the AirFramesService is as follow. I would like to join the component data under a array called "component".
getAirframes() {
this.http
.get<{ message: string; airframes: any }>("http://3.135.49.46:8080/api/airframes")
.pipe(
map(airframeData => {
return airframeData.airframes.map(airframe => {
return {
name: airframe.name,
sNumber: airframe.sNumber,
aStatus: airframe.aStatus,
id: airframe._id,
};
});
})
)
.subscribe(transformedAirframes => {
this.airframes = transformedAirframes;
this.airframesUpdated.next([...this.airframes]);
});
}
getAirframeUpdateListener() {
return this.airframesUpdated.asObservable();
}
getAirframe(id: string) {
return this.http.get<{ _id: string; name: string; sNumber: string ; aStatus: string}>(
"http://3.135.49.46:8080/api/airframes/" + id
);
}
The airframes route code is as follows:
router.get("", (req, res, next) => {
Airframe.find().then(documents => {
res.status(200).json({
message: "Airframes fetched successfully!",
airframes: documents
});
});
});
and here is the code within the ts component file that gets the airframe data is as follows.
constructor( public airframesService: AirframesService) {
this.airframesService.getAirframes();
this.airframesSub = this.airframesService.getAirframeUpdateListener()
.subscribe((airframes: Airframe[]) => {
this.isLoading = false;
this.airframes = airframes;
}, 0);
});
}
the desired outcome would be the following (at the moment I only get the airframe data):
{
_id: "123"
name: "Airframe01"
sNumber: "757"
aStatus: "Active"
id: "5e8052ad1fa18f1c73524664"
components: [
{
name: "Left Tank",
serial: "3456789",
type: "Landing Gear",
airFrame: "5e8052ad1fa18f1c73524664"
},
{
name: "Right Tank",
serial: "45678",
type: "Landing Gear",
airFrame: "5e8052ad1fa18f1c73524664"
}
]
}
Your document structure already established a one-to-many kinda relationship between the two models, you can use Mongoose population to get the join you described in the question. The populate code should be somewhere in the airframes route like this:
router.get("", (req, res, next) => {
// Notice the populate() method chain below
Airframe.find().populate('components').then(documents => {
// The documents output should have their "components" property populated
// with an array of components instead of just Object IDs.
res.status(200).json({
message: "Airframes fetched successfully!",
airframes: documents
});
});
});
You can read more about Mongoose population here.

How do I query nested documents related by ObjectID with GraphQL

USER has a field 'groups' which is an array of objectID's corresponding to GROUP
GROUP has a field 'boards' which is an array of objectID's corresponding to BOARD
BOARD has a field 'posts' which is an array of objectID's corresponding to POST
POST has a field 'comment' which is an array of objectID's corresponding to COMMENT
My goal here is to be able to query GrapQL like so:
Query{
user('_id'){
username
groups{
name
boards{
name
posts{
comment{
text
}
}
}
}
}
}
So by only supplying the query with the user's ID, I can get all the groups, boards, posts, and comments relative to the user.
My Type Defs are as follows:
type Query {
user(_id: String): User
groups(_id: String): [Group]
boards(_id: String): [Board]
posts(_id: String): [Post]
comments(_id: String): [Comment]
}
type User {
_id: String,
username: String,
email: String,
Groups: [Group]
}
type Group {
_id: String,
name: String,
bio: String,
owner: User
members: [User]
boards: [Board]
}
type Board {
_id: String,
name: String,
bio: String,
group: Group
posts: [Post]
}
type Post {
_id: String,
name: String,
board: Board,
comments: [Comment]
}
type Comment {
_id: String
text: String
}
schema {
query: Query
}
My resolvers (where I'm sure the problem lies):
const resolvers = {
Query: {
user: async (root, { _id }) => {
return prepare(await Users.findOne(ObjectId(_id)))
},
groups: async (root, { _id }) => {
return prepare(await Groups.findOne(ObjectId(_id)))
},
boards: async (root, { _id }) => {
return prepare(await Boards.findOne(ObjectId(_id)))
},
posts: async (root, { _id }) => {
return prepare(await Posts.findOne(ObjectId(_id)))
},
comments: async (root, { _id }) => {
return prepare(await Coments.findOne(ObjectId(_id)))
}
},
User: {
groups: async ({_id}) => {
return (await Groups.find({_id: _id}).toArray()).map(prepare)
}
}
}
Basically I'm trying to makle a big, beefy, nested query with a single call. All the models are related by objectID's. Does anyone have any input on how to proceed here or perhaps some links to related documentation? I've searched all over, but can't seem to find docs related to this specifically. Querying related documents by objectID.

graphql.js: field is null even though resolver is returning value for sequilize query

I have the following graphql schema and I am trying to execute businessUser query
type Query {
businessUser(id: String!): BusinessUser
}
type BusinessUser {
id: ID!,
firstName: String,
lastName: String,
email: String,
posts: [BusinessPostFormat]
}
type BusinessPostFormat {
postId: Int,
user: String,
isActive: Boolean,
postText: String
}
In my resolver I am joining my Post table with my businessPost table to retrieve a 'isActive' column. When I print out what Sequilize is returning I see that I am getting back exactly the data object that should map to the BusinessPostFormat type:
post {
dataValues:
{ postId: '188',
postText: 'blah blah blah',
user: 'userName',
isActive: false },
However when I execute the query in graphiql the isActive field is coming back as null. I dont understand why thats happening if I can see that the resolver is returning exactly what BusinessPostFormat type is expecting?
resolver function below:
const resolvers = {
Query: {
businessUser(_, args) {
return BusinessUser.findById(args.id);
}
},
BusinessUser: {
posts(businessUser) {
return businessUser.getPosts({
attributes: {
include: [[Sequelize.literal('business_posts.is_active'), 'isActive']],
exclude: ['business_user_id']
},
include: [{ model: BusinessPost, attributes: ['is_active'] }]
})
},
}
Maybe you are missing the fact that Sequelize uses instances http://docs.sequelizejs.com/manual/tutorial/instances.html and maybe that is the missing fact in this case.
Try it like this.
return Entries.findAll().then(entries => {
return entries.map(e => e.get({ plain: true }));
})

mongoose $or with mixed string and _id (ObjectId) not working

I am trying to make a method to fetch a "page" from the document base where the query matches _id or permalink.
The below code example returns a mongoose error:
'Cast to ObjectId failed for value "hello-world" at path "_id" for model "pages"'
Now, obviously the query isn't an ObjectId if the case is 'hello-world' or any other string permalink. So how do I go about using $or in this case, or is there a smarter way to go about it?
/**
* Describes methods to create, retrieve, update, and delete pages
* #returns void
*/
function Pages() {
this.pages = require('../models/pages')
this.users = require('../models/users')
require('mongoose').connect(require('../../config/database/mongodb').url)
}
/**
* Retrieve a page by permalink or id
* #param {string} pageQuery - id or permalink
* #callback {function} cFunction
*/
Pages.prototype.getOne = function(pageQuery, cFunction) {
this.pages.findOne({$or: [{ 'permalink': pageQuery }, { '_id': pageQuery }] })
.populate('author', 'email')
.select('title permalink body author')
.exec(function(error, result) {
if (error) {
cFunction(error)
return
}
cFunction(result)
})
}
Pages model
const mongoose = require('mongoose'),
Schema = mongoose.Schema,
ObjectId = Schema.ObjectId,
pages = new Schema({
title: { type: String },
permalink: { type: String, unique: true },
body: { type: String },
author: { type: ObjectId, ref: 'users' },
createdAt: { type: Date },
revisedAt: { type: Date }
})
.index({
title: 'text',
permalink: 'text',
body: 'text'
})
module.exports = mongoose.model('pages', pages)
Users model
const mongoose = require('mongoose'),
Schema = mongoose.Schema,
ObjectId = Schema.ObjectId,
users = new Schema({
email: { type: String, unique: true },
username: { type: String, unique: true },
password: { type: String },
createdAt: { type: Date }
})
.index({
email: 'text',
username: 'text'
})
module.exports = mongoose.model('users', users)
It looks like if you run new ObjectId(pageQuery) and it's not a valid ObjectId, it will throw an error telling you that (i.e. Error: Argument passed in must be a single String of 12 bytes or a string of 24 hex characters.)
In saying that, I would just use a try/catch block at the beginning of Pages.prototype.getOne to try and cast a pageQueryOid variable, and if you get to the catch block you know it's because pageQuery is not a valid ObjectId.
Using this method you no longer need an $or filter, but can just build your exact filter based on if pageQuery is a valid ObjectId. The following is just an example of what this might look like, but you can update it to meet your needs:
Pages.prototype.getOne = function(pageQuery, cFunction) {
var ObjectId = require('mongoose').Types.ObjectId
var pageQueryOid
try {
pageQueryOid = new ObjectId(pageQuery)
} catch(err) {
console.log("pageQuery is not a valid ObjectId...")
}
var filter
if (pageQueryOid) {
filter = { '_id': pageQueryOid }
} else {
filter = { 'permalink': pageQuery }
}
this.pages.findOne(filter)
.populate('author', 'email')
.select('title permalink body author')
.exec(function(error, result) {
if (error) {
cFunction(error)
return
}
cFunction(result)
})
}

Resources