GraphQL mutation without sub section - graphql

I want to send graphql mutation request without sub section
mutation _ {
updateCurrentUser(fullName: "Syava", email: "fake#gmail.com")
}
and I am getting
{
"errors": [
{
"message": "Field \"updateCurrentUser\" of type \"User\" must have a sub selection.",
...
}
]
}
add { id } to request works fine but I don't want
Also Schema code
const userType = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: { type: new GraphQLNonNull(GraphQLString) },
fullName: { type: GraphQLString },
email: { type: GraphQLString },
}),
});
type: userType,
args: {
fullName: { type: GraphQLString },
email: { type: new GraphQLNonNull(emailType) },
password: { type: GraphQLString },
},
resolve: async (root, { fullName, email, password }, { rootValue }) => {
const user = await User.findById(rootValue.req.user.id);
...
return user;
},

You define the type of the field to be UserType. Even though it's a mutation, it still follows the same rules and behavior as a query. Because UserType is an object type, it requires nested fields.
mutation _ {
updateCurrentUser(fullName: "Syava", email: "fake#gmail.com") {
fullName
email
}
}
// would respond with { fullName: 'Syava', email: 'fake#gmail.com' }
If you don't want the mutation to return a User, you can declare its type to GraphQLBoolean for example -- that's a scalar and doesn't have any nested fields.
{
type: GraphQLBoolean,
args: {
fullName: { type: GraphQLString },
email: { type: new GraphQLNonNull(emailType) },
password: { type: GraphQLString },
},
resolve: async (root, { fullName, email, password }, { rootValue }) => {
const user = await User.findById(rootValue.req.user.id);
user.fullName = fullName;
user.password = password; // or hashed to not store plain text passwords
return user.save(); // assuming save returns boolean; depends on the library you use
}
}
Note that the best practice for mutations in GraphQL APIs is to return a "result" object with multiple fields, such as the mutated object itself (e.g. user), clientMutationId (per Relay spec), and others as needed. This makes it flexible so you could add more data in the future.
updateCurrentUser(fullName: "Syava", email: "fake#gmail.com") {
clientMutationId
user {
...
}
}

Related

GraphQL Subscriptions Error: "The \"properties\" argument must be of type Array. Received type object"

