Graphql execute where condition only if the argument is passed - node.js

I am using graphql in node js for my oracle database wherein I connect to the remote database and fetch some details. I am fairly new to these technologies so please pardon me. I have a customer table with below schema:
const Customer = new GraphQLObjectType({
description: 'Customer data schema',
name: 'Customer',
fields: () => ({
name: {
type: GraphQLString,
sqlColumn: 'NAME',
},
city: {
type: GraphQLString,
sqlColumn: 'CITY'
},
country: {
type: GraphQLString,
sqlColumn: 'COUNTRY'
},
gender: {
type: GraphQLString,
sqlColumn: 'GENDER'
},
emp_id: {
type: GraphQLString,
sqlColumn: 'EMP_ID'
}
})
});
Customer._typeConfig = {
sqlTable: 'CUSTOMER',
uniqueKey: ['NAME','EMP_ID']
}
Using join monster I create my Query root as:
const QueryRoot = new GraphQLObjectType({
description: 'global query object',
name: 'RootQuery',
fields: () => ({
customer: {
type: new GraphQLList(Customer),
args: {
emp_id: {
description: 'Emp Id',
type: GraphQLString
},
name: {
description: 'Customer Name',
type: GraphQLString
}
},
where: (customer, args, context) => {
return `${customer}."EMP_ID" = :emp_id AND ${customer}."NAME" = :name`;
},
resolve: (parent, args, context, resolveInfo) => {
return joinMonster(resolveInfo, context, sql => {
console.log('joinMaster', sql);
return database.simpleExecute(sql, args,{
outFormat: database.OBJECT
});
});
}
}
})
})
When I pass my query in graphql in browser with emp_id and name parameters I get data. But there are cases when I cannot pass any parameters and would want all the rows to be fetched.
When I do not send the parameters I get error as:
ORA-01008 : Not all variables bound
I want the arguments to be optional, and if I don't send them then it should return all rows.
Thank you.

Both the where and resolver functions are passed an args argument. This will have the parameter names and values if any. You can use that argument to build a dynamic where clause. Here's an untested example:
const QueryRoot = new GraphQLObjectType({
description: 'global query object',
name: 'RootQuery',
fields: () => ({
customer: {
type: new GraphQLList(Customer),
args: {
emp_id: {
description: 'Emp Id',
type: GraphQLString
},
name: {
description: 'Customer Name',
type: GraphQLString
}
},
where: (customer, args, context) => {
if (Object.keys(args).length === 0) {
return false;
}
let whereClause = '1 = 1';
if (args.emp_id != undefined) {
whereClause += `\n AND ${customer}."EMP_ID" = :emp_id`;
}
if (args.name != undefined) {
whereClause += `\n AND ${customer}."NAME" = :name`;
}
return whereClause;
},
resolve: (parent, args, context, resolveInfo) => {
return joinMonster(resolveInfo, context, sql => {
console.log('joinMaster', sql);
return database.simpleExecute(sql, args,{
outFormat: database.OBJECT
});
});
}
}
})
})
Since the where clause would then match the number of arguments, you shouldn't get the ORA-01008 error.

Related

Why does the child's resolve function not contain the result of the parent's resolve function?

