can't use type 'object' for Type-GraphQL field types - graphql

I would like to create a GraphQL layer for my NestJs API. So based on this interface holding a key/value pairs
export interface Mapping {
id: string;
value: object;
}
I created a DTO acting as a return type for the GraphQL queries
#ObjectType()
export class MappingDTO {
#Field(() => ID)
id: string;
#Field()
value: object;
}
So when I want to find all mappings I would come up with this
#Query(() => [MappingDTO])
public async mappings(): Promise<Mapping[]> {
return this.mappingsService.getMappings();
}
Unfortunately I can't use object for the value field. I'm getting this error
NoExplicitTypeError: You need to provide explicit type for
MappingDTO#value !
When chaning the type e.g. to string no error gets thrown on startup. The thing is that I can't specify an explicit type because my MongoDB stores objects holding "anything".
How can I fix my resolver or DTO to deal with an object type?
Edit:
Same for the resolver decorator. When I have this
#Query(() => Object)
public async getValueByKey(#Args('id') id: string): Promise<object> {
return this.mappingsService.getValueByKey(id);
}
I get this error
Error: Cannot determine GraphQL output type for getValueByKey

Sounds like you should be using a custom JSON scalar. Like this one.
import { GraphQLJSONObject } from 'graphql-type-json'
This can then either be used directly in the field decorator:
#Field(() => GraphQLJSONObject)
value: object
or you can leave the decorator as #Field() and add the appropriate scalarsMap to the options you pass to buildSchema. See the docs for additional details.

Just to point out the fact that the whole idea of GraphQL is declarative data fetching.
Also you can define custom types in GraphQL the scalar types don't have object as option, but you can define a type of yourself like this.
type customObject {
keyOne: Int!
KeyTwo: String!
...
key(n): ...
}
and use this type in Interfaces, Query, Mutations:
export interface Mapping {
id: string;
value: customObject;
}
You might think that you will have to define every key of the object you are storing in the mongoDB in this type customObject, but you don't have to. this is where the GraphQL declarative data fetching comes in. You only define those fields in the type (customObject) that you want to use on the front end, you don't need to fetch the complete object from DB.

Related

Spring Boot GraphQL: Expected type to be a GraphQLInputType, but it wasn't

