Sequelize with GraphQl: How to update fields using mutation - node.js

I'm using a stack of koa2, sequelize and graphql. I wan't to change the state field of the users model using graphql mutation and return the changed object.
Currently my mutation looks like this:
mutation: new GraphQLObjectType({
name: 'Mutation',
fields: {
setState: {
type: userType,
args: {
input: {
type: userStateInputType
}
},
resolve: resolver(db.user, {
before: async (findOptions, {input}) => {
const {uuid, state} = input;
await db.user.update(
{state},
{where: {uuid}}
);
findOptions.where = {
uuid
};
return findOptions;
}
})
}
}
})
And that's the corresponding query:
mutation setstate{
setState(input: {uuid: "..UUID..", state: "STATE"}) {
uuid
state
}
}
It's working, but I'm pretty sure there are better solutions for this.

I would avoid trying to use graphql-sequelize's resolver helper for a mutation. Looking over the source for that library, it looks like it's really meant only for resolving queries and types.
I think a much cleaner approach would just to do something like this:
resolve: async (obj, { input: { uuid, state } }) => {
const user = await db.user.findById(uuid)
user.set('state', state)
return user.save()
}
I'm avoiding using update() here since that only returns the affected fields. If you ever decide to expand the fields returned by your mutation, this way you're returning the whole User Object and avoiding returning null for some fields.

Related

avoid calling a parent resolver if only nested resolver was called