I've tried to isolate this example and I hope it's ok. I know, this isn't great code, but I hope you get the drift.
For the time being the resolvers return a static result object.
Here's my problem:
The result of the company resolve function should be passed on the user's resolve function. But that ain't happenin' and I wonder what I am missing.
const GraphQL = require('graphql');
const UserType = new GraphQL.GraphQLObjectType({
name: 'User',
fields: {
givenName: { type: GraphQL.GraphQLString },
familyName: { type: GraphQL.GraphQLString },
city: { type: GraphQL.GraphQLString },
},
});
const CompanyType = new GraphQL.GraphQLObjectType({
name: 'Company',
fields: {
legalName: { type: GraphQL.GraphQLString },
city: { type: GraphQL.GraphQLString },
employees: { type: new GraphQL.GraphQLList(UserType) },
},
});
const queryDef = new GraphQL.GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: UserType,
args: {
id: { type: GraphQL.GraphQLID },
givenName: { type: GraphQL.GraphQLString },
familyName: { type: GraphQL.GraphQLString },
city: { type: GraphQL.GraphQLString },
},
resolve: (parent, args, context, info) => {
console.log('parent should provide company object', parent);
// currentyl parent is undefined
return {
id: 10,
givenName: 'test',
};
},
},
company: {
type: CompanyType,
args: {
id: { type: GraphQL.GraphQLID },
},
resolve: (parent, args, context, info) => {
return {
id: 3,
legalName: 'legal test name',
city: 'company location',
};
},
},
},
});
const schema = new GraphQL.GraphQLSchema({ query: queryDef });
const companyQuery = `
{
company(id: 1) {
city
employees {
familyName
}
}
}`;
GraphQL.graphql(schema, companyQuery).then( (companyResult) => {
console.log(companyResult);
} ).catch( (err) => {
console.error(err);
});

How to use query parameter in Graphiql tool

I am using zomato api to make an restaurant review app with React, node and Graphql. As per the docuemnt, I am passing query parameters, but in graphiql tool I am getting 'Not defined'.
Here is the error:
graphiql tool error
Here is the api documentation reference:
curl -X GET --header "Accept: application/json" --header "user-key: abc" "https://developers.zomato.com/api/v2.1/restaurant?res_id=2"
My query is how can we test the api using query parameters in graphiql tool? Also let me know how can we update the query parameter in api to catch user input and correspondingly show the result.
Below is the code for schema.js which defined graphql schema
const axios = require("axios");
const {
GraphQLObjectType,
GraphQLString,
GraphQLInt,
GraphQLBoolean,
GraphQLList,
GraphQLFloat,
GraphQLSchema
} = require("graphql");
const RestaurantType = new GraphQLObjectType({
name: "Restaurant",
fields: () => ({
res_id: {
type: GraphQLInt
},
name: {
type: GraphQLString
},
url: {
type: GraphQLString
},
photos_url: {
type: GraphQLString
},
menu_url: {
type: GraphQLString
},
rating: {
type: UserRatingType
},
location: {
type: LocationType
}
})
});
const UserRatingType = new GraphQLObjectType({
name: "UserRating",
fields: () => ({
aggregate_rating: {
type: GraphQLFloat
}
})
});
const LocationType = new GraphQLObjectType({
name: "Location",
fields: () => ({
address: {
type: GraphQLString
},
locality: {
type: GraphQLString
},
zipcode: {
type: GraphQLInt
}
})
});
const userKey = 'abc';
const RootQuery = new GraphQLObjectType({
name: "RootQueryType",
fields: {
restaurants: {
type: new GraphQLList(RestaurantType),
resolve(parent, args) {
return axios
.get(`https://developers.zomato.com/api/v2.1/restaurant`, {
headers: {
'user-key': {userKey}
}})
.then(res => res.data)
.catch((error) => {
console.log('error is ' + error);
});
}
},
restaurant: {
type: RestaurantType,
args: {
res_id: {
type: GraphQLInt
}
},
resolve(parent, args) {
return axios
.get(`https://developers.zomato.com/api/v2.1/restaurant?
res_id`, {
params: {
res_id
},
headers: {
'user-key': {
userKey
}
}
})
.then(res => {
console.log(res.data)})
.catch((error) => {
console.log('error is ' + error);
});
}
}
}
});
module.exports = new GraphQLSchema({
query: RootQuery
});

Graphql get aggregation of dynamic data

