How to make GraphQL schema for MySQL models? - mysql

I have the following table in mysql.
Users
CREATE TABLE users(
id INTEGER AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
and Posts
CREATE TABLE posts(
id INTEGER AUTO_INCREMENT PRIMARY KEY,
created_by INTEGER NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
post_title VARCHAR(100) NOT NULL UNIQUE,
post_body VARCHAR(255) NOT NULL,
slug VARCHAR(100) NOT NULL UNIQUE,
FOREIGN KEY (created_by) REFERENCES users(id)
);
I am using nodejs and the mysql npm package. How can I make graphQL schema for the models?
I searched a lot but failed to find any solution to this. Mostly people are using sequelize for this purpose. Is it better than mysql package?

Yes You can use sequelize orm for connecting graphql to Mysql database
Refference :- http://docs.sequelizejs.com/manual/installation/getting-started
Sample Schema and resolvers are given
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];
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 };
resolver.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;

You can try a new open source tool called SwitchQL (github.com/SwitchQL/SwitchQL). I've been working on the project for a while.
You pass it your connection string and it returns everything you need to run a graphql server on top of an existing database. It also returns Apollo compliant client mutation and queries.
We only support Postgres at the moment, but if you'd like to look into helping us support MySQL let me know!

Related

Running Sql migrations throws syntax error

I have created my first migration using sequelize-cli, now when I enter npx sequelize-cli db:migrate to run migration and create table in DB, I get error
I look into documentation could not find how and what should go into migration file.
Error
ERROR: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'NUMBER, `otp` INTEGER, `otp_expiration_date` DATETIME, `createdAt` DATETIME NOT ' at line 1
My migration File
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('Users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
phone_number: {
type: Sequelize.NUMBER
},
otp: {
type: Sequelize.INTEGER(4)
},
otp_expiration_date: {
type: Sequelize.DATE
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
})
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Users');
}
};
My User Model:
const moment = require('moment');
'use strict';
module.exports = (sequelize, DataTypes) => {
class User extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
}
};
User.init({
name: {
type: DataTypes.STRING,
allowNull: false
},
phone_number: {
type: DataTypes.NUMBER
},
otp: {
type: DataTypes.INTEGER(4)
},
otp_expiration_date: {
type: DataTypes.DATE,
set(value) {
// convert regular Date to moment Date
value = moment(value).add(5, 'minutes');
this.setDataValue('otp_expiration_date', value);
}
},
is_otp_expired: {
type: DataTypes.VIRTUAL,
get() {
// otp_expiration_date < current date
return this.getDataValue(otp_expiration_date).isAfter(moment()) ? true : false
}
}
}, {
sequelize,
modelName: 'User',
});
return User;
};
I have tried
changing datatypes
moving getters into migration
removing createdAt
[SOLVED]
Problem was with DataType of phone_number , there is no Sequelize.NUMBER type per Sequelize docs
I confused Sequelize DataTypes with MySql DataTypes
phone_number: {
type: DataTypes.NUMBER <---- bug
}
Solution
phone_number: {
type: DataTypes.INTEGER <---- solution
}

How to write object id reference of mongoose using Sequelize for MySql in Node.js

I am trying to write the category model for MySQL using Sequelize taking reference from this code of mongoose model in node(shared below) . I have gone through some articles but going through some confusion. Please help.
const mongoose = require("mongoose");
const categorySchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
trim: true,
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
},
);
module.exports = mongoose.model("Category", categorySchema);
Here I've tried my code in using Sequelize for MySql but got stuck in createdBy block. Need Help!!!
module.exports = (sequelize, Sequelize) => {
const Category = sequelize.define("categories", {
name:{
type: Sequelize.STRING,
required: true,
trim: true,
},
createdBy:{
}
});
return Category;
};
The createdBy value from Mongo will be an ObjectID, which you can save as a string in MySQL, so use a DataTypes.STRING type in the definition, then set it on insert/update/etc from your Mongo lookup. The Sequelize variable in your example should be DataTypes as well.
module.exports = (sequelize, DataTypes) => {
const Category = sequelize.define('categories', {
name: {
type: DataTypes.STRING,
required: true,
trim: true,
},
createdBy: {
type: DataTypes.STRING,
// any other settings
},
});
return Category;
};
// get the value from mongoose
const createdBy = ...;
// set it on the create for your model to insert.
await Category.create({
name: 'Some Name',
createdBy,
});

Sequelize primaryKey as UUID in MySql database - not working

