GraphQL + SpringBoot: How to hide certain fields from request in response - spring-boot

I created a graphql schema
type Query {
vehicle(id: String): Vehicle
}
type Vehicle {
producer: String
color: String
engine: String
year: String
price: Float
}
and received a request
query
{
vehicle(id: "123") {
producer
color
price
}
}
But I wanted the response to contain producer and color only, so price is excluded.
Is it possible to do this with Graphql in Springboot? If yes, how can we do that?

Related

How to write resolver for my query with Spring GraphQL

I need help in writing resolver for my GraphQL query with Spring.
problem is that each of the objects you see here are being passed from different API and I need to combine them with defined dependency. Since Graphql automatically calls instance api for each customer, I am not sure how to write resolver which can filter customers based on certain instance name
Here is my schema query and schema.graphqls
customers(filter: {tenant: {instance: {instanceName: "ABC"}}}){
tenants{
tenantId
instance{
instanceName
}
}
}
}
schema
type Customer{
customerName: String
tenants: [Tenant]
}
type Tenant{
tenantId: ID
instance: Instance
}
type Instance{
instanceId: ID
instanceName: String
}
type Query{
customers(filter: CustomerFilterInput): [Customer]
}
input CustomerFilterInput{
customerName: String
tenant: TenantInput
}
input TenantInput{
tenantId: String
instance: InstanceInput
}
input InstanceInput{
instanceName: String
instanceId: String
}

What is a correct return type of a GraphQL resolve function?

I faced with an issue that can't resolve on my own. Let's go through it step by step to point out the problem.
I have a mutation bookAppointment which returns an Appointment object
GraphQL schema says that this object should return 4 properties: id, date, specialist, client.
To follow the GraphQL-style the specialist and client properties should be a field level resolvers
To fetch this objects I need pass specialistId to the specialist field level resolver, as well as clientId to the client field level resolver.
At this point a problem arises.
The field level resolvers of client, specialist expects that root mutation returns fields like clientId and specialistId. But GraphQL syntax and types that were generated by that syntax doesn't include this props (make sense).
How to "extend" the return type of the resolver and its interface BookAppointmentPayload to make me and TypeScript happy?
This is my GraphQL schema
type Client {
id: ID!
name: String!
}
type Specialist {
id: ID!
name: String!
}
type Appointment {
id: ID!
date: Date!
client: Client!
specialist: Specialist!
}
input BookAppointmentInput {
date: Date!
userId: ID!
specialistId: ID!
}
type BookAppointmentPayload {
appointment: Appointment!
}
type Mutation {
bookAppointment(input: BookAppointmentInput!): BookAppointmentPayload!
}
This is TypeScript representation of GraphQL schema
interface Client {
id: string
name: string
}
interface Specialist {
id: string
name: string
}
interface Appointment {
id: string
date: Date
client: Client
specialist: Specialist
}
interface BookAppointmentPayload {
appointment: Appointment
}
Here I define my resolvers objects
const resolvers = {
...
Mutation: {
bookAppointment: (parent, args, context, info): BookAppointmentPayload => {
return {
appointment: {
id: '1',
date: new Date(),
clientId: '1', // This prop doesn't exist in the TypeScript interface of Appointment, but is required for the field-level resolver of a `client` prop
specialistId: '1' // This prop doesn't exist int he TypeScript interface of Appointment, but is required for the field-level resolver of a `specialist` prop
}
}
}
},
Appointment: {
client: (parent, args, context, info) => {
// I need a clientId (e.g. args.clientId) to fetch the client object from the database
return {
id: '1',
name: 'Jhon'
}
},
specialist: (parent, args, context, info) => {
// I need a specialistId (e.g. args.specialistId) to fetch the specialist object from the database
return {
id: '1',
name: 'Jane'
}
}
}
}
Solution that come to my mind:
Create an interface which represent "actual" return type of the resolver
...
interface Apppointment {
id: string
date: Date
clientId: string // instead of `client: Client`
specialistId: string // instead of `specialist: Specialist`
}
interface BookAppointmentPayload {
appointment: Appointment
}
...
But this doesn't reflect the GraphQL type. Also tools like graphql-generator generates the type with actual objects that should be included in the response, not the fields that are going to be used by field-level resolvers. (Am I wrong?)
I would like to know how you're solving such issue?
I've been investigating this problem quite a lot and have come to the following conclusion.
Create an interface which represent "actual" return type of the resolver
Most of the time the return type of the resolver function (in JavaScript) doesn't match the type that was declared in the GraphQL SDL
For instance,
# GraphQL SDL
type Appointment {
id: String!
client: User!
specialist: Specialist!
}
type BookAppointmentInput { ... }
type BookAppointmentPayload {
appointment: Appointment!
}
type Mutation {
bookAppointment: (input: BookAppointmentInput!): BookAppointmentPayload!
}
interface AppointmentDatabaseEntity {
id: string
clientId: string // In GraphQL-world this prop is an object, but not in JS. Use this prop in field-level resolver to fetch entire object
specialistId: string // In GraphQL-world this prop is an object, but not in JS. Use this prop in field-level resolver to fetch entire object
}
interface BookAppointmentPayload {
appointment: AppointmentDatabaseEntity // The return type SHOULDN'T be equal to the GraphQL type (Appointment)
}
const resolvers = {
Mutatiuon: {
bookAppointment: (parent, args, context, info) => {
const appointment = { id: '1', specialistId: '1', clientId: '1' }
return {
id: appointment.id,
specialistId: appointment.specialistId, // Pass this prop to the child resolvers to fetch entire object
clientId: appointment.clientId // Pass this prop to the child resolvers to fetch entire object
}
}
},
Appointment: {
client: (parent: AppointmentDatabaseEntity, args, context, info) => {
const client = database.getClient(parent.clientId) // Fetching entire object by the property from the parent object
return {
id: client.id,
name: client.name,
email: client.email
}
},
specialist: (parent: AppointmentDatabaseEntity, args, context, info) => {
const specialist = database.getSpecialist(parent.specialistId) // Fetching entire object by the property from the parent object
return {
id: specialist.id,
name: specialist.name,
email: specialist.email
}
}
}
}
But this doesn't reflect the GraphQL type
As far as I understand it is okay
Also tools like graphql-generator generates the type with actual objects that should be included in the response, not the fields that are going to be used by field-level resolvers. (Am I wrong?)
Yes. I was wrong. The graphql-generator has a configuration file that can be used to replace default generated types with the types that you expect your resolvers to return. This option is called mappers.
plugins
config:
mappers:
User: ./my-models#UserDbObject # User is GraphQL object, which will be replaced with UserDbObject
Book: ./my-modelsBook # Same rule goes here
I don't want to go into details of how to configure it and use, but you can check the links that helped me to understand this
Documentation (check the mappers chapter)
Great explanation by
Jamie Barton (YouTube)
If you disagree with my conclusions or you have a better understanding of how to handle it feel free to leave a comment