lets say I have a simple query to get post's comments and it looks like this
post(id:"123") {
comments: {
id,
body
}
}
currently it the graph will call postResolver and then commentsResolver
but the call to postResolver is redundant since I only need to fetch all the comments by postId
I am using an implementation using nodeJs with typescript
i have a resolver such as this
const resolvers : Resolvers = {
Query: {
post: (parent, args, info) => { return fetchPost(args.id);}
},
Post: {
comments: (parent, args, info) => { return fetchComments(parent.id)}
}
}
basically in this example I don't need to fetch the post at all, but the resolver is still invoked, any way to elegantly avoid it ?
I'm looking of a generalized pattern and not this specific resolver situation, there are other nodes with same situation would like to know if there is anything common in this situation that was already solved ...
My solution so far is to remodel the graph like this
type Post (id: ID!){
postData: PostData,
comments: [Comment!]
}
type PostData {
id: ID! ...
}
type Comment{
id: ID! ....
}
Your original model is fine, you just need a different query that goes straight for the comments based on their postId:
getCommentsByPostId(postId: ID!): [Comment]
Then augment your query resolvers:
const resolvers : Resolvers = {
Query: {
post: (_, { id }) => { return fetchPost(id);},
getCommentsByPostId: (_, { postId }) => fetchComments(postId)
},
…

GraphQL Resolver - Multiple Promises

I'm currently trying to call multiple Rest data sources to pull back a combined set of users in a single graph.
When running the query via Graphql Playground I'm getting null values which would point to the resolver function.
Looking in the debug console I can see that I'm getting data returned but I've got an additional layer of nesting includes which would explain why I'm not seeing the data in Graphql playground.
Resolver function looks like this:
latestProspects: async (_, __, { dataSources }) => {
try {
return Promise.all([
dataSources.site1Prospects.getLatestProspects(),
dataSources.site2Prospects.getLatestProspects()
])
} catch(error) {
console.log(error)
}
},
UPDATE
Thinking things through I realise that the result sets are held in a dimension per data source.
I've updated my code to merge the two result sets together, not sure I'm able to improve things further?
type Prospect {
date_created_gmt: String
first_name: String
last_name: String
email: String
billing: Address
meta_data: [Metadata]
}
latestProspects: async (_, __, { dataSources }) => {
try {
return await Promise.all([
dataSources.site1Prospects.getLatestProspects(),
dataSources.site2Prospects.getLatestProspects()
])
.then(result => {
const prospects = result[0]
return prospects.concat(result[1])
})
} catch(error) {
console.log(error)
}
},

Sequelize upsert or create without PK

I'm unable to perform any kind of upsert or create within Sequelize (v: 6.9.0, PostGres dialect).
Using out-of-the-box id as PK, with a unique constraint on the name field. I've disabled timestamps because I don't need them, and upsert was complaining about them. I've tried manually defining the PK id, and allowing Sequelize to magically create it. Here's the current definition:
const schema = {
name: {
unique: true,
allowNull: false,
type: DataTypes.STRING,
}
};
class Pet extends Model { }
Pet.define = () => Pet.init(schema, { sequelize }, { timestamps: false });
Pet.buildCreate = (params) => new Promise((resolve, reject) => {
let options = {
defaults: params
, where: {
name: params.name
}
, returning: true
}
Pet.upsert(options)
.then((instance) => {
resolve(instance);
})
.catch(e => {
// message:'Cannot read property 'createdAt' of undefined'
console.log(`ERROR: ${e.message || e}`);
reject(e);
});
});
module.exports = Pet;
Upsert code:
// handled in separate async method, including here for clarity
sequelize.sync();
// later in code, after db sync
Pet.buildCreate({ name: 'Fido' });
In debugging, the options appear correct:
{
defaults: {
name: 'Fido'
},
returning:true,
where: {
name: 'Fido'
}
}
I've also tried findOrCreate and findCreateFind, they all return errors with variations of Cannot convert undefined or null to object.
I've tried including id: null with the params, exact same results.
The only way I've succeeded is by providing PK in the params, but that is clearly not scalable.
How can I upsert a Model instance without providing a PK id in params?
class Pet extends Model { }
//...you might have the id for the pet from other sources..call it petId
const aPet = Pet.findCreateFind({where: {id: petId}});
aPet.attribute1 = 'xyz';
aPet.attribute2 = 42;
aPet.save();

Rolling back resolve function from GraphQLObjectType to another

I'm currently studying GraphQL and as part of the developing process, i'm interested with modularization of my code - i do understand how to write query, but fail to understand how to correctly implement query of queries.
That is the rootQuery.js
const {
GraphQLInt,
GraphQLList,
GraphQLObjectType,
GraphQLSchema,
GraphQLFloat,
GraphQLString
} = require("graphql");
const bankRootQuery = require('../graphql/queries/bank.queries')
const rootQuery = new GraphQLObjectType({
name: "rootQuery",
fields: {
bankRootQuery: { type: bankRootQuery, resolve: () => { console.log(bankRootQuery.resolve) } }
}
});
module.exports = new GraphQLSchema({
query: rootQuery
});
And here is the bankRootQuery.js:
const { GraphQLObjectType, GraphQLInt, GraphQLNonNull, GraphQLID, GraphQLList } = require("graphql");
const BankType = require('../types/bank.type');
const models = require('../../models/models_handler');
module.exports = new GraphQLObjectType({
name: "bankRootQuery",
fields: {
getbanks: {
type: new GraphQLList(BankType),
resolve: () => {
return models.getBanks()
}
},
getbankByID: {
type: BankType,
args: {
bankID: { name: "bankID", type: GraphQLInt }
},
resolve: (_, args) => {
if (!models.getBanks().has(args.bankID))
throw new Error(`Bank with ID ${args.bankID} doesn't exists`);
return models.getBank(args.bankID);}
}
}
});
Assining bankRootQuery to the scheme object instead of rootQuery works perfectly fine, but using the rootQuery yields with null result when querying using GraphiQL - The Documentation Explorer structure seems to be in proper manner, so i'm guessing the problem is with the resolve function, which i don't understand how to define correctly.
Here is the result when querying using GraphQL:
{
"data": {
"bankRootQuery": null
}
}
If a field resolves to null, then execution for that "branch" of the graph ends. Even if the field's type is an object type, none of the resolvers for its "children" fields will be called. Imagine if you had a field like user -- if the field resolves to null, then it makes no sense to try to resolve the user's name or email.
Your resolver for the bankRootQuery field just logs to the console. Because it doesn't have a return statement, its return value is undefined. A value of undefined is coerced into a null. Since the field resolved to null, execution halts.
If you want to return something other than null, then your resolver needs to return something -- even if it's just an empty object ({}). Then the resolvers for any "child" fields will work as expected.
In general, I would advise against nesting your queries like this -- just keep them at the root level. For additional details around how field resolution works, check out this post.

Merging GraphQL Resolvers for Apollo Server not working with Object.assign()

I am modularizing my schema for a GraphQL API and trying to merge the resolvers without using any 3rd party libraries.
Is there a simple way to do this without Lodash.merge() or equivalent?
The Apollo Documentation says to use a library such as Lodash to merge() modularized resolvers. (http://dev.apollodata.com/tools/graphql-tools/generate-schema.html#modularizing)
The problem seems to be that by their nature, the resolvers contain functions as properties, so they seem to be omitted when I access them via Object.assign() or even JSON.stringify().
If I console.log them, I see: {"Query":{},"Mutation":{}}
Here is what one of the resolvers looks like:
const productResolvers = {
Query: {
myProducts: (root, { userId }, context) => {
return [
{ id: 1, amount: 100, expiry: '12625383984343', created: '12625383984343' },
{ id: 2, amount: 200, expiry: '12561351347311', created: '12625383984343' },
{ id: 3, amount: 200, expiry: '11346347378333', created: '12625383984343' },
{ id: 4, amount: 350, expiry: '23456234523453', created: '12625383984343' },
];
},
},
Mutation: {
addProduct: (root, { userId }, context) => {
return { id: 350, amount: 100, expiry: '12625383984343', created: '12625383984343' };
},
}
};
Let's assume there is another one virtually identical called widgetResolvers.
Here is a fully functional block of code:
export const schema = makeExecutableSchema({
typeDefs: [queries, mutations, productSchema, widgetSchema],
resolvers
});
Here is what I'm trying to achieve:
export const schema = makeExecutableSchema({
typeDefs: [queries, mutations, productSchema, widgetSchema],
resolvers: Object.assign({}, productResolvers, widgetResolvers)
});
I haven't loaded in ability to use rest spread yet (https://babeljs.io/docs/plugins/transform-object-rest-spread/). I suspect it won't work for the same reason Object.assign() doesn't work.
Oh, and here is why I suspect this merge doesn't work: Why doesn't JSON.stringify display object properties that are functions?
If you're using Object.assign(), your Query and Mutation properties shouldn't end up empty, but you will run into an issue because, unlike lodash's merge(), it's not recursive. Object.assign() only compares the "direct" properties of the objects it's passed -- overriding properties of previous sources as it moves through the list.
Because Query and Mutation are properties of the objects being passed, each subsequent resolver override the previous object's Query and Mutation, with the resulting object only holding the Query and Mutation properties of the last object passed into Object.assign().
It's a lot less neat, but if you're bent on avoiding importing lodash, you could get the expected behavior this way:
const productResolver = {
Query: { ... ✂ ... },
Mutation: { ... ✂ ... }
}
const widgetResolver = {
Query: { ... ✂ ... },
Mutation: { ... ✂ ... }
}
const resolvers = {
Query: Object.assign({}, widgetResolver.Query, productResolver.Query),
Mutation: Object.assign({}, widgetResolver.Mutation, productResolver.Mutation)
}
Got type resolvers too? No problem:
const Widget = { ... ✂ ... }
const Product = { ... ✂ ... }
const resolvers = Object.assign(
{
Query: Object.assign({}, widgetResolver.Query, productResolver.Query),
Mutation: Object.assign({}, widgetResolver.Mutation, productResolver.Mutation)
},
Widget,
Product)

Resources