I'm facing the problem with implementation model in Sequalize with primary key as uuid. I follow step by step all instruction, but still I cannot solve it.
This is how my model looks like:
'use strict';
module.exports = (sequelize, DataTypes) => {
var Todo = sequelize.define('Todo', {
title: DataTypes.STRING,
description: DataTypes.STRING,
test: DataTypes.UUID
}, {});
Todo.associate = function(models) {
// associations can be defined here
};
return Todo;
};
And migration file:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Todos', {
id: {
primaryKey: true,
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV1
},
title: {
type: Sequelize.STRING
},
description: {
type: Sequelize.STRING
},
test: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Todos');
}
};
After calling post method to create element, I habe got an empty value in ID column, and NULL in test column...
The Datebase is MySql.
Is there any other way to do it?
1) Setting Custom Keys in Sequelize
I'm facing the problem with implementation model in Sequalize with
primary key as uuid.
By default, if your model does not contain a key with primaryKey: true, then sequelize assumes a PK of type INTEGER named id.
In your case, it seems like you wish to make your own custom PK.
Use the following in your model:
var Todo = sequelize.define('Todo', {
id: {
primaryKey: true,
type: DataTypes.UUID
}
// rest of properties
});
2) Validations
After calling post method to create element, I habe got an empty value in ID column, and NULL in test column...
Without much information regarding not only your query, but how you seeded the database, it's hard to answer specifically.
However, it doesn't surprise me that test column was null, because you have not listed any validations. Thus if you seed/create rows that do not set a test value, it will be null.
To create validations do the following
model:
var Todo = sequelize.define('Todo', {
// rest of properties
test: {
allowNull: false
type: DataTypes.UUID,
validate: {
notNull: true
}
}
});
migration:
queryInterface.createTable('Todos', {
// rest of properties
test: {
allowNull: false,
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4
}
});
3) Keeping Models and Migrations Synchronized
(see: https://stackoverflow.com/a/49796801/8954866) In sequelize, models and migrations are not automatically in-sync with one another, except when initially generated using sequelize-cli model:generate. I'm unsure if you ran your migrations or if you were running your query in a unit test against the model. But you have to make sure they are synchronized. A primary example is that in the above case, your migration says id is of type UUID, but your model will think it's of type INTEGER.
References
Setting Custom Primary Keys
Validations and allowNull
Make sure your package.json has updated uuid module, this was the problem in my case.
accessSecret: {
type: DataTypes.UUID,
defaultValue: Sequelize.UUIDV4,
allowNull: false,
unique: true,
},
This works fine,
just make sure package.json has
"uuid": "^8.2.0",
Looks like this issue is the same for both MySQL and Postgres: Sequelize needs to use the defaultValue setting of the model to point to its uuid dependency.
For example, for an entity called Product, create your migration like this:
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("Products", {
id: {
type: Sequelize.UUID,
primaryKey: true,
defaultValue: Sequelize.UUIDV4,
},
productName: {
type: Sequelize.STRING,
},
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable("Products");
},
};
Then, create your model like this:
"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
class Product extends Model {
static associate(models) {
// associations
}
}
Product.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
productName: DataTypes.STRING,
},
{
sequelize,
modelName: "Product",
}
);
return Product;
};
Note that in your model, you're defining defaultValue: DataTypes.UUIDV4.
When you create a new instance of Product, you only need the name field. The UUID is auto-generated by the Sequelize model.
const {
Product
} = require("./models");
const createProduct = async (name) => {
const newProductData = { productName: "First product" };
const creationResult = await Product.create(newProductData);
};
The id field in the model will trace the defaultValue setting back to the v4() function of the uuid package that Sequelize relies on to auto-generate the UUID as part of the model operation, not as part of native Postgres.

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

Mongoose many to many relation and circular structure to JSON

I am trying to create a classic Articles/Categories association with mongoose.
Everything works fine, but since I am trying to expose the query results as JSON, I get a Converting circular structure to JSON error.
I know the issue is related in cross referencing models, but I don't know how to solve this.
Here are my model schemas.
var ArticleSchema = new Schema({
created: {
type: Date,
default: Date.now
},
title: {
type: String,
default: '',
trim: true
},
content: {
type: String,
default: '',
trim: true
},
user: {
type: Schema.ObjectId,
ref: 'User'
},
categories: [{
type: Schema.ObjectId,
ref: 'Category'
}]
});
ArticleSchema.statics.load = function(id, cb) {
this.findOne({
_id: id
}).populate('user', 'name username').populate('categories', 'title').exec(cb);
};
/**
* Category Schema
*/
var CategorySchema = new Schema({
created: {
type: Date,
default: Date.now
},
title: {
type: String,
default: '',
trim: true
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
},
{
toObject: { virtuals: true },
toJSON: { virtuals: true }
});
/**
* Virtual Schema
*/
var articles = CategorySchema.virtual('articles');
articles.get(function () {
return Article.find({categories : { $eq: this }});
});
Seems to be like you have circular references, this mean that the json you create is calling it self in somere.. Im not sure if this line could be the problem:
articles.get(function () {
return Article.find({categories : { $eq: this }});
});
why dont you try with a hardcore value in $eq ??