How to return the result from a raw query (Sequelize) to GraphQL - mysql

I'm newbie with GraphQL and Sequelize but I have developed a test where I can make querys and get results from Graphiql using the functions of Sequalize, but I'm interested in making more complex querys with querys with several tables.
Now, this code works fine:
schema.js
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLID,
GraphQLInt,
GraphQLString,
GraphQLFloat,
GraphQLList,
GraphQLSchema
} from "graphql";
import { DB } from "../db";
import {DateTime} from "../scalar/dateTime";
import {Player} from "./Player";
import {League} from "./League";
import {Group} from "./Group";
import {Season} from "./Season";
const Query = new GraphQLObjectType({
name: "Query",
description: "This is root query",
fields: () => {
return {
players: {
type: GraphQLList(Player),
args: {
id: {
type: GraphQLID
}
},
resolve(root, args){
return DB.db.models.tbl003_player.findAll({where: args});
}
},
leagues: {
type: GraphQLList(League),
args: {
id: {
type: GraphQLID
}
},
resolve(root, args){
return DB.db.models.tbl001_league.findAll({where: args});
}
},
groups: {
type: GraphQLList(Group),
args: {
id: {
type: GraphQLID
}
},
resolve(root, args){
return DB.db.models.tbl024_group.findAll({where: args});
}
},
seasons: {
type:GraphQLList(Season),
args: {
id: {
type: GraphQLID
}
},
resolve(root, args){
return DB.db.models.tbl015_seasons.findAll({where: args})
}
}
}
}
});
const Schema = new GraphQLSchema({
query: Query
});
module.exports.Schema = Schema;
So, I would like to make an easy test to know how to return the data from a raw query to GraphQL. I have read that resolve method returns a promise, and I have tried to return a promise with the result of the query, but it doesn't work.
players: {
type: GraphQLList(Player),
args: {
id: {
type: GraphQLID
}
},
resolve(root, args){
DB.db.query("select * from tbl003_player where id = 14",
{raw: true, type: DB.db.QueryTypes.SELECT}).then((players)=>{
let myPromise = new Promise((resolve, reject)=>{
resolve(players);
});
return myPromise;
}).catch((reject)=>{
console.log("Error: " + reject);
});
}
},
Therefore, how can I return data from a query with Sequelize to GraphQL?

Return the promise you get from sequelize. You are also doing a lot of work that is not required after your promise. Maybe read more about promises before moving on :)
resolve(root, args){
return DB.db.query(
"select * from tbl003_player where id = 14",
{ raw: true, type: DB.db.QueryTypes.SELECT }
);
}

Related

GraphQL error update mutation "Resolve function for \"User.id\" returned undefined"

I am a newbie to GraphQL and trying to write an update mutation. However, I am receiving Resolve function for \"User.id\" returned undefined" error although the database is actually got updated.
What am I doing wrong?
userSchema.js:
import Sequelize from 'sequelize';
import SqlHelper from '../helpers/sqlhelper';
const config = require('../../config');
const sequelizer = new SqlHelper(config).Init();
const createUser = sequelizer.define(
'createUser',
{
...
}
);
const updateUser = sequelizer.define(
'updateUser',
{
id: {
type: Sequelize.UUID,
field: 'Id',
primaryKey: true,
defaultValue: Sequelize.UUIDV4,
},
username: {
type: Sequelize.STRING,
field: 'Username',
allowNull: true,
},
email: {
type: Sequelize.STRING,
field: 'Email',
allowNull: true,
},
firstname: {
type: Sequelize.STRING,
field: 'FirstName',
allowNull: true,
},
lastname: {
type: Sequelize.STRING,
field: 'LastName',
allowNull: true,
},
....
},
{
// define the table's name
tableName: 'Users',
},
);
module.exports = User;
UserResolver.js:
import User from '../dbschemas/user';
import Sequelize from 'sequelize';
const Op = Sequelize.Op;
export default {
Mutation: {
createUser: async (obj, args) =>
(await User.create(args)),
updateUser: async (obj, args) =>
(await User.update(args,
{
where: {
id: args.id,
},
returning: true
}))
}
};
Although calling updateUser from GraphiQL updates the records (in db), it results in a "Resolve function for \"User.id\" returned undefined" error:
mutation{
updateUser(id: "2ecd38ca-cf12-4e79-ac93-e922f24af9e3",
username: "newUserTesting",
email: "testemail#yahoo.com",
lastname: "TestUserLName",
firstname: "fname1") {
id
}
}
{
"data": null,
"errors": [
{
"message": "Resolve function for \"User.id\" returned undefined",
"locations": [
{
"line": 16,
"column": 4
}
],
"path": [
"updateUser",
"id"
]
}
]
}
The issue is clear, your resolver does not return an object containing id.
The docs say that Model.update returns an array in which the 2nd element is the affected row.
Hence, your resolver should look like:
async updateUser(obj, args) {
const resultArray = await User.update( ... )
return resultArray[1]
}
... To be replaced by whatever you need.
So apparently, update does NOT return affected rows for MSSQL, only the number of records affected.
This is true only for postgres when returning: true:
public static update(values: Object, options: Object): Promise<Array<affectedCount, affectedRows>>
Setting returning: true (for MSSQL) returns undefined (and order of params in the array is not even in the right order... i.e. first affectedRows -> undefined, then affectedCount ->num of affected rows.)
Tho get an object back you would need to do something like this:
Mutation: {
createUser: async (obj, args) =>
(await User.create(args.user)),
updateUser: async (obj, args, context, info) =>{
let user = args.user;
let response = await User.update(user,
{
where: {
[Op.or]: [{ email: user.email }, { id: user.id }, { username: user.username }, { lastname: user.lastname}]
},
//returning: true //not working... only for postgres db :(
}).then(ret => {
console.log('ret', ret);
return ret[0];
}).catch(error => {
console.log('error', error)
});
if (response > 0) return user; //return record
//return response > 0; //return true
}
}

