Graphql Delete operation using mutation - javascript

I'm trying to create delete operation. I have a user and tasks to do. Each user has a list of tasks assigned. Here is my code of delete operation:
const Mutation = new GraphQLObjectType({
name: 'Mutation',
fields: {
deleteUser: {
type: UserType,
args: {
id: { type: new GraphQLNonNull(GraphQLID) }
},
resolve(parent, args){
var id = (args.id).toString()
let todos = Todo.find({ user_id: id });
let user = User.findById(args.id);
todos.remove();
user.findOneAndRemove();
return todos;
}
}
}
})
And here are my defined types:
const ToDoType = new GraphQLObjectType({
name: 'todoItem',
fields: () => ({
id: {type: GraphQLID},
title: {type: GraphQLString},
completed: {type: GraphQLBoolean},
user: {
type: UserType,
resolve(parent, args){
return User.findById(parent.user_id);
}
}
})
});
const UserType = new GraphQLObjectType({
name: 'user',
fields: () => ({
id: {type: GraphQLID},
name: {type: GraphQLString},
email: {type: GraphQLString},
login: {type: GraphQLString},
todos: {
type: new GraphQLList(ToDoType),
resolve(parent, args){
return Todo.find({ user_id: parent.id })
}
}
})
});
The problem is that while deleting user I want to delete all his "to do's" but according to what I put in return I can delete only one thing.. So now I have "return todos" and it deletes only todo assigned to user, but not the user. When I put there "return user" it will delete only user without his tasks. How can I delete user and his tasks at once?

The code as shown appears to actually mutate the data to delete both the user, and all their todos. I believe the confusion you're having is that the mutation operation has the "type" (i.e. what it will return) of User. That is therefore what you'd need to return from the mutation function.
More conceptually, it might be a good idea to return from that both the user and all their data, so that if the API user wanted to capture some or all of that, they could, though given privacy implications that might not be ideal. An alternative approach would be to have the mutation just return a success-or-failure status.

Related

Mongoose is not populating the fields in MongoDB

When I run the following code, I get the object along with the populated fields logged on the console.
Screenshot
But, the fields have not been populated in the books collection. Can someone please help me figure this out?
const bookSchema = new Schema({
title: String,
genre: { type: Schema.Types.ObjectId, ref: "genre" },
author: { type: Schema.Types.ObjectId, ref: "author" },
numberInStock: { type: Number, default: 0 },
rating: Number,
yearPublished: Number,
dateAdded: { type: Date, default: Date.now },
liked: { type: Boolean, default: false },
});
const genreSchema = new Schema({ name: String });
const authorSchema = new Schema({ name: String });
const Book = model("book", bookSchema);
const Genre = model("genre", genreSchema);
const Author = model("author", authorSchema);
const books = [
{
title: "Sapiens",
genre: "632873144b0bbfc10ae1942d",
author: "632873e706fe265eaee77de3",
numberInStock: 6,
rating: 4.4,
yearPublished: 2011,
},
];
async function saveBook(b) {
let book = new Book(b);
book
.save()
.then((result) => {
populateBook(result._id);
})
.catch((err) => console.log("Error: ", err));
}
function populateBook(id) {
Book.findById(id)
.populate("genre")
.populate("author")
.exec((err, book) => {
if (err) {
console.log("Error: ", err);
return;
}
console.log(book);
});
}
books.forEach((b) => {
saveBook(b);
});
That's how population works, it only stores references to other documents in the database. At query time, and if you ask for it (using .populate()), Mongoose will retrieve the referenced documents and insert them into the "parent" document.
If you want the referenced documents to be stored in the database, you can't use population but have to use subdocuments.
However, this will limit the flexibility of your database, because if for example an author name needs to be changed, you need to change all the Book documents in your database to update the author's name. With population, you only need to change the Author document.

how do I query from parent model a array of other model in Mongoose?