I am trying to implement a simple API with GraphQL. My queries and my mutations are in place and working, but now I'm trying to include subscriptions as well.
I already added the subscription in the schema, I included the event publish in the addUser mutation and defined the subscribe function for the subscription type.
Now, when I am trying to run a subscription query in the graphiql in-browser IDE, I get this error:
"The \"properties\" argument must be of type Array. Received type object"
Attached is the schema object. Did I configured something wrong or am I missing something? Thanks!
P.S I also need to mention that I am using mongoose to store the data on an a mongo instance, hence the entities.
import {
GraphQLFloat,
GraphQLID,
GraphQLInt,
GraphQLList,
GraphQLNonNull,
GraphQLObjectType,
GraphQLSchema,
GraphQLString
} from 'graphql';
// models
import UserType from '../types/user/UserType';
import AccountType from '../types/account/AccountType';
import TransactionType from '../types/transaction/TransactionType';
// entities
import User from '../entities/user/user';
import Account from '../entities/account/account';
import Transaction from '../entities/transaction/transaction';
// subscriptions
import { PubSub } from 'graphql-subscriptions';
// subscriptions
const pubsub = new PubSub();
const USER_CREATED = 'user_created';
// the acceptable starting point of our graph
const RootQueryType = new GraphQLObjectType({
name: 'RootQueryType',
fields: () => ({
// query individual entities in the database
user: {
type: UserType,
description: 'The current user identified by an id',
args: {
id: {
type: GraphQLID
}
},
resolve(parent, args) {
return User.findById(args.id);
}
},
account: {
type: AccountType,
description: 'Details about the account in question identified by an id',
args: {
id: {
type: GraphQLID
}
},
resolve(parent, args) {
return Account.findById(args.id);
}
},
transaction: {
type: TransactionType,
description: 'Details about the transaction in question identified by an id',
args: {
id: {
type: GraphQLID
}
},
resolve(parent, args) {
return Transaction.findById(args.id);
}
},
// query all entities in the database
users: {
type: new GraphQLList(UserType),
resolve: (parent, args) => {
return User.find({});
}
},
accounts: {
type: new GraphQLList(AccountType),
resolve: (parent, args) => {
return Account.find({});
}
},
transactions: {
type: new GraphQLList(TransactionType),
resolve(parent, args) {
return Transaction.find({});
}
}
})
});
const MutationType = new GraphQLObjectType({
name: 'Mutation',
fields: () => ({
addUser: {
type: UserType,
args: {
name: {
type: new GraphQLNonNull(GraphQLString)
},
age: {
type: new GraphQLNonNull(GraphQLInt)
},
email: {
type: new GraphQLNonNull(GraphQLString)
}
},
resolve(parent, args) {
let user = new User({
name: args.name,
age: args.age,
email: args.email
});
pubsub.publish(USER_CREATED, {
newUser: user
});
return user.save();
}
},
addAccount: {
type: AccountType,
args: {
currency: {
type: new GraphQLNonNull(GraphQLString)
},
balance: {
type: new GraphQLNonNull(GraphQLFloat)
},
holderId: {
type: new GraphQLNonNull(GraphQLString)
}
},
resolve(parent, args) {
let account = new Account({
currency: args.currency,
balance: args.balance,
holderId: args.holderId
});
return account.save().then(() => console.log('user created'));
}
},
addTransaction: {
type: TransactionType,
args: {
sourceAccountId: {
type: new GraphQLNonNull(GraphQLString)
},
targetAccountId: {
type: new GraphQLNonNull(GraphQLString)
},
amount: {
type: new GraphQLNonNull(GraphQLFloat)
}
},
resolve(parent, args) {
let transaction = new Transaction({
sourceAccountId: args.sourceAccountId,
tagetAccountId: args.tagetAccountId,
timestamp: new Date(),
amount: args.amount
});
Account.findById(args.sourceAccountId, (err, account) => {
if (!err) {
account.balance -= args.amount;
return account.save();
}
});
Account.findById(args.targetAccountId, (err, account) => {
if (!err) {
account.balance += args.amount;
return account.save();
}
});
return transaction.save();
}
}
})
});
const SubscriptionType = new GraphQLObjectType({
name: 'Subscription',
fields: () => ({
newUser: {
type: UserType,
description: 'This subscription is going to provide information every time a new user creation event fires',
resolve: (payload, args, context, info) => {
console.table(payload, args, context, info); // debugging
return payload;
},
subscribe: () => pubsub.asyncIterator(USER_CREATED)
}
})
});
const schema = new GraphQLSchema({
query: RootQueryType,
mutation: MutationType,
subscription: SubscriptionType
});
export default schema;
I expect that when I run the subscription query, it will run listening for events being published and when from another tab I will run a mutation to add a new user, the first tab will catch the event and return details of the user in the payload.

Is it possible to do a multi tenancy with Graphql and Sequelize?