I am trying to set up a GraphQL endpoint in Spring Boot, and when I try to run my App, I get the following error:
Expected type 'Order' to be a GraphQLInputType, but it wasn't! Was a type only permitted for object types incorrectly used as an input type, or vice-versa?
Here is my models.graphqls:
type Order {
id: String!
storeOrderId: String
connectionId: String
}
type Mutation {
createOrder(order: Order): Order
}
This is happening because you're trying to use a type for an input, which isn't allowed.
type Order {
Should be...
input Order {
So that the whole thing looks like:
I am trying to set up a GraphQL endpoint in Spring Boot, and when I try to run my App, I get the following error:
Expected type 'Order' to be a GraphQLInputType, but it wasn't! Was a type only permitted for object types incorrectly used as an input type, or vice-versa?
Here is my models.graphqls:
input Order {
id: String!
storeOrderId: String
connectionId: String
}
type Mutation {
createOrder(order: Order): Order
}
if it still does't work after use a input type, There may be other problems here.
the input java types should have public getters,
the input java types should also implement Serializable interface

Exception using nested Input Types in GraphQL and Spring Boot

I'm trying to create a Mutation that receives an input type like this:
input Cart {
items: [CartItem!]!
client: String!
}
input CartItem {
productId: Int!
qty: Int!
}
And my Mutation goes like this:
type Mutation {
createOrder(cart: Cart!): Order!
}
But when I try to run my Application I get the following exception:
Caused by: com.coxautodev.graphql.tools.SchemaError: Expected type 'CartItem' to be a GraphQLOutputType, but it wasn't! Was a type only permitted for object types incorrectly used as an input type, or vice-versa?
What's going on?
It is a known bug of Graphql-java-tools: https://github.com/graphql-java-kickstart/graphql-java-tools/issues/216
The input java types should have public getters.
You should initiate a proper class implementation for their respective graphql schema declaration.
You can check an example here: https://github.com/MerNat/graphql-spring-boot-nested-input-types-example
except the input java types should have public getters, the input java types should also implement Serializable interface

NestJS + Typeorm + Graphql: correct design pattern for DTO in nested relations

Say i have a Typeorm entity definition like this:
#Entity()
export class MyEntity {
#PrimaryGeneratedColumn()
id: number;
#Column('varchar', { length: 500 })
name: string;
...
#OneToOne(type => DocumentEntity)
#JoinColumn()
myDoc: DocumentEntity;
#OneToMany(type => DocumentEntity, document => document.myEntity)
#JoinColumn()
otherDocs: DocumentEntity[];
...
}
so it has several entity relations, OneToMany/OneToOne
How do I approach this when crafting my DTOs?
Here I have an example DTO:
export class CreateMyEntityInputDto {
#IsString()
name: string;
...
#IsOptional()
myDoc: DocumentEntity;
#IsOptional()
otherDocs: DocumentEntity[];
....
}
I'm unclear on the best approach via Graphql
Current graphql interface:
####################
# #input
####################
input CreateDealInput {
name: String
...
myDoc: DocumentInput
otherDocs: [DocumentInput]
}
If I were designing a 'traditional' RESTful service, I would create my documents in the DB via a separate endpoint, wait for a success that returns documentID(s):int
then specify those ids as plain ints in the myEntity.myDoc / myEntity.otherDocs
fields when creating a new myEntity (at a separate endpoint).
Do i take the same approach here?
i.e. Do I create the documents entities in a separate query in graphql, parse out the created ids from the success response, then specify these int values in the DTO definition?
something like :
#IsOptional()
myDoc: int;
then, when creating the myEntity, load those (existing) document entities by id:int before finally saving via Typeorm?
Or do I pass all the document fields as nested entities in one big nested POST graphql query and use cascade to create them all?
Just ran into the same issue myself. My solution was to reference the nested entity by id. In your example this would be something like:
export class CreateMyEntityInputDto {
#IsString()
name: string;
...
#IsOptional()
myDocId: string;
#IsOptional()
otherDocIds: string[];
....
}
Rather than create nested entities in one mutation, this solution requires multiple.

AWS AppSync GraphQL input validation - ignore extra fields?

I have an input type in my schema that specifies lots of attributes, as it's intended to do. The issue is that what I'm sending to the mutation that will persist these objects is an object with arbitrary fields that may change. As it stands, if I send attributes not specified in the schema, I get the error:
Validation error of type WrongType: argument 'input' with value (...)
contains a field not in 'BotInput': 'ext_gps' # 'setBot'
Concretely, my input type did not specify the attribute exp_gps, and that field was provided.
My Question
Is there a way to make it so the input validation simply ignores any attributes not in the schema, so that it continues to perform the mutation with only whatever was specified in the schema? It'll be often that I don't want to persist the additional attributes, so dropping them is fine, as long as the other attributes get added.
GraphQL does not support arbitrary fields, there is a RFC to support a Map type but it has not been merged/approved into the specification.
I see two possible workarounds that both require to change your schema a little bit.
Say you have the following schema:
type Mutation {
saveBot(input: BotInput) : Boolean
}
input BotInput {
id: ID!
title: String
}
and the input object is:
{
"id": "123",
"title": "GoogleBot",
"unrelated": "field",
"ext_gps": "else"
}
Option 1: Pass the arbitrary fields as AWSJSON
You would change your schema to:
type Mutation {
saveBot(input: BotInput) : Boolean
}
input BotInput {
id: ID!
title: String
arbitraryFields: AWSJSON // this will contain all the arbitrary fields in a json string, provided your clients can pluck them from the original object, make a map out of them and json serialize it.
}
So the input in our example would be now:
{
"id": "123",
"title": "GoogleBot",
"arbitraryFields": "{\"unrelated\": \"field\", \"ext_gps\": \"else\"}"
}
In your resolver, you could take the arbitraryFields string, deserialize it, and hydrate the values on the BotInput object before passing it to the data source.
Option 2: Pass the input as AWSJSON
The principle is the same but you pass the entire BotInput as AWSJSON.
type Mutation {
saveBot(input: AWSJSON) : Boolean
}
You don't have to do the resolver hydration and you don't have to change your client, but you lose the GraphQL type validation as the whole BotInput is now a blob.

GraphQL Schema Type with no fields, or scalar type with arguments

I have the following situation, my data look like this:
Product
_id: ID
name
en_US: String
nl_NL: String
weight: Number
Now I'd like to make the following GraphQL query:
{
products {
_id
name(locale:"nl_NL")
}
}
This works when adding a resolver to the productType, which returns the right locale based on the given argument. I'd rather not repeat this type + resolver all the time everytime I use this locale structure in my database. So it seems logical to create a localeType, and pass the right argument (locale) to this type, to then resolve the locale there.
For this I'd imagine to need either a Schema Type with no fields (which is not possible afaik):
const localeType = new GraphQLObjectType({
name: 'Locale',
fields: {
resolve: (locale, args) => this[args.locale],
},
});
Or a Scalar Type which accepts arguments.
Are either of these possible in GraphQL currently? And what would the syntax be?
Or any other way to solve this issue?

Resources