I have two Schema for user & todo. Every todo has an owner as a user, every user has an array of todos.
// user.js
const TodoSchema = require('./todo').TodoSchema;
var UserSchema = mongoose.Schema({
name: {
type: String,
required: true
},
todos: {
type: [TodoSchema]
}
});
module.exports.UserSchema = UserSchema;
module.exports.UserModel = mongoose.model('UserModel', UserSchema);
// todo.js
var TodoSchema = mongoose.Schema({
body: {
type: String, required: true
},
owner: {
type: mongoose.Schema.Types.ObjectId,
ref: 'UserModel',
required: true
}
});
module.exports.TodoSchema = TodoSchema;
module.exports.TodoModel = mongoose.model('TodoModel', TodoSchema);
I entered data like this.
var nUser = new UserModel({
name: "Alex
)};
nUser.save().then(user => {
var t = new TodoModel({
body: "my new todo",
owner: user._id
});
t.save().then();
});
But the problem is I want to get all the todos from a specific user, something like this...What is the correct way?
UserModel.findOne({name: "Alex"})
.then(user => {
// user.todos
});
P.S.
I can do this like TodoModel.find({owner: specific_user._id}), but I want it from UserModel.
Since you're asking for the proper way of doing it, I am gonna start with your User Schema. If you want to find all the todos of a user, then putting the todo documents inside an array in the User document is not required. So you should probably remove that from your schema.
After that you can use a simple aggregation to get your desired outcome.
UserModel.aggregate([
{
$match:{
name:"Alex"
}
},
{
$lookup:{
from:"todomodels",
localField:"$_id",
foreignField:"$owner",
as:"todos"
}
}
])
this will return all the todos for that user in an array of the same name.

GraphQL.js - use Interface as a default (fallback) type in resolveType function

I'm trying to return generic type in resolveType function if none of the provided types is matched. The example below shows this problem: API worked like a charm supporting UserType and MovieType until in database was added BookType (not supported by GraphQL schema).
const {
graphql,
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLNonNull,
GraphQLList,
GraphQLInterfaceType
} = require("graphql");
const DATA = [
{
// UserType
name: "catherine woolf",
nick: "catherine"
},
{
// MovieType
name: "cat woman",
director: "Jack Wolfgang"
},
{
// --- missing type --- (BookType)
name: "cats secrets",
author: "Nicky Glace"
}
];
const resolveType = data => {
if (data.nick) {
return UserType;
}
if (data.director) {
return MovieType;
}
};
const SearchableType = new GraphQLInterfaceType({
name: "Searchable",
fields: {
name: { type: GraphQLString }
},
resolveType: resolveType
});
const UserType = new GraphQLObjectType({
name: "User",
interfaces: [SearchableType],
fields: {
name: { type: GraphQLString },
nick: { type: GraphQLString }
}
});
const MovieType = new GraphQLObjectType({
name: "Movie",
interfaces: [SearchableType],
fields: {
name: { type: GraphQLString },
director: { type: GraphQLString }
}
});
const schema = new GraphQLSchema({
types: [MovieType, UserType, SearchableType],
query: new GraphQLObjectType({
name: "RootQueryType",
fields: {
search: {
type: new GraphQLList(SearchableType),
args: {
text: { type: new GraphQLNonNull(GraphQLString) }
},
resolve(_, { text }) {
return DATA.filter(d => d.name.indexOf(text) !== -1);
}
}
}
})
});
const query = `
{
search(text: "cat") {
name
... on User {
nick
}
... on Movie {
director
}
}
}
`;
graphql(schema, query).then(result => {
console.log(JSON.stringify(result, null, 2));
});
So now this code ends with error:
"Abstract type Searchable must resolve to an Object type at runtime for field RootQueryType.search with value \"[object Object]\", received \"undefined\". Either the Searchable type should provide a \"resolveType\" function or each possible types should provide an \"isTypeOf\" function."
This is nothing surprising since currently resolveType may not return any type.
Workaround
Crate type containing the same field like interface SearchableType (1 to 1 implementation):
const _SearchableType = new GraphQLObjectType({
name: '_Searchable',
interfaces: [SearchableType],
fields: {
name: { type: GraphQLString },
}
});
Use it as a fallback type:
const resolveType = data => {
if (data.nick) {
return UserType;
}
if (data.director) {
return MovieType;
}
return _SearchableType;
};
And add it to types in schema definition:
types: [MovieType, UserType, SearchableType, _SearchableType],
But the problem with this solution is presence of this dummy _SearchableType in documentation like this:
Question
Is there any way to return interface SearchableType or equivalent of it in resolveType? The key point for me is hiding of this "fallback type" in a documentation.
GraphQL is strongly typed and doesn't support generics or some kind of "fallback" mechanism when resolving unions and interfaces. At the end of the day, if your underlying data layer is returning some type that you have not yet implemented in your schema, the simplest solution is to simply add that type to your schema. Migrations to your database and changes to your schema should go hand-in-hand.
If you want to derive your schema from your storage layer, I would suggest looking into something like PostGraphile (formerly PostGraphQL).
That said, if you're bent on using a workaround, you could just fallback to one of the existing types:
const resolveType = data => {
if (data.nick) {
return UserType
}
return MovieType
}
Now a book's name will still be accessible, provided you query it on the interface and not one of the types. The only downside to this approach is that movie-specific fields will be returned for a book and will resolve to null, but that won't cause any issues unless they're specifically defined as non-null in your schema.

