This is in a Node project where I am trying to write a graphql api for a simple blog.
I am using apollo and behind the scenes attempting to save data to a mongo db via mongoose.
I am currently getting the following error when I try to save a Post data.
"GraphQLError: ID cannot represent value: { type: "Buffer", data:
[Array] }", "at GraphQLScalarType.serialize
(/Users/name/projects/graphtime/node_modules/graphql/type/scalars.js:301:11)",
It is complaining about the Author insertion as part of the Post I believe.
Is there something wrong with my schema or how I am performing the query on the Apollo GraphiEditor?
The is the DB Schema
import mongoose from "mongoose";
const authorSchema = new mongoose.Schema({
name: { type: String, required: true },
avatar: { type: String },
});
export const Author = mongoose.model('Author', authorSchema);
const postSchema = new mongoose.Schema({
title: { type: String, required: true },
authors: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Author'
}
]
});
export const Post = mongoose.model('Post', postSchema);
This is the graphql Schema
type Post {
id: ID!
title: String
authors: [Author]
}
type Author {
id: ID!
name: String
avatar: String
}
type Mutation {
addPost(
title: String!,
authors: [ID!],
): Post
}
This is the grapql resolver.
const resolvers = {
Mutation: {
addPost: (_, args, context) => {
const newPost = new Post({
title: args.title,
authors: args.authors,
});
return newPost.save();
},
},
};
This is how I am querying in the editor which throws the error.
P.S: The DB already has an existing author in there and lets say the id for that row is: 63babc44e18d174016b03433
mutation {
addPost(
title: "New Post",
authors: ["63babc44e18d174016b03433"],
) {
title
authors {
id
name
}
}
}
You have defined the mutation in this way:
type Mutation {
addPost(
title: String!,
authors: [ID!],
): Post
}
That's mean addPost return a Post object, which is:
type Post {
id: ID!
title: String
authors: [Author]
}
So authors is an array of Authors objects, not the _ids.
You are getting the ID and saving into DB but the returned object does not match [Author]. Also note how the postSchema is mongoose.Schema.Types.ObjectId. Referenced to Authors, yes, but is an array of IDs.
So you can return an array of IDs instead the Author object or populate the query in mongoose because you have the reference.
I'm trying to get Mutation Update query in GraphQL Playground. I'm basic level in GraphQL and in learning phase. I don't know how to create udpate Mutation for the below Owner code.
Any idea what I'm missing in my code / query?
---Resolver---
> #Mutation(() => Owner) updateOwner(
> #Args('id', { type: () => Int }) id: number,
> #Args('updateOwnerInput') updateOwnerInput: UpdateOwnerInput) {
> return this.ownersService.update(id, updateOwnerInput); }
---Service---
update(id: number, updateOwnerInput: UpdateOwnerInput) {
return this.ownersRepository.update(id, updateOwnerInput);
}
---dto---
#InputType()
export class UpdateOwnerInput extends PartialType(CreateOwnerInput) {
#Column()
#Field(() => Int)
id: number;
}
---entity---
#Entity()
#ObjectType()
export class Owner {
#PrimaryGeneratedColumn()
#Field(type => Int)
id: number;
#Column()
#Field()
name: string;
#OneToMany(() => Pet, pet => pet.owner)
#Field(type => [Pet], { nullable: true })
pets?: Pet[];
}
---schema---
type Pet {
id: Int!
name: String!
type: String
ownerId: Int!
owner: Owner!
}
type Owner {
id: Int!
name: String!
pets: [Pet!]
}
type Query {
getPet(id: Int!): Pet!
pets: [Pet!]!
owners: [Owner!]!
owner(id: Int!): Owner!
}
type Mutation {
createPet(createPetInput: CreatePetInput!): Pet!
createOwner(createOwnerInput: CreateOwnerInput!): Owner!
updateOwner(id: Int!, updateOwnerInput: UpdateOwnerInput!): Owner!
}
input CreatePetInput {
name: String!
type: String
ownerId: Int!
}
input CreateOwnerInput {
name: String!
}
input UpdateOwnerInput {
name: String
id: Int!
}
---GraphQL Query (I don't know whether it is correct or wrong)
mutation {
updateOwner (updateOwnerInput:{
id:6,
name: "josh",
})
}
---error---
"message": "Field \"updateOwner\" of type \"Owner!\" must have a selection of subfields. Did you mean \"updateOwner { ... }\"?",
You need to make a selection of subfields to return (even if you're not interested in any):
mutation {
updateOwner (updateOwnerInput:{
id:6,
name: "josh",
})
{
id
name
}
}
I tried the below code. Update Mutation working and able to update the field in Database column.
mutation updateOwner{
updateOwner(id:2, updateOwnerInput: {
id:2,
name: "test2"
})
{
id,
name
}
}
Has a many to many relationship. Category - Product
I want to filter the products by category id.
I checked some examples and wrote this code below, but can't make it work
Can somebody help me out?
thanks
#Entity()
export class Product {
#PrimaryGeneratedColumn()
id: number;
#ManyToMany(() => Category, {eager: true})
#JoinTable({
name: 'product_category'
})
categories: Array<Category>;
}
#Entity()
export class Category {
#PrimaryGeneratedColumn()
id: number;
}
findProducts(categoryId: number) {
return this.find({
join: {alias: 'categories'},
where: (qb) => {
qb.where('categories.id = :categoryId', {categoryId: filter.categoryId})
}
});
}
I read some documentation and debugged the typeorm code and successfully created the query:
I made some modification on the many to many relation:
#ManyToMany(() => Category, {eager: true})
#JoinTable({
name: 'product_category',
inverseJoinColumn: {
name: 'category_id',
referencedColumnName: 'id'
},
joinColumn: {
name: 'product_id',
referencedColumnName: 'id'
}
})
categories: Array<Category>;
and the query:
{
relations: ['categories'],
where: (qb: SelectQueryBuilder<Product>) => {
qb.where('category_id = :categoryId', {categoryId: categoryId})
}
}
I hope someone else will find it useful
you don't need to be define many-to-many in two way side
its must define in one of side
https://typeorm.biunav.com/en/many-to-many-relations.html#what-are-many-to-many-relations
and better way use the Separating Entity Definition
https://orkhan.gitbook.io/typeorm/docs/separating-entity-definition
I have a GraphQL/Apollo server using Sequelize/mysql. My GraphQL types (Employee, Contractor) each implement a Person interface. My db model contains an employee, contractor, and events table. I would like my Person Type to have a "has many" relationship with Events. While my Event Type "belongs to" a Person Type either Employee or Contractor.
I'm guessing it has something do do with the person_id field in the Event Type. I can get it to work without the interface on a single table "Employee" and changing person_id to employee_id. So im guessing it just doesnt know how to distinguish between Employee and Contractor to reference that table?
//typeDefs.js
const typeDefs = gql`
type Event {
id: ID!
person_id: ID!
location: String!
escort: String!
}
interface Person {
id: ID!
first_name: String!
last_name: String!
department_company: String!
events: [Event]
}
type Employee implements Person {
id: ID!
first_name: String!
last_name: String!
department_company: String!
events: [Event]
employee_sh_id: String
}
type Contractor implements Person {
id: ID!
first_name: String!
last_name: String!
department_company: String!
events: [Event]
escort_required: Boolean!
}
//Employee model
module.exports = (sequelize, DataTypes) => {
const Employee = sequelize.define('Employee', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
first_name: DataTypes.STRING,
last_name: DataTypes.STRING,
department_company: DataTypes.STRING,
emplyee_sh_id: DataTypes.STRING
}, {});
Employee.associate = function(models) {
Employee.hasMany(models.Event);
};
return Employee;
};
// Contractor model
module.exports = (sequelize, DataTypes) => {
const Contractor = sequelize.define('Contractor', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
first_name: DataTypes.STRING,
last_name: DataTypes.STRING,
department_company: DataTypes.STRING,
escort_required: DataTypes.BOOLEAN,
}, {});
Contractor.associate = function(models) {
Contractor.hasMany(models.Event);
};
return Contractor;
};
// Event model
module.exports = (sequelize, DataTypes) => {
const Event = sequelize.define(
"Event",
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
person_id: DataTypes.INTEGER,
location: DataTypes.STRING,
escort: DataTypes.STRING
},
{}
);
Event.associate = function(models) {
Event.belongsTo(models.Employee),
Event.belongsTo(models.Contractor)
};
return Event;
};
// resolvers.js
const resolvers = {
Query: {
async employee(root, { id }, { models }) {
return models.Employee.findByPk(id);
},
async contractor(root, { id }, { models }) {
return models.Contractor.findByPk(id);
},
async employees(root, args, { models }) {
return models.Employee.findAll();
},
async contractors(root, args, { models }) {
return models.Contractor.findAll();
},
async event(root, { id }, { models }) {
return models.Event.findByPk(id);
},
async events(root, args, { models }) {
return models.Event.findAll();
}
},
Mutation: {
async addEmployee(
root,
{
first_name,
last_name,
department_company,
employee_sh_id
},
{ models }
) {
return models.Employee.create({
first_name,
last_name,
department_company,
employee_sh_id
});
},
async addContractor(
root,
{
first_name,
last_name,
department_company,
escort_required,
},
{ models }
) {
return models.Contractor.create({
first_name,
last_name,
department_company,
escort_required,
});
},
async addEvent(
root,
{ person_id, location, escort },
{ models }
) {
return models.Event.create({
person_id,
location,
escort
});
},
Person: {
__resolveType: person => {
if (person.employee) {
return "Employee";
}
return "Contractor";
}
},
Employee: {
events: (parent, args, context, info) => parent.getEvents(),
},
Contractor: {
events: (parent, args, context, info) => parent.getEvents(),
}
};
Do you even need an interface?
The primary purpose behind abstract types like interfaces and unions is that they allow a particular field to resolve to one of a set of types. If you have Contractor and Employee types and want a particular field to return either type, it makes sense to add an interface or union to handle that scenario:
type Event {
contractors: [Contractor!]!
employees: [Employee!]!
people: [Person!]! # could be either
}
If you don't need this functionality, you don't really need any abstract types. (Side note: you can use interfaces just to enforce congruence between types that share fields, but at that point you're just using them as a validation tool and their existence probably shouldn't impact how you design the underlying data layer).
There is no silver bullet
Dealing with inheritance in relational databases can be tricky and there's no one-size-fits-all answer. Things get even weirded when using Sequelize or another ORM because your solution has to work within the limits of that particular library as well. Here's a couple of different ways you could approach this problem, though it's by far not an exhaustive list:
If you only have a couple of fields that return a Person type, you can get away with having separate tables and separate models and just merging the results yourself. Something like:
people: async (event) => {
const [employees, contractors] = await Promise.all([
event.getEmployees(),
event.getContractors(),
])
const people = employees.concat(contractors)
// Sort here if needed
return people
}
This does mean you're now querying the DB twice, and potentially spending extra time doing sorting that the DB would have otherwise done for you. However, it means you can maintain separate tables and models for Contractors and Employees, which means querying for those entities is straightforward.
Lump both Contractors and Employees under a single table, using some kind of type field to distinguish between the two. You can then use scopes to help you model the relationships appropriately in Sequelize:
Event.hasMany(models.Person, { as: 'employees', scope: {type: 'EMPLOYEE'} ... })
Event.hasMany(models.Person, { as: 'contractors', scope: {type: 'CONTRACTOR'} ... })
Event.hasMany(models.Person, { as: 'people', /** no scope **/ ... })
This works, even if it doesn't "feel" right to have everything in one table. You have to remember to scope your associations and queries correctly.
If you're using Sequelize strictly as an ORM and not generating your database from your Sequelize models (i.e. not calling sync), it's also possible to model a view as a Sequelize model. Views are a bit of a pain to write and maintain, but this would allow you keep separate tables for Employees and Contractors while creating a virtual table from the other two that could be used to query all People.
I want to query a nested field for multiple variables.
In this case, I want to query RPR, but only return RPR if the label of the nested Region is given. One variable for the nested Region (field: label) works fine, but how can I filter on multiple variables of the same field?
The way I see it, when I call up the query-client (in my case through Apollo), I want to give an array as a variable and let the query in the backend go through that array and return results based on any of the variables given in the array.
The resolver does nothing more then:
rPRs: (root, args, ctx, info) => {
return ctx.db.query.rPRs(args, info);
}
The relevant part of the schema:
type RPR {
id: ID! #Unique
RPRID: String! #Unique
state: String
region: Region!
resource: Resource!
price: Float
theme: String
editionTitle: String
information: String
}
type Region {
id: ID! #Unique
regionID: String! #Unique
label: String! #Unique
name: String! #Unique
aov: Float!
aov_multiplier: Float!
}
The current query to retrieve all 'RPR' with nested regions:
query ADVICE_RESOURCES_QUERY($theme: String, $regio: String) {
rPRs(where: {
theme: $theme,
region: {
label: $regio
}
})
{
RPRID
region {
label
}
}
}
you should be able to use the label_in filter and provide the array of Strings to it. Your where would then look like this:
where: {
theme: $theme,
region: {
label_in: $regio
}
}