I have a rather tricky question about GraphQl and multi-tenancy.
Let's assume there are 3 tables, OWNER, HOUSE and TENANTS. I will describe them in Sequelize and GraphQl pseudo code:
Owner table (has multiple houses and multiple tenants)
const OWNER = sequelize.define('owner', {
ownerId: type: Sequelize.INTEGER,
name: type: Sequelize.STRING
}
OWNER.associate = models => {
models.owner.hasMany(models.house, {foreignKey: {name: 'ownerId', field: 'ownerId'}})
models.owner.hasMany(models.tenant, {foreignKey: {name: 'ownerId', field: 'ownerId'}})
}
House table (belongs to owner and has multiple tenants)
const HOUSE = sequelize.define('house', {
houseId: type: Sequelize.INTEGER,
ownerId: type: Sequelize.INTEGER,
name: type: Sequelize.STRING
}
HOUSE.associate = models => {
models.house.belongsTo(models.owner, {foreignKey: {name: 'ownerId', field: 'ownerId'}})
models.house.hasMany(models.tenant, {foreignKey: {name: 'houseId', field: 'houseId'}})
}
Tenant table (belongs to owner and house)
const TENANT = sequelize.define('tenant', {
tenantId: type: Sequelize.INTEGER,
ownerId: type: Sequelize.INTEGER,
houseId: type: Sequelize.INTEGER,
name: type: Sequelize.STRING
}
TENANT.associate = models => {
models.tenant.belongsTo(models.owner, {foreignKey: {name: 'ownerId', field: 'ownerId'}})
models.tenant.belongsTo(models.house, {foreignKey: {name: 'houseId', field: 'houseId'}})
}
The owner graphql object
const OwnerType = new GraphQLObjectType({
name: 'Owner',
fields: () => ({
ownerId: { type: GraphQLInt },
name: { type: GraphQLString },
houses: {
type: GraphQLList(HouseType),
resolve(owner) {
return owner.getHouse()
}
},
houseById: {
type: HouseType,
args: <args is not defined>
resolve(owner) {
return <???>
}
},
})
})
Here are a few simple GraphQL queries:
ownerById = {
type: OwnerType,
args: {
ownerId: { type: GraphQLInt },
},
resolve(parents, args){
return models.owner.findOne({ where: args })
}
}
houses = {
type: GraphQLList(HouseType),
resolve(parents, args){
return models.house.findAll()
}
}
houseById = {
type: HouseType,
args: {
houseId: { type: GraphQLInt },
},
resolve(parents, args){
return models.house.findOne({ where: args })
}
}
tenants = {
type: GraphQLList(TenantType),
resolve(parents, args){
return models.tenant.findAll()
}
}
These client queries work:
{
ownerById(ownerId: 1) {
ownerId
name
house {
houseId
name
}
}
}
{
houseById(houseId: 2) {
houseId
name
tenant {
tenantId
name
}
}
}
What I need to make multi-tenancy to work is something like that:
{
ownerById(ownerId: 1) {
ownerId
name
houseById(houseId: 2) {
houseId
name
tenant {
tenantId
name
}
}
}
}
Is there a way to archive this or is that out of scope what GraphQl can do?
If yes, how would the graphql object houseById query look like?
Thanks in advance.
Unless I'm missing something, it seems like your resolver for houseById would not be that different from the resolver for the houses field on the same type.
houseById: {
type: HouseType,
args: {
houseId: { type: GraphQLInt },
},
async resolve(owner, { houseId }) {
const houses = await owner.getHouses({ where: { id: houseId } })
return houses[0]
}
},
For a HasMany association, the getter for the target model resolves to an array of instances. So we need to grab that array first and then return just the first item in it, since our field represents a single object and not a list. If you don't want to use async/await, you can also do:
return owner.getHouses({ where: { id: houseId } })
.then(houses => houses[0])
It would also be worth mentioning that this sort of pattern for a schema defies convention. Rather than having a houses field, a houseById field, a houseBySomeOtherArg field, etc., consider exposing a single houses field with one or more arguments like id, name or whatever filter criteria you want to provide. Your field can then just filter the houses based on whatever arguments are passed in, or return all results if no filter arguments were provided.

How can I get user by userName in GraphQL?

I have this RootQuery:
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
user: {
type: UserType,
args: { id: { type: GraphQLID } },
resolve(parent, {id}) {
return User.findById(id)
}
},
and then Ill use this query to get the user:
{
user(id:"5bd78614e71a37341cd2b647"){
id
userName
password
isAdmin
}
}
it works just fine' now i dont want to get the user by his ID,
I want to get him by his userName insted so I used this
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
user: {
type: UserType,
args: { userName: { type: GraphQLString } },
resolve(parent, {userName}) {
console.log('userName',userName)
return User.find({ userName })
}
},
this will bring back the user with all properties be null
please help!!
First, you should use findOne to get only one user, find will bring you all users with that name. If you want that, maybe your return should be type: GraphQLList(UserType).
If it's bringing all properties it is probably because you are asking for them on the query.
Also, you might be missing an await User.find({ userName }), and an async on your function:
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
user: {
type: UserType,
args: { userName: { type: GraphQLString } },
resolve: async (parent, {userName}) => {
console.log('userName',userName)
const user = await User.findOne({ userName })
console.log('user',user);
return user;
}
},
Check if this helps you :)

GraphQL Nested Data in Mutation

I am having some difficulty getting a mutation working in GraphQL where the type in the schema includes a nested type. So say I have a data type for a booking:
const BookingType = new GraphQLObjectType({
name: 'Booking',
fields: () => ({
id: { type: GraphQLInt },
Date: { type: GraphQLString },
Venue: { type: GraphQLString }
})
});
In the schema file I also have a root mutation which looks like this:
createBooking: {
type: BookingType,
args: {
Date: { type: new GraphQLNonNull(GraphQLString) },
Venue: { type: new GraphQLNonNull(GraphQLString) }
},
resolve(parentValue, args){
return axios.post('http://localhost:3000/booking', args)
.then(resp => resp.data);
}
}
I can write a mutation in GraphiQL to create data for the booking no problem:
mutation {
createBooking(
Date: "2018-03-12",
Venue: "Some place",
) {
id
Date
Venue
}
}
So far so good. Now, I need to add a nested type to the original booking object to record staff members assigned to the booking. So I added types for the staff member (both input and output types) and added those to the Booking type and the mutation:
// output type
const AssignedStaffType = new GraphQLObjectType({
name: 'AssignedStaff',
fields: () => ({
id: { type: GraphQLInt },
Name: { type: GraphQLString }
})
});
// input type
const AssignedStaffInputType = new GraphQLInputObjectType({
name: 'AssignedStaffInput',
fields: () => ({
id: { type: GraphQLInt },
Name: { type: GraphQLString }
})
});
The booking type becomes:
const BookingType = new GraphQLObjectType({
name: 'Booking',
fields: () => ({
id: { type: GraphQLInt },
Date: { type: GraphQLString },
Venue: { type: GraphQLString },
Staff: { type: new GraphQLList(AssignedStaffType) }
})
});
And the root mutation becomes:
createBooking: {
type: BookingType,
args: {
Date: { type: new GraphQLNonNull(GraphQLString) },
Venue: { type: new GraphQLNonNull(GraphQLString) },
Staff: { type: new GraphQLList(AssignedStaffInputType) }
},
resolve(parentValue, args){
return axios.post('http://localhost:3000/booking', args)
.then(resp => resp.data);
}
}
What I don't know is how to now formulate the mutation in GraphiQL, specifically what to use as a value for Staff:
mutation {
createBooking(
Date: "2018-03-14",
Venue: "Some place",
Staff: // ??? <--- What goes here??
) {
id
Venue
Date
Staff
}
}
I have tried giving it an object, or an array of objects which have the same structure as AssignedStaffInputType, but I just get an error ('expecting AssignedStaffInputType'). The client (GraphiQL in this instance) doesn't know anything about the AssignedStaffInputType as defined in the schema, so I don't understand a) how to use this input type in the client, or b) how I would then populate such a type with the required data.
Help please!
Never mind, I figured it out. I can, in fact, pass an object (or array of objects) in the correct format (specified in the input type in the schema) and it works fine. The reason I was having problems is that I had the wrong scalar type for one of the fields in the input type and this was throwing the error. The client doesn't need to know about the types specified in the schema it seems. So, the above problematic mutation should, in fact, be written like this:
mutation {
createBooking(
Date: "2018-03-14",
Venue: "Some place",
Staff: [{staffId: 1}]
) {
id
Venue
Date
Staff{
Name
}
}
}

