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

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?

Related

graphql query with certain combination of parameters

I am looking to specify a certain required combination of parameters in a graphQL query.
The query should be valid either without any params and return all cats or filter by size AND species.
extend type Query {
cats(size: String, species: String): [Cat]
}
Is the only way to do this via the resolver (throw error if one arg is passed) or is there a neater way?
I don't believe that this is defined in the spec. You could define a new input type and then use this though.
input CatFilter {
size: String!
species: String!
}
extend type Query {
cats(filter: CatFilter): [Cat]
}
That way the parameter is optional, but if given, both properties are required.

Passing variables in GraphQL

I'm trying to run a GraphQL query in the AWS AppSync console:
query MyQuery {
getUserInfoById(id: "1234566789") {
account {
id // need this value for getAvailableCourses
}
}
getAvailableCourses(accountId: "", pageNumber: 0) {
data {
id
name
type
}
}
}
Basically I need the value account.id in getUserInfoById for getAvailableCourses. I'm obviously new to GraphQL. How would I go about this?
To the best of my knowledge, there can be two ways you can do this.
You can handle this in your frontend by getting user's id
from the session info and pass it to the other query.
You can also merge these two queries and make it one. You will also have to change the respective fields. Then attach a resolver with AvailableCourses and use $ctx.source.id in the resolver to get further details. Schema would look something like this
type Account {
id : ID!
availableCourses: AvailableCourses
..
}
type AvailableCourses {
name: String!
type: String!
..
}
type Query {
getUserInfoById(id: ID!): Account
}
Using the returned fields as inputs for a second query into your datasource is precisely what field resolvers are for. I can't say for sure since I don't know your schema or access patterns but it looks like you need to make available courses a sub field of the user.

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

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.

How to use graphql type in other files for relationship without duplication

I am trying to separate all the tables in different directory. However, I am encountering some problems. Look at the ExpenseItem below. ExpenseType is under ExpenseType directory so graphql throws error. In order to solve that I need to add ExpenseType in ExpeseItem/typeDefs which is redundant.
ExpenseItem/typeDefs
type ExpenseItem {
id: ID!
name: String,
updatedAt: DateTime,
expenseType: [ExpenseType]
}
Is there any better way to stitch the schema?
I also tried something like this. However, graphql stop working.
When I query, it does not goes into the resolver, is there anything wrong on setup?
const schemas = makeExecutableSchema({
typeDefs: `
scalar JSON
scalar DateTime
type Query {
test: String
}
${typeDefs1}
${typeDefs2}
${typeDefs3}
${typeDefs4}
`,
resolvers: {
// Type resolvers
JSON: GraphQLJSON,
DateTime: GraphQLDateTime,
// schema resolvers
...resolvers1,
...resolvers2,
...resolvers3,
...resolvers4
}
})
const server = new ApolloServer({
schema,
context: { db }
})
You cannot merge your resolvers like this. Using the spread operator to merge an object results in a shallow merge. If the merged objects share any common properties (for example, Query), only the last merged property will be present in the resulting object. You need to deep merge the objects. You can do so yourself, or use something like lodash's merge.
Additionally, you can pass an array of strings as the typeDefs to makeExecutableSchema instead of combining them yourself.

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.

Resources