I am using graphql with node js on oracle db to fetch some data. I have two tables customer and sub_customer with the below schemas:
const Customer = new GraphQLObjectType({
description: 'Customer data schema',
name: 'Customer',
fields: () => ({
name: {
type: GraphQLString,
sqlColumn: 'NAME',
},
city: {
type: GraphQLString,
sqlColumn: 'CITY'
},
region: {
type: GraphQLString,
sqlColumn: 'REGION'
},
country: {
type: GraphQLString,
sqlColumn: 'COUNTRY'
},
gender: {
type: GraphQLString,
sqlColumn: 'GENDER'
},
emp_id: {
type: GraphQLString,
sqlColumn: 'EMP_ID'
},
sub_id: {
type: new GraphQLList(Subcustomer),
sqlJoin: (Subcustomer, Customer, args) => `${Subcustomer}.REGION_CODE = ${Customer}.REGION`
}
})
});
Customer._typeConfig = {
sqlTable: 'CUSTOMER',
uniqueKey: ['NAME','EMP_ID']
}
const Subcustomer = new GraphQLObjectType({
description: 'Subcustomer data schema',
name: 'Subcustomer',
fields: () => ({
sub_customer_id: {
type: GraphQLString,
sqlColumn: 'SUB_CUSTOMER_ID',
},
region_code: {
type: GraphQLString,
sqlColumn: 'REGION_CODE'
},
customer: {
type: new GraphQLList(Customer),
args: {
emp_id: {
type: GraphQLString
}
},
where: (customer, args, context) => {
if (Object.keys(args).length === 0) {
return false;
}
let whereClause = '1 = 1';
if (args.emp_id != undefined) {
whereClause += `\n AND ${customer}."EMP_ID" = '${args.emp_id}'`;
}
return whereClause;
},
sqlJoin: (Subcustomer, Customer, args) => `${Subcustomer}.REGION_CODE = ${Subcustomer}.REGION`
}
})
});
Subcustomer._typeConfig = {
sqlTable: 'SUB_CUSTOMER',
uniqueKey: 'REGION_CODE'
}
And this is my Query:
const QueryRoot = new GraphQLObjectType({
description: 'global query object',
name: 'RootQuery',
fields: () => ({
customer: {
type: new GraphQLList(Customer),
args: {
emp_id: {
description: 'Emp Id',
type: GraphQLString
},
name: {
description: 'Customer Name',
type: GraphQLString
}
},
where: (customer, args, context) => {
if (Object.keys(args).length === 0) {
return false;
}
let whereClause = '1 = 1';
if (args.emp_id != undefined) {
whereClause += `\n AND ${customer}."EMP_ID" = :emp_id`;
}
if (args.name != undefined) {
whereClause += `\n AND ${customer}."NAME" = :name`;
}
return whereClause;
},
resolve: (parent, args, context, resolveInfo) => {
return joinMonster(resolveInfo, context, sql => {
console.log('joinMaster', sql);
return database.simpleExecute(sql, args,{
outFormat: database.OBJECT
});
});
}
},
sub_customer: {
type: new GraphQLList(Subcustomer),
args: {
user_id: {
description: 'User Id',
type: GraphQLString
},
},
where: (user, args, context) => {
if (Object.keys(args).length === 0) {
return false;
}
let whereClause = '1 = 1';
if (args.user_id != undefined) {
whereClause += `\n AND ${user}."SUB_CUSTOMER_ID" = :user_id`;
}
return whereClause;
},
resolve: (parent, args, context, resolveInfo) => {
return joinMonster(resolveInfo, context, sql => {
console.log('joinMaster', sql);
return database.simpleExecute(sql, args,{
outFormat: database.OBJECT
});
});
}
}
})
})
I get correct results when I execute:
{
sub_customer(user_id: "123") {
sub_customer_id
customer(emp_id: "456") {
name
}
}
}
For getting count of records with static where clause I do:
sqlExpr: Customer => `(SELECT count(*) FROM CUSTOMER WHERE EMP_ID = '456')`
This gives me correct count. However I want the count of all the results returned when where clause is applied by the user and it could be dynamic.
I am using join monster as the library to fetch records. I could not find any way to execute this aggregation with dynamic where clauses.
Thank you.
The sqlExpr function is passed more than a single parameter. You have access to: tableAlias, args (for this field), context, and sqlASTNode:
https://join-monster.readthedocs.io/en/latest/API/#sqlExpr
The sqlASTNode has a parent property (not enumerable) that allows you to get the args of the parent field (Customer in your case). So you could make this field more dynamic too. Here's an example that I've not tested.
sqlExpr: (table, args, context, sqlASTNode) => {
let expr;
if (sqlASTNode.parent && sqlASTNode.parent.args && sqlASTNode.parent.args.emp_id != undefined) {
expr = '(SELECT count(*) FROM CUSTOMER WHERE EMP_ID = :emp_id)';
} else {
expr = 'null';
}
return expr;
}

