Graphql apollo playground - how to add array of strings - mongodb

tldr: How do I write a mutation in GraphQL playground to add an array of strings when creating a new record in the database
I'm using a MongoDB database for an application. I always have trouble finding documentation for how to write queries and mutations in GraphQL apollo playground. In this example, let's take an example of an athlete. An athlete can win many awards. The awards are just an array of strings. I will use a simple model as an example...
const playerSchema = new Schema({
playerName: {
type: String,
required: true,
},
awards: [{
type: String,
}]
})
const Player = model('Player', playerSchema)
module.exports = Player;
Here is my typeDef for this model with Queries and Mutations
const typeDefs = gql`
type Player{
playerName: String!
awards: [String]
}
type Query {
players: [Player!]
}
type typeDefs {
addPlayer:(playerName: String!, awards:[String]
}
}`;
module.exports = typeDefs;
Here is my resolvers for this model
const resolvers = {
Query: {
players: async () => {
return await Player.find(populate('player');
}
Mutation: {
addPlayer: async( parent, {playerName, awards}) => {
return await Player.create({playerName, awards})
}
}
module.exports = resolvers;
Now, I start the server and go to GraphQL playground in the browser. I cannot add an array of strings for the awards. How do I write this query in GraphQL playground? Here is an example of my best attempt:
mutation addPlayer($playerName: String!, $awards:[String]){
addPlayer(playerName:$playerName, awards:$awards){
playerName
awards
}
}
and finally my query variables for this mutation
{
"playerName": "Michael Jordan",
"awards": ["Most Valuable Player", "Rookie of the Year", "Scoring Champion"]
}
If I run a query in GraphQL to see what players exist in the database:
query players{
players{
playerName
awards
}
}
This is the results. Why is the 'awards' array empty??
{
"data": {
"players": [
{
"playerName": "Michael Jordan",
"awards": [], //EMPTY ARRAY HERE, WHY?
}
]
}
}

Related

graphql.GraphQLSchema: what type of argument to use to grab the query to pass to mongo db.collection.find to resolve the query

I'm learning ho to develop GraphQL service with express, express-graphql, **graphql, mongoose,
db.collection.find has an optional query parameter that specifies selection filter using query operators.
I wonder if it is possible to define a schema in which to define an argument for a query field that ultimately it is passed as it is to the collection find methods.
for example I expect that the graphql query:
{ todosQuerable(query: {title: "Andare a Novellara"})
{ _id, title, completed }
}
responds with:
{
"data": {
"todos": [
{
"title": "Andare a Novellara",
"completed": false
}
]
}
}
since in mongo
> db.Todo.find({title: 'Andare a Novellara'})
{ "_id" : ObjectId("600d95d2e506988bc4430bb7"), "title" : "Andare a Novellara", "completed" : false }
I'm thinking something like:
todosQuerable: {
type: new graphql.GraphQLList(TodoType),
args: {
query: { type: <???????????????> },
},
resolve: (source, { query }) => {
return new Promise((resolve, reject) => {
TODO.find(query, (err, todos) => {
if (err) reject(err)
else resolve(todos)
})
})
}
}
I have made a few attempts but have not been able to get an idea of which type I should use in this case
ho help reproduce the problem here the source repository of my tests
Please note that this works fine:
todosByTitle: {
type: new graphql.GraphQLList(TodoType),
args: {
title: { type: graphql.GraphQLString },
},
resolve: (source, { title }) => {
return new Promise((resolve, reject) => {
TODO.find({title: {$regex: '.*' + title + '.*', $options: 'i'}}, (err, todos) => {
if (err) reject(err)
else resolve(todos)
})
})
}
}
but what I'm looking for is something more generic: I would like to grab graphql field argument named query and pass it as is to the the query parameter of the mongo collection find.
So the good news is you can do whatever you want. The bad news is that:
You have to do it yourself
You have to add every searchable field, so you'll probably end up with two copies of the Todo object here.
The type you're looking for is just a custom input object type like this:
Notice the GraphQLInputObjectType below is different from GraphQLObjectType.
var TodoQueryType = new graphql.GraphQLInputObjectType({
name: 'TodoQuery',
fields: function () {
return {
_id: {
type: graphql.GraphQLID
},
title: {
type: graphql.GraphQLString
},
completed: {
type: graphql.GraphQLBoolean
}
}
}
});
todosQuerable: {
...
type: new graphql.GraphQLList(TodoType),
...
args: {
query: { type: TodoQueryType },
},
...
}
These two queries work great!
(this is me using aliases so I can make the same query twice in one call)
{
titleSearch: todosQuerable(query:{ title:"Buy orange" }) {
_id
title
completed
}
idSearch: todosQuerable(query:{ _id:"601c3f374b6dcc601890048d" }) {
_id
title
completed
}
}
Footnote:
Just to have it said, this is generally a GraphQL anti-pattern, as this is building an API based on your database choices, rather than as a client-driven API.
Regex Edit as requested:
If you're trying to do regular expression lookups, you have to figure out how to programmatically convert your strings into regular expressions. i.e. your input is a string ("/Novellara/"), but mongoose requires passing a RegExp to do wildcards (/Novellara/, no quotes).
You can do that a number of ways, but I'll show one example. If you change your input fields to use two properties of value & isExpression, like below, you can do it, but you have to specifically craft your query, since it's no longer just a passthrough.
var ExpressableStringInput = new graphql.GraphQLInputObjectType({
name: 'ExpressableString',
fields: {
value: {
type: graphql.GraphQLString
},
isExpression:{
type: graphql.GraphQLBoolean,
defaultValue: false,
}
}
})
var TodoQueryType = new graphql.GraphQLInputObjectType({
name: 'TodoQuery',
fields: function () {
return {
_id: {
type: graphql.GraphQLID
},
title: {
type: ExpressableStringInput
},
completed: {
type: graphql.GraphQLBoolean
}
}
}
});
// resolver
todosQuerable: {
type: new graphql.GraphQLList(TodoType),
args: {
query: { type: TodoQueryType },
},
resolve: async (source, { query }) => {
const dbQuery = {};
if (query.title.isExpression) {
dbQuery.title = new RegExp(query.title.value);
} else {
dbQuery.title = query.title.value;
}
return new Promise((resolve, reject) => {
TODO.find(dbQuery, (err, todos) => {
if (err) reject(err)
else resolve(todos)
})
})
}
}
your query would then look like
query {
todosQuerable(query:{ title: { value: "Buy.*", isExpression: true }}) {
_id
title
completed
}
}
This query makes sense in my mind. If I think about the form you would show to a user, there is probably an input box and a checkbox that says "is this a regular expression?" or something, which would populate this query.
Alternatively, you could do like string matching: if the first and last characters are "/", you automagically make it into a regex before passing it into mongoose.

Why I'm I getting an error saving date using graphql > hasura > postgres

I'm using react, apollo, graphql, hasura, postgres as my stack to interact with the database. I think my issue is something small, so I'll just focus on the part that's not working rather than posting the whole code.
Thanks.
Error: GraphQL error: unexpected variables in variableValues: birthday
at new ApolloError (bundle.esm.js:63)
at Object.next (bundle.esm.js:1004)
at notifySubscription (Observable.js:135)
at onNotify (Observable.js:179)
at SubscriptionObserver.next (Observable.js:235)
at bundle.esm.js:866
at Set.forEach (<anonymous>)
at Object.next (bundle.esm.js:866)
at notifySubscription (Observable.js:135)
at onNotify (Observable.js:179)
at SubscriptionObserver.next (Observable.js:235)
at bundle.esm.js:76
variables{ id: 2324324, name: "Fred", birthday: "1991-01-11" }
If i remove birthday the query works.
Here is the function
const onUpdateUser = (options) => {
updateUser({
variables: Object.assign({ id: userId }, options),
optimisticResponse: {
__typename: "mutation_root",
update_users: {
__typename: "users_mutation_response",
affected_rows: 1,
returning: [
{
__typename: "users",
id: userId,
...options,
},
],
},
},
});
};
input {birthday: '1991-01-11'}
So without looking at your graphql query, I think you may be thinking of it a little bit off.
You can't dynamically add non-existent variables to a graphql query. The error is telling you that you are trying to add a variable that doesn't exist in your query
i.e. this with NOT work because you haven't defined birthday.
mutation updateUser(
$userId: Int!
$birthday (UNDEFINED)
) {
rest of query...
}
If you need to add a dynamic amount of variables, you could do something like this.
React Code
const onUpdateUser = (options) => {
updateUser({
variables: {
userId,
userVariables: options
},
optimisticResponse: {
__typename: "mutation_root",
update_users: {
__typename: "users_mutation_response",
affected_rows: 1,
returning: [
{
__typename: "users",
id: userId,
...options,
},
],
},
},
});
};
GraphQL mutation
mutation updateUser(
$userId: Int!
$userVariables: user_set_input!
) {
update_user(
where: { id: { _eq: $userId} }
_set: $userVariables
) {
affected_rows
}
}
https://hasura.io/docs/1.0/graphql/manual/mutations/update.html

The method findOneAndUpdate change the id of my element in a array

I'm working with mongoDB, mongoose and graphQL. I'm trying to make an update in my DB.
I'm doing an update in an array called phones, the changes work perfectly, the only problem is that when the update ends, the value of the objectId changes.
// Models -> Schema Organization
const organizationSchema = new mongoose.Schema({
name: String,
address: String,
phones: [
{
number: Number,
prefix: Number
}
],
email: String
})
// Types -> Organization
type Response {
success: Boolean!
token: String
errors: [Error]
}
type Error {
path: String!
message: String!
}
input iOrganization {
_id: ID
arrID: ID
address: String
email: String
number: Int
prefix: Int
name: String
}
type Mutation {
updateOrgGeneric(iOrg: iOrganization): Response!
}
// Resolvers -> Organization (1st way)
Mutation: {
updateOrgGeneric: (parent, args, {models}) => {
return models.Organization.findOneAndUpdate(
{ "_id": args.iOrg._id, "phones._id": args.iOrg.arrID },
{ $set: { "phones.$": { number: args.iOrg.number, prefix: args.iOrg.prefix }} },
{new: true}
)
.then((resp) => {
console.log(resp);
return {
success: true,
errors: []
}
})
.catch((error) => {
return {
success: false,
errors: error
};
})
},
}
// Resolvers -> Organization (2nd way)
Mutation: {
updateOrgGeneric: (parent, args, {models}) => {
return models.Organization.findOneAndUpdate(
{ "_id": args.iOrg._id },
{ $set: { "phones.$[arr]": { number: args.iOrg.number, prefix: args.iOrg.prefix }} },
{new: true}
{ arrayFilters:[{ "arr._id": mongoose.Types.ObjectId(args.iOrg.arrID) }], new: true}
)
.then((resp) => {
console.log(resp);
return {
success: true,
errors: []
}
})
.catch((error) => {
return {
success: false,
errors: error
};
})
}
}
// Playground (http://localhost:5000/graphql)
mutation {
updateOrgGeneric(
iOrg: {
_id: "5bdbee1b794b972bc8562aeb"
arrID: "5bdcea7cae88be098c020b19"
number: 85239,
prefix: 862
}
){
success
errors {
path
message
}
}
}
Both _id, as arrID, exist in the BD.
In the playground example the initial arrID was: _id:ObjectId("5bdcea7cae88be098c020b19"), but after the update is another, example: _id:ObjectId("5bdcec0a2ab78533b4bd1d98"). What am I doing wrong?
Thank you!
Mongodb is a nosql database which means that every object in the database should consist of an Id and revision values. Once an update occurs the revision value changes as part of the update process to implement the changes made to the data object. Since your data object don't have the revision value then the id value changes. Because it is unique. Now I'm no expert with mongo but you should check the docs on how to persist data objects and change accordingly
In case anyone lands here (despite this being old post), the problem probably lies in trying to update the entire phones object, of which the overwritten _id is a part. Since there's a model defined for phonesin mongoose, it will try to create a new _id any time an entire new phones object is created.
Someone who wanted to keep the same id would need to $set only the fields they want to change, rather than the entire object. So
{ $set: { "phones.$[arr]": { number: args.iOrg.number, prefix: args.iOrg.prefix }} }
could be changed to
{ $set: { "phones.$[arr].number": args.iOrg.number, "phones.$[arr].prefix": args.iOrg.prefix } }

Waterline: How to perform IN queries if attribute is a collection?

In the docs of waterline it is stated that this is the way to perform a IN query on a model:
Model.find({
name : ['Walter', 'Skyler']
});
And this the way to perform an OR query on a model:
Model.find({
or : [
{ name: 'walter' },
{ occupation: 'teacher' }
]
})
My problem now is that i need a combination of those two, and to make it even more complicated, one of the attributes i have to use is a collection.
So what i tried is this, but it doesn't seem to work:
Product.find({
or : [
{ createdBy: userIds },
{ likes: userIds }
]
})
Note: userIds is an array of id's from a user model.
The (simplified) product model looks likes this:
module.exports = {
attributes: {
name: 'string',
description: 'string',
createdBy: {
model: 'User'
},
brand: {
model: 'Brand',
},
likes: {
collection: 'User',
}
}
}
The query works when I only include createdBy, so it seems to be a problem with the collection attribute.
Is this somehow possible?
Thank you for your input.
UPDATE:
I think this is only possible with native() queries.
The way I understand it something like this should work.
Product.native(function(err, products){
if(err) return res.serverError(err);
products.find({"likes": { $elemMatch: { _id: { $in: userIds}}}}).toArray(function(err, results){
if (err){
console.log('ERROR', err);
}
else {
console.log("found products: " + results.length);
console.log(results);
return res.ok(results);
}
});
});
Unfortunately, it doesn't. The returned results is always an empty array.

Distinct Query with Cloudant Connector using Loopback in API Connect/StrongLoop

I am trying to get distinct values for a query using Loopback with a Cloudant Connector, but I haven't found anything about this in the documentation.
e.g. I need a query to turn this:
[
{
rating: "★★★★★"
},
{
rating: "★★★★★"
},
{
rating: "★★★★★"
},
{
rating: "★★★★★"
},
{
rating: "★★★☆☆"
},
{
rating: "★★★☆☆"
}
]
into this:
[
{
rating: "★★★★★"
},
{
rating: "★★★☆☆"
}
]
I'm using the REST API to query my Products model (above is a filtered view of just the rating field). If there is some sort of filter that I can use without modifying the server that I somehow just missed in the documentation, that would be the best choice.
Is there any way I can add a distinct field like:
/Products?filter[fields][rating]=true?distinct=true
or how can I go about solving this?
Also, I've seen another answer talking about adding a remote method to solve this (something like this for mySQL):
Locations.regions = function (cb) {
var ds = Locations.app.datasources.myDS;
var sql = "SELECT DISTINCT region FROM Locations ORDER BY region"; // here you write your sql query.
ds.connector.execute(sql, [], function (err, regions) {
if (err) {
cb(err, null);
} else {
cb(null, regions);
}
});
};
Locations.remoteMethod(
'regions', {
http: {
path: '/regions',
verb: 'get'
},
returns: {
root: true,
type: 'object'
}
}
);
If this would work, how would I implement it with the Cloudant NoSQL DB connector?
Thanks!
If your documents looked like this:
{
"name": "Star Wars",
"year": 1978,
"rating": "*****"
}
You can create a MapReduce view, which emits doc.rating as the key and uses the build-in _count reducer:
function(doc) {
emit(doc.rating,null);
}
When you query this view with group=true, distinct values of rating will be presented with counts of their occurrence in the data set.