GraphQL: Nesting Reusable Interface Fragment within a Type Fragment

I'm new to GraphQL and Apollo Client.
I'm trying to write DRY queries as much possible and found that Fragments can be of great help.
Here is how my GraphQL schema would look like:
interface header { # Implemented by multiple types
uuid: ID!
created_at: DateTime
created_by: String
updated_at: DateTime
updated_by: String
}
type account implements header #withSubscription {
id: String! #id #search(by:[term])
name: String #search(by:[fulltext])
description: String #search(by:[fulltext])
...
}
on the client side, I'm trying to setup my operations.graphql as follows:
fragment headerData on header {
uuid
created_at
created_by
updated_at
updated_by
}
fragment accountNode on account {
...headerData
id
name
description
#.. rest of the fields
}
query getPaginatedAccounts(first: Int, offset: Int) {
queryaccount {
...accountNode
# .. rest of the fields
}
}
However, when I execute the query, the results set doesn't fetch any data from the accountNode fragment.
Is this correct way to nest the fragments?
I realize that if I change the accountNode fragment as follows, it works and gets me the data:
fragment accountNode on account {
... on header {
uuid
created_at
#..rest of header fields
}
id
name
description
#.. rest of the fields
}
But since header interface is implemented by multiple types, its not DRY to repeat those fields on every Type Fragment.
Thanks in advance.

Apollo graphql queries with type condition on nested fields

I am working on the following types, where the "content" of a "Comment" is a union type:
type TextContent {
text: String
}
type RichContent {
participants: [String]
startTime: String
}
union Content = TextContent | RichContent
type Comment {
id: ID
sender: String
content: Content
}
type Review {
id: ID
title: String
lastComment: Comment
}
In my Apollo query, I was trying to use conditional fragments on the 2 Content types:
query listOfReviews {
reviews {
...reviewFields
}
}
fragment reviewFields on Review {
id
title
lastComment {
content {
... on TextContent {
text
}
... on RichContent {
participants
startTime
}
}
}
}
I received a runtime error where Apollo seems trying to access "participants" field of "undefined", where the actual content object is:
{
__typename: "TextContent:,
text: "abc"
}
It looks the two types of the union Content are merged together.
My question is: is it allowed to use type conditions on nested fields in Apollo queries? Or type conditions have to be used on the top level types returned by the queries? If it's allowed, how should I fix my types/queries?
Thanks a lot!
#const86 helped point out that this is due to this bug: https://github.com/apollographql/apollo-link-state/pull/258.

Validation of properties in GraphQL

input MessageInput {
content: String
author: String
}
type Message {
id: ID!
content: String
author: String
}
type Query {
getMessage(id: ID!): Message
}
type Mutation {
createMessage(input: MessageInput): Message
}
Message content can be max length 255. How to document that max length is 255 characters? How/Where to do this validation?
On the server-side, you would validate in the resolver for the createMessage mutation.
I would also recommend having some sort of client-side validation as well!

Resources