Custom type in GraphQL mutation

I am using GraphQL js.I want to implement One-to-many association in it.I have two types user and Office.One user has many offices.
userType:
var graphql = require('graphql');
const userType = new graphql.GraphQLObjectType({
name: 'user',
fields :()=>{
var officeType=require('./officeSchema');
return {
_id: {
type: graphql.GraphQLID
},
name: {
type: graphql.GraphQLString
},
age: {
type: graphql.GraphQLString
},
office:{
type:officeType
}
};
}
});
module.exports=userType;
officeSchema:
const officeType = new graphql.GraphQLObjectType({
name: 'office',
fields:()=> {
var userType = require('./userSchema');
return {
_id: {
type: graphql.GraphQLID
},
room: {
type: graphql.GraphQLString
},
location: {
type: graphql.GraphQLString
},
users: {
type: new graphql.GraphQLList(userType),
resolve: (obj,{_id}) => {
fetch('http://0.0.0.0:8082/office/user/'+obj._id, {
method: "GET",
headers: {
'Content-Type': 'application/json'
}
})
.then(function(res) {return res});
}
}
};
}
});
Now the mutation code is as follows:
const Adduser = {
type: userType,
args: {
name: {
type: graphql.GraphQLString
},
age: {
type: graphql.GraphQLString
}
},
resolve: (obj, {
input
}) => {
}
};
const Addoffice = {
type: OfficeType,
args: {
room: {
type: graphql.GraphQLString
},
location: {
type: graphql.GraphQLString
},
users: {
type: new graphql.GraphQLList(userInputType)
}
},
resolve: (obj, {
input
}) => {
}
};
const Rootmutation = new graphql.GraphQLObjectType({
name: 'Rootmutation',
fields: {
Adduser: Adduser,
Addoffice: Addoffice
}
});
This code is throwing error as
Rootmutation.Addoffice(users:) argument type must be Input Type but got: [user].
I want to add the actual fields in database as well as associated tables' fields but couldn't figure out the problem.
Updated:
1-Added GraphQLInputObjectType:
const officeInputType = new graphql.GraphQLInputObjectType({
name: 'officeinput',
fields: () => {
return {
room: {
type: graphql.GraphQLString
},
location: {
type: graphql.GraphQLString
}
}
}
});
const userInputType = new graphql.GraphQLInputObjectType({
name: 'userinput',
fields: () => {
return {
name: {
type: graphql.GraphQLString
},
age: {
type: graphql.GraphQLString
}
}
}
});
2-Added userinputtype instead of usertype in AddOffice.
Now the error is
Rootmutation.Addoffice(user:) argument type must be Input Type but got: userinput.
The problem is that you provided userType as one of the argument types for the Addoffice mutation. userType cannot be an argument type. Instead, you must use an input type.
There are two object types: output and input types. Your userType and officeType are output types. You need to create an input type using GraphQLInputObjectType [docs]. It will likely have very similar fields. You can use that as a type on your argument field.
const userInputType = new graphql.GraphQLInputObjectType({
name: 'UserInput',
fields () => {
return {
_id: {
type: graphql.GraphQLID
},
// ...
};
}
});

Creating Primary and Foreign Key relations in Sequelize