how to set many-to-many relation in graphql mutation?

I may be missing something, but can not find any information on Apollo docs about the way to set a many-to-many relation when creating a new entry.
When the relation is one-to-many it is as simple as setting the ID of the one-side of the relationship in the many-side object.
But let's pretend I am working with Books and Authors, how would I write a graphql query that creates a Book for one (or many?) Authors?
This should probably happen at the API layer on the GraphQL server (i.e. schema). For many-to-many relationships, you should have a "join" type to denote the BookAuthor many-to-many relationship, and then add an entry to that join type.
Essentially then you'll have a type called Book, another called Author, and finally one more called BookAuthor. And you can add a few mutations to be able to manage that relationship. Perhaps...
addToBookAuthorConnection
updateBookAuthorConnection
removeFromBookAuthorConnection
This is a conventional setup using a Relay-spec compliant API. You can read more about how to structure your API for many-to-many relationships here.
Then, you only need to call the addToBookAuthorConnection mutation from Apollo instead to be able to add to that many-to-many connection on your frontend.
Hope this helps!
If u r using apollo graph server with one to many relations then connectors.js, resolvers.js and schema.js files as given formats
schema.js
const typeDefinitions = `
type Author {
authorId: Int
firstName: String
lastName: String
posts: [Post]
}
type Post {
postId: Int
title: String
text: String
views: Int
author: Author
}
input postInput{
title: String
text: String
views: Int
}
type Query {
author(firstName: String, lastName: String): [Author]
posts(postId: Int, title: String, text: String, views: Int): [Post]
}
type Mutation {
createAuthor(firstName: String, lastName: String, posts:[postInput]): Author
updateAuthor(authorId: Int, firstName: String, lastName: String, posts:[postInput]): String
}
schema {
query: Query
mutation:Mutation
}
`;
export default [typeDefinitions];
resolvers.js
import { Author } from './connectors';
import { Post } from './connectors';
const resolvers = {
Query: {
author(_, args) {
return Author.findAll({ where: args });
},
posts(_, args) {
return Post.findAll({ where: args });
}
},
Mutation: {
createAuthor(_, args) {
console.log(args)
return Author.create(args, {
include: [{
model: Post,
}]
});
},
updateAuthor(_, args) {
var updateProfile = { title: "name here" };
console.log(args.authorId)
var filter = {
where: {
authorId: args.authorId
},
include: [
{ model: Post }
]
};
Author.findOne(filter).then(function (product) {
Author.update(args, { where: { authorId: args.authorId } }).then(function (result) {
product.posts[0].updateAttributes(args.posts[0]).then(function (result) {
//return result;
})
});
})
return "updated";
},
},
Author: {
posts(author) {
return author.getPosts();
},
},
Post: {
author(post) {
return post.getAuthor();
},
},
};
export default resolvers;
connectors.js
import rp from 'request-promise';
var Sequelize = require('sequelize');
var db = new Sequelize('test', 'postgres', 'postgres', {
host: '192.168.1.168',
dialect: 'postgres',
pool: {
max: 5,
min: 0,
idle: 10000
}
});
const AuthorModel = db.define('author', {
authorId: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true, field: "author_id" },
firstName: { type: Sequelize.STRING, field: "first_name" },
lastName: { type: Sequelize.STRING, field: "last_name" },
},{
freezeTableName: false,
timestamps: false,
underscored: false,
tableName: "author"
});
const PostModel = db.define('post', {
postId: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true, field: "post_id" },
text: { type: Sequelize.STRING },
title: { type: Sequelize.STRING },
views: { type: Sequelize.INTEGER },
},{
freezeTableName: false,
timestamps: false,
underscored: false,
tableName: "post"
});
AuthorModel.hasMany(PostModel, {
foreignKey: 'author_id'
});
PostModel.belongsTo(AuthorModel, {
foreignKey: 'author_id'
});
const Author = db.models.author;
const Post = db.models.post;
export { Author, Post };

Resources