Is it possible to leverage both GraphQL and Mongoose?
So far, I have been able to integrate both GraphQL and Mongoose to handle populating the database, but I am struggling to understand how this can work to retrieve data, specifically data with nested references.
Consider this schema:
const fooSchema = new Schema({
name: { type: 'String', required: true },
bar: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Bar',
required: false,
}],
});
The Bar schema is essentially the same, with just a field for "name".
Is it possible to run a GraphQL query to populate the data with the references in 'bar'?
Currently, we are using GraphQL-Tools to create our typeDefs, Mutations, and Queries which looks something like this:
const typeDefs = `
type Foo {
name: String!,
bars:[Bar]
}
type Bar {
_id: ID,
name: String,
}
type Query {
allFoos: [Foo!]!
foo(_id: ID!): Foo
}
type Mutation {
...
}
`;
module.exports = makeExecutableSchema({typeDefs, resolvers});
And finally a query directive that looks like this:
const allFoos = async (root, data) => {
return await Foo.find({});
};
I am able to change the query directive to use .populate() to get Bar, but that does not actually end up populating the results, which I think is because of the way the typeDefs are set up.
So is it possible to make these two concepts work together? Does it even make sense to use them both?
As they describe GraphQL:
GraphQL is a query language for your API, and a server-side runtime
for executing queries by using a type system you define for your data.
GraphQL isn't tied to any specific database or storage engine and is
instead backed by your existing code and data.
Where as mongoose is
Writing MongoDB validation, casting and business logic boilerplate is
a drag. That's why we wrote Mongoose
Monogoose work with mongodb validation whereas graphql is a query language for the API.
You can read a basic example from here about Setting up a simple GraphQL Server with Node, Express and Mongoose.
These two are completely different. Mongoose work when you are performing any operation on database, whereas grapgl comes in picture when you call a API. Graphql validate your API input parameter and return parameter. If you are adding these two in single app. It will work well.
Mongoose will validate your db operation.
GraphQL will validate your API input and output parameter.
Related
Looking to prevent NoSQL injection attacks for a node.js app using mongodb.
var mongoose = require('mongoose'); // "^5.5.9"
var Schema = mongoose.Schema;
var historySchema = new Schema({
userId: {
type: String,
index: true,
},
message: {},
date: {
type: Date,
default: Date.now,
}
});
var history = mongoose.model('history', historySchema);
// the following is to illustrate the logic, not actual code
function getHistory(user){
history.find({userId: user}, function(err, docs) {
console.log(docs)
}
}
Based on this answer to a similar question, my understanding is that using mongoose and defining the field as string should prevent query injection. However, by changing the user input to a query object, it is possible to return all users. For example:
getHistory({$ne: 1}) // returns the history for all users
I am aware of other ways to prevent this type of attack before it gets to the mongoose query, like using mongo-sanitize. But I'd like to know if there's something wrong with the way I defined the schema or if one can't expect mongoose to convert inputs according to the schema.
Thanks in advance!
this part is good enough, you do not need anything else there. There is method that receives string and uses the string.
The best approach is to validate the input that can be modified (usually HTTP request) on top level before processing anything (I can recommend https://github.com/hapijs/joi its easy to use and you can check if there all required fields and if all fields are in correct format).
So put the validation into middleware just before it hits your controller. Or at the beginning of your controller.
From that point you are in full control of all the code and you believe what you got through your validation, so it cannot happen that someone pass object instead of string and get through.
Following the "skinny controllers, fat model" paradigm, it would be best to expose a custom validation schema from your model to be used in your controller for POST and PUT requests. This means that any data that attempts to enter your database will first be sanitized against a validation schema. Every Mongoose model should own its own validation schema.
My personal favorite for this is Joi. It's relatively simple and effective. Here is a link to the documentation: https://www.npmjs.com/package/#hapi/joi
A Joi schema permits type checking (i.e., Boolean vs. String vs. Number, etc), mandatory inputs if your document has the field required, and other type-specific enforcement such as "max" for numbers, enumerable values, etc.
Here is an example you'd include in your model:
const Joi = require('joi');
...
function validateHistory(history) {
const historySchema = {
userId: Joi.string(),
message: Joi.object(),
date: Joi.date()
}
return Joi.validate(history, historySchema);
}
...
module.exports.validate = validateHistory;
And then in your controller you can do:
const {
validate
} = require('../models/history');
...
router.post('/history', async (req, res) => {
const {
error
} = validate(req.body.data);
if (error) return res.status(400).send(error.details[0].message);
let history = new History({
userID: req.body.user,
message: req.body.message,
date: req.body.date
})
history = await history.save();
res.send(history);
});
*Note that in a real app this route would also have an authentication callback before handling the request.
I am trying to define my schema for my API. I am running into an issue where each resource has many different sections. Ideally I would like to just be able to say sections is a JSON object rather than define all the different modules within the sections. Is there a way I can do this? As far as I can tell, there doesn't seem to be a JSON type definition using graphql-tools
// Define your types here.
const typeDefs = `
type Resource {
id: ID,
title: String!,
slug: String!,
path: String!,
template: String!,
published: String!,
sections: Sections
}
type Sections {
// ...
}
type Query {
allLinks(filter: LinkFilter): [Link!]!
findResource(filter: ResourceFilter): [Resource!]!
}
`;
You'll need to import a custom JSON scalar. This module is one of the more popular ones available.
Anywhere in your typeDefs, add the following line:
scalar JSON
And then inside the resolvers object you pass to makeExecutableSchema:
const GraphqlJSON = require('graphql-type-json')
const resolvers = {
Query: //queries
Mutation: //mutations
// All your other types
JSON: GraphqlJSON
}
One word of warning: anytime you use JSON as a scalar, you lose flexibility. Part of the charm of GraphQL is that it allows clients to only query the fields they need. When you use JSON, your client will only be able to either query the whole chunk of JSON, or not at all -- they won't be able to pick and choose parts of it.
So I've decided to use graphql as my query engine along side with mongodb. So I created my schemas and everything looks great, BUT, one of my schemas contains a list of Strings, for instance:
exports.default = new gql.GraphQLInputObjectType({
name: 'myModel',
fields: {
type: { type: gql.GraphQLString },
workingDays: { type: new gql.GraphQLList(GraphQLString) }
}
});
So in the workingDays list I have 50 elements, and I'd like to change one of them, is there a way to do that with Graphql?
It just so happens to be a string type inside but, it could be an object as well.
Thanks.
You can add a new mutation that encodes this functionality.
For example updateWorkingDays(modelId: ID!, index: Int!, workDay: String) that updates the working day of model modelId at index to the new workDay.
I have a product model, it has many fields. Some of them are dedicated to front-end application, ex:
var GameSchema = new Schema({
likes: {
type: [{
type: Schema.ObjectId,
ref: 'User'
}]
},
likes_count: {
type: Number
}
});
I don't need likes_count field in Db, but controller returns only fields that model have, so i add likes_count field to db model
exports.some_method = function(req, res){
var game = req.game;
game.likes_count = game.likes.length
res.json(game);
}
Is there a way to add extra data to db model when sending request without having them in db?
Please note, problem is not in likes_count field itself, i have different models, but the point is having extra data on db model.
For those who still interested, mongo_db mongoose(#robertklep) has virtual fields, that can be used as temporary data field, that doesn't exist in database
GameSchema.virtual('likes_count').get(function () {
return this.likes.length;
});
And note, your model must have permission for virtuals like this, so that you can use it inside controllers
var UserSchema = new Schema({
username: {
type: String
}
}, {
toObject: { virtuals: true },
toJSON: { virtuals: true }
});
"Is there a way to add extra data to db model when sending request without having them in db?"
You may be able to do so from a driver's perspective and I'll leave that to those who know abut such things. Check out the following post Mapping a private backing field with MongoDB C#.
I can answer from the MongoDB engine & server processes aspect; if you are looking for a way to flag a field in the JSON document to make it private when sent to the actual CRUD request the MongoDB engine receives then no.
However, you could intercept the JSON prior to the actual CRUD request and transform it. The JSON you are generating is not inserted until you execute one of the INSERT, Modify, or Update statements. The pseudo steps would be to generate a JSON document, send it to a broker\wrapper etc in front of MongoDB, and then transform it by removing the fields in question, then send the new object as a CRUD request to the MongoDB engine.
I have the following schema in my Node js / express app where each warehouse can optionally have a parent warehouse. I wrote the code to save warehouse, but can't figure out how to retrieve any warehouse (which has a parent) and get its parent warehouse name...so was wondering if there is any way I can do that in one call? Thanks
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var warehouseSchema = new Schema({
name: { type: String, required: true },
parentID: { type: String, ref: 'Warehouse' }
});
module.exports = mongoose.model('Warehouse', warehouseSchema);
Just as Philipp said, you can't do it with a single MongoDB auery.
But you can do it with single Mongoose command, using its Query Population feature:
Warehouse.findById(warehouse_id).populate('parentID').exec(function(err, doc) {
if (err) {
// something get wrong
} else {
// doc.parentID is a parrent Warehouse here
}
})
Internally, Mongoose will make two separate queries to MongoDB (one for the child document, and then another for its parent), bu for you it'll look like a single command.
MongoDB can't do JOINs on the database. As long as the parent is a reference in the warehouse object, you can not retrieve both with one query. You first have to query the warehouse(s) and then resolve the reference(s) with a second query.
To avoid the second query you might want to look into embedding the required information about the parent-document in the child-document. Duplicate information in MongoDB isn't as abnormal as in a relational database.