I have 2 models Project model and Task model defined in sequelize as shown below
import { INTEGER, STRING, DATE } from 'sequelize';
import sequelize from '../sequelize';
import Task from './task.model'
const ProjectModel = sequelize.define('project', {
project_id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true
},
phabricator_project_id: {
type: STRING,
allowNull: false
},
name: {
type: STRING
},
description: {
type: STRING
},
start_date: {
type: STRING,
},
end_date: {
type: STRING
}
},
{
timestamps: false
}
);
export default ProjectModel;
and the task model
import { INTEGER, STRING, DATE } from 'sequelize';
import sequelize from '../sequelize';
const TaskModel = sequelize.define('task', {
task_id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true
},
title: {
type: STRING
},
status: {
type: STRING
},
priority: {
type: STRING
},
description: {
type: STRING
},
tool_project_id: {
type: STRING
},
date_modified: {
type: STRING
}
},
{
timestamps: false
}
);
export default TaskModel;
What I want to achieve is to create a relation between tool_project_id in TaskModel and phabricator_project_id in ProjectModel (they are same values only diff column names are given) and write a query for a GET request which outputs the data in form shown below
{ {project1Details,TaskDetails-->{task1, task2, task3},
{project2Details,TaskDetails-->{task4, task5, task6},
{project3Details,TaskDetails-->{task7, task8, task9},
{project4Details,TaskDetails-->{task10, task11, task12} }
All the database design has been done accordingly and another file is called to create all these databases. This is written in typescript and I tried this as a GET method
listByProjects(req, res) {
TaskModel.belongsTo(ProjectModel, { as: 'task' , foreignKey: 'tool_project_id'});
ProjectModel.findAll({
include:[{model:TaskModel}],
where:{status:'open'}
}).then(function(projects) {
res.json(projects);
});
}
Here in this method I define the relation and try to list all 'open' tasks and send them back as response but I am getting the error
Unhandled rejection Error: task is not associated to project!
ANY HELP TO THIS PROBLEM WOULD BE WONDERFULL
The answer to this question is that when creating the table we should create the relation and then create the table such as
Create the relation also the name of the key should be same so as to create relation.
TaskModel.belongsTo(ProjectModel, {foreignKey: 'project_id' });
ProjectModel.hasMany(TaskModel, { foreignKey: 'project_id' });
Then create the table project and then tasks
ProjectModel.sync({ force: false }).then(function () {
console.log('Project table created');
TaskModel.sync({ force: false }).then(function () {
console.log('Task table created');
});
});
then in the API method, you are invoking just include the model which you want to provide to get the required data.
ProjectModel.findAll({
include: [{
model: TimeSheetModel,
where: {
status: "ACTIVE"
},
}],
}).then(function (projects) {
const responseData = {
'status': 1,
'message': 'List successfull.',
'projects': projects,
};
res.json(responseData);
}).catch(error => {
const responseData = {
'status': 1,
'message': error.message,
'projects': [],
};
res.json(responseData);
})
This uses nodemon and sequilize to manage node and relations of the table respectively

Single JSON argument in mutation

Below I'm trying to set a mutation example with one object-arg credentials. I had this working previously then all the sudden it stopped working failing on the JSON part. Why can't I send json through credentials?
import {
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLInt,
GraphQLInputObjectType,
GraphQLNonNull,
graphql
} from 'graphql'
let requestType = new GraphQLInputObjectType({
name: 'Request',
fields: {
name: {type: GraphQLString},
}
})
let responseType = new GraphQLObjectType({
name: 'Response',
fields: {
name: {type: GraphQLString},
age: {type: GraphQLInt}
}
})
let schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
author: {
type: GraphQLString,
resolve: (source, args, context, info) => {
return 'Thomas Reggi'
}
}
}
}),
mutation: new GraphQLObjectType({
name: 'Mutation',
fields: {
example: {
type: responseType,
args: {
credentials: {
name: 'credentials',
type: requestType
}
},
resolve: (source, args, context, info) => {
return {
'name': 'Thomas Reggi',
'age': 26
}
}
}
}
})
})
let credentials = { name: "Thomas Reggi" }
let requestString = `
mutation {
example(credentials: ${JSON.stringify(credentials)}) {
name,
age
}
}`
graphql(schema, requestString).then(result => {
console.log(result)
})
Here's the error:
{ errors:
[ Syntax Error GraphQL request (3:25) Expected Name, found String "name: "
2: mutation {
3: example(credentials: {"name":"Thomas Reggi"}) {
^
4: name,
] }
Where does the reference to Name come from? Why is this throwing an error?
Just found out the hard way. You can't have {"name": "Thomas Reggi"} because name is in quotes.
This query works.
mutation {
example(credentials: {name: "Thomas Reggi"}) {
name,
age
}
}

aurelia bridge kendo grid refresh

I'm trying to use Aurelia KendoUi Bridge in my application.
In my code I have a service which returns a new KendoDataSource :
export class KendoDataSource {
ToKendoDataSource(data: any, recordCount: number, pageSize: number, currentPage: number): any {
return {
transport: {
read: (p) => {
p.success({ data: data, recordCount: recordCount });
}
},
pageSize: pageSize,
serverPaging: true,
serverFiltering: true,
serverSorting: true,
schema: {
data: (result) => {
console.log('Transforming data to kendo datasource.');
return result.data;
},
total: (result) => {
return result.recordCount;
}
}
};
}
}
And this is my viewModel:
#inject(HttpService, KendoDataSource, EventAggregator)
export class GroupList {
grid: any;
gridVM: any;
datasource: any;
pageable: any;
subscriber: any;
paginationDetailsRequest: PaginationDetailsRequest;
test: string;
constructor(private httpService: HttpService, private kendoDataSource: KendoDataSource, private eventAggregator: EventAggregator) {
this.httpService = httpService;
this.kendoDataSource = kendoDataSource;
this.eventAggregator = eventAggregator;
this.paginationDetailsRequest = new PaginationDetailsRequest(4, 1);
this.GetGroups(this.paginationDetailsRequest);
this.datasource = {
transport: {
read: {
url: 'PersonGroup/GetGroups',
type: 'POST',
contentType: "application/json; charset=utf-8",
dataType: 'json'
},
parameterMap: function (data, type) {
if (type == "read") {
let paginationDetails = new PaginationDetailsRequest(data.pageSize, data.page);
if(data.sort && data.sort.length > 0){
paginationDetails.orderBy = data.sort[0].field;
paginationDetails.OrderDesc = (data.sort[0].dir == 'desc');
}
console.log(this.datasource);
return JSON.stringify(paginationDetails);
}
}
},
schema: {
data: "data.currentPageData",
total: "data.totalCount"
},
pageSize: 2,
serverPaging: true,
serverFiltering: true,
serverSorting: true
};
};
attached() {
this.subscriber = this.eventAggregator.subscribe('Search', response => {
this.search(response);
});
}
activate() {
this.pageable = {
refresh: true,
pageSizes: true,
buttonCount: 10
};
}
GetGroups(paginationDetails: PaginationDetailsRequest): void {
this.httpService.post('PersonGroup/GetGroups', paginationDetails)
.then(response => response.json())
.then(groups => {
console.log(groups);
if (groups.succeeded) {
this.datasource = this.kendoDataSource.ToKendoDataSource(groups.data.currentPageData, groups.totalCount, groups.pageSize, groups.currentPage);
this.grid.setDataSource(this.datasource); // initialize the grid
}
else {
//TODO: Show error messages on screen
console.log(groups.errors);
}
})
.catch(error => {
//TODO: Show error message on screen.
console.log(error);
});
}
search(searchDetails: Filter) {
console.log(searchDetails);
this.paginationDetailsRequest.filters.push(searchDetails);
console.log(this.paginationDetailsRequest);
this.GetGroups(this.paginationDetailsRequest);
}
detached() {
this.subscriber.dispose();
}
}
I understand that kendo does not have two-way data binding, But I'm trying to find a way to refresh the grid when I filter the data and the data source has changed.
Thanks.
I had this problem and found the solution by creating a new dataSource and assigning it to setDataSource, as follows... Note, getClients() is a search activated by a button click.
Here is the grid:
<ak-grid k-data-source.bind="datasource"
k-pageable.bind="{ input: true, numeric: false}"
k-filterable.bind="true"
k-sortable.bind="true"
k-scrollable.bind="true"
k-widget.bind="clientgrid"
k-selectable.bind="true">
<ak-col k-title="First Name" k-field="firstName" k-width="120px"></ak-col>
<ak-col k-title="Last Name" k-field="lastName" k-width="120px"></ak-col>
<ak-col k-title="Email Address" k-field="primaryEmail" k-width="230px"></ak-col>
</ak-grid>
And here is the code that updates the dataSource
public getClients()
{
console.log("ClientService.getClients");
this.clientService.getClients()
.then(result =>
{
this.clientList = result;
// the new datasource in the next line is displayed
// after the call to setDataSource(ds) below.
let ds: kendo.data.DataSource = new kendo.data.DataSource({
data: this.clientList,
schema: {
model: {
id: "id",
fields: {
firstName: { type: 'string' },
id: { type: 'number' },
lastName: { type: 'string' },
primaryEmail: { type: 'string' }
}
}
},
pageSize: 10
});
this.clientgrid.setDataSource(ds);
this.clientgrid.refresh();
})
.catch(err => console.log("Error returned from getClients " + err));
}
You don't really need to create a brand new datasource. To refresh the grid after changing the underlying data you can just replace the data element in the dataSource like so:
this.clientgrid.dataSource.data(this.datasource.data);