Modify GraphQLObjectType fields at runtime

Assume I have the following code as graphql schema. A userType including id and name for users, and there's two kind of queries: allUsers: [userType] and user(id: Int!): userType.
let db = [{
id: 1,
name: 'Amir'
}, {
id: 2,
name: 'John'
}];
const userType = new GraphQLObjectType({
name: 'User',
fields: {
id: { type: GraphQLInt },
name: { type: GraphQLString }
}
});
const queryType = new GraphQLObjectType({
name: 'Query',
fields: {
allUsers: {
type: new GraphQLList(userType),
resolve: () => db
},
user: {
type: userType,
args: {
id: { type: new GraphQLNonNull(GraphQLInt) }
},
resolve: (_, { id }) => db.find(user => user.id == id);
}
}
})
let schema = new GraphQLSchema({ query: queryType });
I need to modify this structure at boot time. I mean before actually executing the last line.
To add more kind of queries, I deferred the schema creation (new GraphQLSchema(...)) to the end, after all the modifications are done. So I can add more fields to the query itself or perhaps modify existing ones.
But how can I modify the types that are already defined? Basically, I need to add other fields to userType, like permissions, which itself is a GraphQLObjectType and has its own resolve function.
Something like this:
let queryFields = {};
const userType = new GraphQLObjectType({
name: 'User',
fields: {
id: { type: GraphQLInt },
name: { type: GraphQLString }
}
});
queryFields['allUsers'] = {
type: new GraphQLList(userType),
// ...
}
queryFields['user'] = {
type: userType,
//...
}
/* HERE <---------------------------------------- */
userType.fields.permission = {
type: GraphQLString,
resolve: user => getPermissionsFor(user);
}
const queryType = new GraphQLObjectType({
name: 'Query',
fields: queryFields
})
var schema = new GraphQLSchema({ query: queryType });
Thanks!
What I have done at the end is to add another layer between the logic of my app and GraphQL. So I have created another library that holds the information about the schema and the types, and it has an API to modify the existing types in the schema. Once all the modifications are in place, we can extract a GraphQL schema from the library.
That's the whole idea. For implementation detail, I have wrote a post here: Distributed Schema Creation in GraphQL

How does Relay / GraphQL 'resolve' works?

I am trying out Relay and GraphQL. When I am doing the schema I am doing this:
let articleQLO = new GraphQLObjectType({
name: 'Article',
description: 'An article',
fields: () => ({
_id: globalIdField('Article'),
title: {
type: GraphQLString,
description: 'The title of the article',
resolve: (article) => article.getTitle(),
},
author: {
type: userConnection,
description: 'The author of the article',
resolve: (article) => article.getAuthor(),
},
}),
interfaces: [nodeInterface],
})
So, when I ask for an article like this:
{
article(id: 1) {
id,
title,
author
}
}
Will it do 3 queries to the database? I mean, each field has a resolve method (getTitle, getAuthor, etc.) which does a request to the database. Am I doing this wrong?
This is an example of getAuthor (I use mongoose):
articleSchema.methods.getAuthor = function(id){
let article = this.model('Article').findOne({_id: id})
return article.author
}
If the resolve method is passed the article, can't you just access the property?
let articleQLO = new GraphQLObjectType({
name: 'Article',
description: 'An article',
fields: () => ({
_id: globalIdField('Article'),
title: {
type: GraphQLString,
description: 'The title of the article',
resolve: (article) => article.title,
},
author: {
type: userConnection,
description: 'The author of the article',
resolve: (article) => article.author,
},
}),
interfaces: [nodeInterface],
})
Since Schema.methods in Mongoose defines methods on the model, it wouldn't take an ID for the article (because you call it on an article instance). So, if you wanted to keep the method, you would just do:
articleSchema.methods.getAuthor = function() {
return article.author;
}
If it was something you need to look up e.g. in another collection, then you'd need to do a separate query (assuming you're not using refs):
articleSchema.methods.getAuthor = function(callback) {
return this.model('Author').find({ _id: this.author_id }, cb);
}

Categories