GraphQL Mongoose promise best practices

Using GraphiQL, I'm able to update a user with the following command (1).
I'm able to return the actual user with the service (3).
Is there simpler/more compact way of returning the user after an update? (seen how easy it is when creating a user).
(1) GraphiQL mutation command
mutation {
updateUser(id: "1", firstName: "Bob"){
firstName
}
}
(2) the mutation:
const mutation = new GraphQLObjectType({
updateUser: {
type: UserType,
args: {
id: { type: new GraphQLNonNull(GraphQLString) },
firstName: { type: GraphQLString },
age: { type: GraphQLInt },
companyId: { type: GraphQLString }
},
resolve(parentValue, args){
return UserService.updateUser(args);
}
},
})
(3) the service:
function updateUser(args) {
var {id} = args;
return User.findOne({id})
.then(user => {
if(!user){throw new Error('No user found')}
return User.update({id}, args)
.then(() => {
return User.findOne({id})
})
})
},
function addUser(args) {
const user = new User(args);
return user.save(args);
}
Assuming you're using mongoose:
function updateUser(args) {
var {id} = args;
return await User.findOneAndUpdate({ id }, args, { new: true });
},

GraphQL.js Node/Express: How to pass object as GraphQL query argument

My goal is to be able to pass an object as an argument in a GraphQL query.
Goal:
{
accounts (filter:
{"fieldName": "id",
"fieldValues":["123"],
"filterType":"in"}){
id
}
}
Error:
"message": "filterType fields must be an object with field names as keys or a function which returns such an object."
I've tried a few different approaches but this seems to be the closest to the potential solution.
Schema:
const filterType = new GraphQLObjectType ({
name: 'filterType',
fields: {
fieldName: { type: GraphQLString },
fieldValues: { type: GraphQLString },
filterType: { type: GraphQLString },
}
})
const QueryType = new GraphQLObjectType({
name: 'Query',
fields: () => ({
accounts: {
type: new GraphQLList(accountType),
args: {
filter: { type: new GraphQLInputObjectType(filterType) },
},
resolve: (root, args, { loaders }) => loaders.account.load(args),
},
}),
});
I have found the solution here.
https://github.com/mugli/learning-graphql/blob/master/7.%20Deep%20Dive%20into%20GraphQL%20Type%20System.md#graphqlinputobjecttype
Schema:
const filterType = new GraphQLInputObjectType({
name: 'filterType',
fields: {
fieldName: { type: GraphQLString },
fieldValues: { type: GraphQLString },
filterType: { type: GraphQLString },
}
})
const QueryType = new GraphQLObjectType({
name: 'Query',
fields: () => ({
accounts: {
type: new GraphQLList(accountType),
args: {
filter: { type: filterType },
},
resolve: (root, args, { loaders }) => {
return loaders.account.load(args)},
},
}),
});
Problem was in the query, I had the both the keys and values as strings in the object argument.
Correct Query:
{
accounts(filter: {fieldName: "id", fieldValues: "123", filterType: "in"}) {
id
}
}
You don't define filterType as an object type then wrap it in an input type, you literally create it as an input type:
const filterType = new GraphQLInputObjectType({
name: 'filterType',
fields: {
fieldName: { type: GraphQLString },
fieldValues: { type: GraphQLString },
filterType: { type: GraphQLString },
}
})
const QueryType = new GraphQLObjectType({
name: 'Query',
fields: () => ({
accounts: {
type: new GraphQLList(accountType),
args: {
filter: { type: filterType },
},
resolve: (root, args, { loaders }) => loaders.account.load(args),
},
}),
});
You'll also want to declare its type at query time, as illustrated in #piotrbienias's answer.

Resources