How do I create a Mutation with arguments for a resolver defined in graphql-yoga as:
const resolvers =
Mutation: {
createProject(root, args) {
const id = (Number(last(data.projects).id) + 1).toString()
const newProject = { ...args, id: id }
...
I've tried the following:
mutation CreateProject($name: String!) {
createProject {
data: {
name: $name
}
}
}
and
mutation CreateProject($name: String!) {
createProject($name: name) {
statusCode
}
}
which produces
and various other structures unsuccessfully.
There seems to be no reference to a Mutation in either the project README or any of the three examples.
Update
I'm now using:
mutation CreateProject($name: String!) {
createProject(name: $name) {
id
name
}
}
which is so similar to examples I've seen on the net that I feel it must be valid & the syntax is not rejected.
The schema definition is:
scalar ID
type Project {
id: ID
type: ProjectType
name: String
}
interface MutationResult {
statusCode: Int
message: String
}
type ProjectMutationResult implements MutationResult {
statusCode: Int
message: String
project: Project
}
type Mutation {
createProject: ProjectMutationResult
}
However on submitting the mutation, I receive:
{
"error": {
"errors": [
{
"message": "Unknown argument \"name\" on field \"createProject\" of type \"Mutation\".",
"locations": [
{
"line": 2,
"column": 17
}
]
},
{
"message": "Cannot query field \"id\" on type \"ProjectMutationResult\".",
"locations": [
{
"line": 3,
"column": 5
}
]
},
{
"message": "Cannot query field \"name\" on type \"ProjectMutationResult\".",
"locations": [
{
"line": 4,
"column": 5
}
]
}
]
}
}
According to your type definition:
The createProject mutation does not expect any argument:
type Mutation {
createProject: ProjectMutationResult
}
The ProjectMutationResult type does not have an id field nor a name field:
type ProjectMutationResult implements MutationResult {
statusCode: Int
message: String
project: Project
}
So when you run the mutation:
mutation CreateProject($name: String!) {
createProject(name: $name) {
id
name
}
}
you have a complete discrepancy between what you're feeding your GraphQL server and what it's actually expecting.
So first of all, if you want to be able to set a name to your project when you create it, you need to amend your createProject definition to this:
type Mutation {
createProject(name: String!): ProjectMutationResult
}
(if you want the naming to be optional, set name to be of type String rather than String!)
Then, assuming you want to retrieve the newly created project id and name from your mutation, change the mutation itself to:
mutation CreateProject($name: String!) {
createProject(name: $name) {
project {
id
name
}
}
}
You need to do this because your createProject mutation returns a ProjectMutationResult which itself contains a project field of type Project, which is the one defining the id and name fields.
Related
I have a project in Node JS with Typescript in which I am creating an API to get data from a local JSON file depending on the given variable.
This is my model.ts:
interface ListProductBatchModel {
resp: ResultMsg
}
interface ResultMsg {
name?: string,
price?: string,
error?: string
}
export { ListProductBatchModel, ResultMsg };
This is my properties JSON file:
{
"CT": {
"name": "box",
"price": "5,00"
},
"CC": {
"name": "car",
"price": "6,00"
}
}
This is my controller.ts:
import * as logger from 'winston';
import { Controller, Get, Response, Route, SuccessResponse, Tags } from 'tsoa';
import { ListProductBatchModel } from './models/listProduct.models';
import { ListProductBatchUtils } from './utils/listProductBatch.utils';
#Route('/list/product')
#Tags('list-product')
export class ListProductBatchController {
private listProductBatchUtils: ListProductBatchUtils;
constructor() {
this.listProductBatchUtils = new ListProductBatchUtils();
}
#Get('/{codProduct}')
#SuccessResponse(200, 'Success Response')
async listProductBatch(codProduct: string): Promise<ListProductBatchModel> {
try {
const listProductBatch = await this.listProductBatchUtils.getDataProduct(codProduct);
return Promise.resolve(listProductBatch as ListProductBatchModel);
} catch (error) {
logger.info(JSON.stringify(error));
return Promise.reject(error);
}
}
}
This is my utils.ts:
import * as logger from 'winston';
import * as getProperty from '../json/product.json';
import { ListProductBatchModel, ResultMsg } from '../models/listProduct.models';
export class ListProductBatchUtils {
public async getDataProduct(codProduct: string): Promise<ListProductBatchModel> {
try {
let result: ResultMsg;
if (getProperty[codProduct.toUpperCase()]) {
result = {
name: getProperty[codProduct.toUpperCase()].name,
price: getProperty[codProduct.toUpperCase()].price
}
}else {
result = {
error: "ERROR"
}
}
logger.info('start')
return Promise.resolve({ resp: result });
} catch (error) {
logger.info(JSON.stringify(error));
return Promise.reject(error);
}
}
}
This is the error I get in getProperty [codProduct.toUpperCase ()]:
The element has a type "any" implicitly because the expression of type "string" cannot be used to index the type "{CT: {name: string; price: string;}; CC: {name: string; price : string;};} ".
No index signature was found with a parameter of type "string" in type "{CT: {name: string; price: string;}; CC: {name: string; price: string;};}".
My problem: I don't understand how the error is generated, what I want is to take the name and price properties that match the codProduct variable. Why can this happen? What am I doing wrong and how can I solve it?
Right now, codProduct is a string. When you're accessing getProduct via its subscript [], TypeScript expects you to use an index of getProduct (which, in this case is either "CT" or "CC").
You can satisfy the TypeScript compiler by casting your string as a keyof getProperty's type. Note that this will work at compile time, but will not guarantee that it is in fact a key of getProperty at runtime. But, since you're doing boolean checks already, that seems like it will be okay in your case.
Here's a simplified example:
const getProperty = {
"CT": {
"name": "box",
"price": "5,00"
},
"CC": {
"name": "car",
"price": "6,00"
}
};
type GetPropertyType = typeof getProperty;
function myFunc(input: string) {
const result = getProperty[input.toUpperCase() as keyof GetPropertyType].name;
}
I'm trying to mutate and query dynamic variables. The user has the choice to add as many variables as they want before sending them off to the server. For example, my app is a productivity app that allows a user to add as many metrics as they want to track their goal so if "Gym" is their goal, the metrics would be "running", "bench press", etc. My problem is, I'm unsure how to save them in the database since there is no pre-configured Schema for these user-created variables.
I've managed to send the variables to the back end using the following:
mutation CreateGoal ($title: String!, $description: String, $metric: [Json!]) {
createGoal(
data: {
title: $title
description: $description
metric: { set: $metric }
}
){
id
}
}
Schema:
type Mutation {
createGoal(data: CreateGoalInput!): Goal!
}
input CreateGoalInput {
title: String!
description: String
metric: GoalCreatemetricInput
}
input GoalCreatemetricInput {
set: [Json!]
}
Once the variables arrive in the resolver, it's in the Json format:
{ set: [ 'running', 'bench press' ] }
Normally, I'd simply save the variables through Prisma:
async createGoal(parent, { data }, { request, prisma }, info) {
const { title, description, metric } = data && data
return prisma.mutation.createGoal({
data: {
user: {
connect: {
email: user.email
}
},
title,
description,
}
}, info)
},
However, since the number of variables are unknown, how do I save 'metric' into my database?
If I were to try the following:
async createGoal(parent, { data }, { request, prisma }, info) {
const { title, description, metric } = data && data
return prisma.mutation.createGoal({
data: {
user: {
connect: {
email: user.email
}
},
title,
description,
metric,
}
}, info)
},
I get the error:
Error: Variable "$_v0_data" got invalid value [ "running", "bench
press" ] at "_v0_data.metric"; Field "0" is not defined by type
GoalCreatemetricInput.
If I were to try:
async createGoal(parent, { data }, { request, prisma }, info) {
const { title, description, metric } = data && data
return prisma.mutation.createGoal({
data: {
user: {
connect: {
email: user.email
}
},
title,
description,
metric: metric.set
}
}, info)
},
I get the error:
Error: Variable "$_v0_data" got invalid value ["running", "bench
press"] at "_v0_data.metric"; Field "0" is not defined by type
GoalCreatemetricInput. Variable "$_v0_data" got invalid value
["Asdfasdf", "Asdfasdf"] at "_v0_data.metric"; Field "1" is not
defined by type GoalCreatemetricInput.
I don't think you need to use the Json scalar at all. It looks like you're trying to pass an array of strings so instead of [Json!] you may just need to use [String!].
input CreateGoalInput {
title: String!
description: String
metric: [String!]
}
Then you should be able to get rid of
input GoalCreatemetricInput {
set: [Json!]
}
Here you should be able to pass the array of strings to the backend:
mutation CreateGoal ($title: String!, $description: String, $metric: [String!]) {
createGoal(
data: {
title: $title
description: $description
metric: $metric
}
){
id
}
}
And in your resolover I think all you need to do is:
async createGoal(parent, { data }, { request, prisma }, info) {
const { title, description, metric } = data && data
return prisma.mutation.createGoal({
data: {
user: {
connect: {
email: user.email
}
},
title,
description,
metric: { set: metric },
}
}, info)
},
So am new to GraphQL and I'm trying to resolve a mutation that has an Input type of an array. I'm getting this error
{
"data": {
"createSub": null
},
"errors": [
{
"message": "Variable '$data' expected value of type 'SubCreateInput!' but got: {\"apps\":[{\"name\":\"ma\",\"package\":\"me\",\"running\":true,\"isSysytem\":true}]}. Reason: 'apps' Expected 'AppListCreateManyInput', found not an object. (line 1, column 11):\nmutation ($data: SubCreateInput!) {\n ^",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"createSub"
]
}
]
}
This is my schema
type Mutation {
createSub(input:subInput): Sub
}
input subInput{
apps: [AppListInput]
}
type Sub{
id: ID!
apps: [AppList]
}
type AppList {
id: ID!
name: String
package: String
running: Boolean
isSysytem: Boolean
}
input AppListInput {
name: String
package: String
running: Boolean
isSysytem: Boolean
}
And this is my resolver
function createSub(root, args, context) {
return context.prisma.createSub({
apps: args.input.apps
})
}
The mutation/payload am sending on the Graphql playground is this
mutation{
createSub( input:{
apps: [{
name: "ma"
package: "me"
running: true
isSysytem: true
}],
})
{
apps{
name
}
}
}
When I console.log(args.input.apps) I'm getting this
[ [Object: null prototype] { name: 'ma', package: 'me', running: true, isSysytem: true } ]
This is the input AppListCreateManyInput generated in the schema
input AppListCreateManyInput {
create: [AppListCreateInput!]
connect: [AppListWhereUniqueInput!]
}
What could I be missing please?
You need to provide the appropriate object to createSub, as shown here. Because apps is a relation, you can't just pass an array of apps in -- after all, when creating the Sub, you may want to either create new apps and relate them to the newly created Sub, or simply relate existing apps to it.
return context.prisma.createSub({
apps: {
create: args.input.apps, // create takes an array of apps to create
}
})
If you wanted to connect existing apps instead of creating new ones, you would use connect instead of create and pass in an object specifying a where condition instead of an array.
I'm here with a problem with rich queries and convector chaincodes, everything works with mango queries, but when I pass content object it's is stringifyed and don't will be sent has an object, but is converted to a string "content":"{\"data\":\"1971\"}", obvious it fails the query
original sample query
{
"selector": {
"type": "io.worldsibu.examples.person",
"attributes": {
"$elemMatch": {
"id": "born-year",
"content": {
"data": "1971"
}
}
}
}
}
graphql query variables
{
"getByAttributeInput": {
"id": "born-year",
"content": {
"data": "1971"
}
},
"skip": 0,
"take": 10
}
packages/person-cc/src/person.controller.ts
chaincode controller method
#Invokable()
public async getByAttribute(
#Param(yup.string())
id: string,
#Param(yup.mixed())
value: any
) {
return await Person.query(Person, {
selector: {
type: c.CONVECTOR_MODEL_PATH_PERSON,
attributes: {
$elemMatch: {
id: id,
content: value
}
}
}
});
}
in docker logs we can view that value is content is sent has a string and not a object ex "content":"{\"data\":\"1971\"}"
{"selector":{"type":"io.worldsibu.examples.person","attributes":{"$elemMatch":{"id":"born-year","content":"{\"data\":\"1971\"}"}}}}
the trick is change #Param(yup.mixed()) to #Param(yup.object()) and now it works has expected, we can query attributes content value with arbitrary and complex objects
#Invokable()
public async getByAttribute(
#Param(yup.string())
id: string,
// #Param(yup.mixed())
#Param(yup.object())
value: any
) {
...
I'm setting up a nodeJS GraphQL API and I'm experimenting a blocking point regarding one of my resource output type.
The feature is a form that contain three different level :
Level 1- formTemplate
Level 2- formItems (templateId, type (video, image, question) - 1-N relation with formTemplate)
Level 3- formQuestions (0-1 relation with formItem if and only if formItems.type is 'question')
My GraphQL resource is returning all the templates in the database so it's an array that for each template is returning all his items and each item of type "question" needs to return an array containing the associated question.
My problem is : I really don't know how to return an empty object type for the formItems where type is different from "question" or if there is a better approach for this kind of situation
I've tried to look at GraphQL directives and inline fragments but I think it really needs to be manage by the backend side because it's transparent for the API consumer.
const formTemplate = new GraphQLObjectType({
name: 'FormTemplate',
fields: () => {
return {
id: {
type: new GraphQLNonNull(GraphQLInt)
},
authorId: {
type: new GraphQLNonNull(GraphQLInt)
},
name: {
type: new GraphQLNonNull(GraphQLString)
},
items: {
type: new GraphQLList(formItem),
resolve: parent => FormItem.findAllByTemplateId(parent.id)
}
}
}
})
const formItem = new GraphQLObjectType({
name: 'FormItem',
fields: () => {
return {
id: {
type: new GraphQLNonNull(GraphQLInt)
},
templateId: {
type: new GraphQLNonNull(GraphQLInt)
},
type: {
type: new GraphQLNonNull(GraphQLString)
},
question: {
type: formQuestion,
resolve: async parent => FormQuestion.findByItemId(parent.id)
}
}
}
})
const formQuestion= new GraphQLObjectType({
name: 'FormQuestion',
fields: () => {
return {
id: {
type: new GraphQLNonNull(GraphQLInt)
},
itemId: {
type: new GraphQLNonNull(GraphQLInt)
},
type: {
type: new GraphQLNonNull(GraphQLString)
},
label: {
type: new GraphQLNonNull(GraphQLString)
}
}
}
})
My GraphQL request :
query {
getFormTemplates {
name
items {
type
question {
label
type
}
}
}
}
What I'm expected is
{
"data": {
"getFormTemplates": [
{
"name": "Form 1",
"items": [
{
"type": "question",
"question": {
"label": "Question 1",
"type": "shortText"
},
{
"type": "rawContent"
"question": {}
}
]
}
]
}
}
I'd design your "level 2" items so that the "type" property corresponded to actual GraphQL types, implementing a common interface. Also, in general, I'd design the schema so that it had actual links to neighboring items and not their identifiers.
So if every form item possibly has an associated template, you can make that be a GraphQL interface:
interface FormItem {
id: ID!
template: FormTemplate
}
Then you can have three separate types for your three kinds of items
# Skipping VideoItem
type ImageItem implements FormItem {
id: ID!
template: FormTemplate
src: String!
}
type QuestionItem implements FormItem {
id: ID!
template: FormTemplate
questions: [FormQuestion!]!
}
The other types you describe would be:
type FormTemplate {
id: ID!
author: Author!
name: String!
items: [FormItem!]!
}
type FormQuestion {
id: ID!
question: Question
type: String!
label: String!
}
The other tricky thing is, since not all form items are questions, you have to specifically mention that you're interested in questions in your query to get the question-specific fields. Your query might look like
query {
getFormTemplates {
name
items {
__typename # a GraphQL builtin that gives the type of this object
... on Question {
label
type
}
}
}
}
The ... on Question syntax is an inline fragment, and you can similarly use it to pick out the fields specific to other kinds of form items.
Thank you David for your answer !
I've figured it out how to solve my problem using inline fragments and UnionTypes that seems to be the most adapted for this use case. Here is the code :
const formItemObjectType = new GraphQLUnionType({
name: 'FormItemObject',
types: [formItemContent, formItemQuestion],
resolveType(parent) {
switch (parent.type) {
case ('question'): return formItemQuestion
default: return formItemContent
}
}
})
and the GraphQL query using inline fragment:
query {
getFormTemplates {
name
items {
...on FormItemContent {
type,
meta
}
...on FormItemQuestion {
type,
meta,
question {
label
}
}
}
}
}