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

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

Related

Apollo Graphql Subscriptions: Different subscription resolvers for create, update & delete or a single resolver?

I am using Typegrpahql for creating my graphql server with express. I want to clarify whether I should create separate subscription resolvers for create, update and delete operations of an entity? Or should I create one resolver since the return type of the data will always be the same?
Suppose I have an entity like this
type Board {
createdAt: DateTime!
id: ID!
members: [User!]!
name: String!
owner: User!
stacks: [Stack!]!
updatedAt: DateTime
}
And I have a mutation resolver class like this
#Resolver(Board)
export class BoardMuations {
#Authorized()
#Mutation(() => Board)
async createBoard(
#PubSub() pubSub: PubSubEngine,
#Arg("name") name: string,
#Ctx() { uid }: MyContext
): Promise<Board> {
// perform create opration
}
#Authorized()
#Mutation(() => Board)
async updateBoard(
#PubSub() pubSub: PubSubEngine,
#Arg("boardId") boardId: string,
#Arg("name") name: string
): Promise<Board | undefined> {
// perform update operation
}
#Authorized()
#Mutation(() => DeleteResponse)
async deleteBoard(#PubSub() pubSub: PubSubEngine, #Arg("boardId") boardId: string): Promise<DeleteResponse> {
//perform delete operation
}
}
So should I create a subscription resolver like this
#Subscription({ topics: "CUD_BOARD" })
newBoard(#Root() data: Board): Board {
return data;
}
Or should I create different resolvers for every operation? what is this topics array?
I could also provide different topic strings as an array as I understand. What difference would it make to create different topics strings and publish the event using them?
I am currently publishing the event like this await pubSub.publish("CUD_BOARD", board);

how can I fetch data from graphql in my resolver

Within my resolver I seem to be unable to fetch connected data
this works in the graphql playground (prisma) but I am unsure of the syntax about how to form a resolver in apollo server
// my typedef for activity is
type Activity {
id: ID! #id
ActivityType: ActivityType!
title: String!
date: DateTime
user: User!
distance: Float!
distance_unit: Unit!
duration: Float!
elevation: Float
elevation_unit: Unit
createdAt: DateTime! #createdAt
updatedAt: DateTime! #updatedAt
// and my resolver currently looks like this
async activity(parent, args, ctx, info) {
const foundActivity = await ctx.db.query.activity({
where: {
id: args.id
}
});
// todo fetch user data from query
console.log(foundActivity);
}
// where db has been placed onto the ctx (context)
// the CL gives all of the data for Activity apart from the user
// in the playground I would do something like this
query activity {
activity(where: {
id: "cjxxow7c8si4o0b5314f9ibek"
}){
title
user {
id
name
}
}
}
// but I do not know how to specify what is returned in my resolver.
console.log(foundActivity) gives:
{ id: 'cjxxpuh1bsq750b53psd2c77d',
ActivityType: 'CYCLING',
title: 'Test Activity',
date: '2019-07-10T20:21:27.681Z',
distance: 13.4,
distance_unit: 'KM',
duration: 90030,
elevation: 930,
elevation_unit: 'METERS',
createdAt: '2019-07-10T20:48:50.879Z',
updatedAt: '2019-07-10T20:48:50.879Z' }
Prisma is the DB ORM and then I have an Apollo-Server 2 server running on top of that. Unfortunately, stack overflow also thinks that there is too much code on this post so I will have to waffle on about inconsequential gibberish due to the fact that their system can't handle it.
You will have to implement a resolver for Activity.user. Unfortunately your entity does not seem to contain a reference to the user. First, add the user connection to your Prisma data model. Then implement a resolver for Activity.user. I am not very familiar with Prisma 1 but this naive implementation should already do what you want:
let resolvers = {
Query: {
// ...
},
Activity: {
user(parent, args, ctx) {
return ctx.db.query.activity({ id: parent.id }).user();
}
}
}
Find out more about resolving relations in Prisma here
So the answer was incredibly simple:
I just add a second argument to the query (after the "where" with a gql tag of the data shape to be returned so my code now looks like:
const foundActivity = await ctx.db.query.activity(
{
where: {
id: args.id
}
},
`{id title user { id name }}`
);

How to add multiple resolvers in a type (Apollo-server)

I have used express-graphql and there i used to do something like this.
const SubCategoryType = new ObjectType({
name: 'SubCategory',
fields: () => ({
id: { type: IDType },
name: { type: StringType },
category: {
type: CategoryType,
resolve: parentValue => getCategoryBySubCategory(parentValue.id)
},
products: {
type: List(ProductType),
resolve: parentValue => getProductsBySubCategory(parentValue.id)
}
})
});
Here I have multiple resolvers, id and name are fetched directly from the result. and the category and products have there own database operation. and so on.
Now I am working on apollo-server and I can't find a way to replicate this.
for example I have a type
type Test {
something: String
yo: String
comment: Comment
}
type Comment {
text: String
createdAt: String
author: User
}
and in my resolver I want to split it up, for example something like this
text: {
something: 'value',
yo: 'value',
comment: getComments();
}
NOTE: this is just a representation of what I need.
You can add type-specific resolvers to handle specific fields. Let's say you have the following schema (based on your example):
type Query {
getTest: Test
}
type Test {
id: Int!
something: String
yo: String
comment: Comment
}
type Comment {
id: Int!
text: String
createdAt: String
author: User
}
type User {
id: Int!
name: String
email: String
}
Let's also assume you have the following DB methods:
getTest() returns an object with fields something, yo and
commentId
getComment(id) returns an object with fields id, text, createdAt and userId
getUser(id) returns an object with fields id, name and email
Your resolver will be something like the following:
const resolver = {
// root Query resolver
Query: {
getTest: (root, args, ctx, info) => getTest()
},
// Test resolver
Test: {
// resolves field 'comment' on Test
// the 'parent' arg contains the result from the parent resolver (here, getTest on root)
comment: (parent, args, ctx, info) => getComment(parent.commentId)
},
// Comment resolver
Comment: {
// resolves field 'author' on Comment
// the 'parent' arg contains the result from the parent resolver (here, comment on Test)
author: (parent, args, ctx, info) => getUser(parent.userId)
},
}
Hope this helps.

Skipping over a resolver for a query [duplicate]

I think I'm missing something obvious in the way GraphQL resolvers work. This is a simplified example of my schema (a Place that can have AdditionalInformation):
import { ApolloServer, gql } from 'apollo-server';
const typeDefs = gql`
type Place {
name: String!
additionalInformation: AdditionalInformation
}
type AdditionalInformation {
foo: String
}
type Query {
places: [Place]
}
`;
And the associated resolvers:
const resolvers = {
Query: {
places: () => {
return [{name: 'Barcelona'}];
}
},
AdditionalInformation: {
foo: () => 'bar'
}
};
const server = new ApolloServer({typeDefs, resolvers});
server.listen().then(({ url }) => {
console.log(`API server ready at ${url}`);
});
When I execute a basic query:
{
places {
name,
additionalInformation {
foo
}
}
}
I always get null as the additionalInformation:
{
"data": {
"places": [
{
"name": "Barcelona",
"additionalInformation": null
}
]
}
}
It's my first GraphQL app, and I still don't get why the AdditionalInformation resolver is not automatically executed. Is there some way to let GraphQL know it has to fire it?
I've found this workaround but I find it a bit tricky:
Place: {
additionalInformation: () => { return {}; }
}}
Let's assume for a moment that additionalInformation was a Scalar, and not an Object type:
type Place {
name: String!
additionalInformation: String
}
The value returned by the places resolver is:
[{name: 'Barcelona'}]
If you were to make a similar query...
query {
places {
name
additionalInformation
}
}
What would you expect additionalInformation to be? It's value will be null because there is no additionalInformation property on the Place object returned by the places resolver.
Even if we make additionalInformation an Object type (like AdditionalInformation), the result is the same -- the additionalInformation field will resolve to null. That's because the default resolver (the one used when you don't specify a resolver function for a field) simply looks for a property with the same name as the field on the parent object. If it fails to find that property, it returns null.
You may have specified a resolver for a field on AdditionalInformation (foo), but this resolver is never fired because there's no need -- the whole additionalInformation field is null so all of the resolvers for any fields of the associated type are skipped.
To understand why this is a desirable behavior, imagine a different schema:
type Article {
title: String!
content: String!
image: Image
}
type Image {
url: String!
copyright: String!
}
type Query {
articles: [Article!]!
}
We have a database with an articles table and an images table as our data layer. An article may or may not have an image associated with it. My resolvers might look like this:
const resolvers = {
Query: {
articles: () => db.getArticlesWithImages()
}
Image: {
copyright: (image) => `©${image.year} ${image.author}`
}
}
Let's say our call getArticlesWithImages resolves to a single article with no image:
[{ title: 'Foo', content: 'All about foos' }]
As a consumer of the API, I request:
query {
articles {
title
content
image
}
}
The image field is optional. If I get back an article object with a null image field, I understand there was no associated image in the db. As a front end client, I know not to render any image.
What would happen if GraphQL returned a value for the image regardless? Obviously, our resolver would break, since it would not be passed any kind of parent value. Moreover, however, as a consumer of the API, I would have to now parse the contents of image and somehow determine whether an image was in fact associated with the article and I should do something with it.
TLDR;
As you already suggested, the solution here is to specify a resolver for additionalInfo. You can also simply return that value in your places resolver, i.e.:
return [{name: 'Barcelona', additionalInfo: {}}]
In reality, if the shape of your schema aligns with the shape of your underlying data layer, it's unlikely you'll encounter this sort of issue when working with real data.

Enumerating all fields from a GraphQL query

Given a GraphQL schema and resolvers for Apollo Server, and a GraphQL query, is there a way to create a collection of all requested fields (in an Object or a Map) in the resolver function?
For a simple query, it's easy to recreate this collection from the info argument of the resolver.
Given a schema:
type User {
id: Int!
username: String!
roles: [Role!]!
}
type Role {
id: Int!
name: String!
description: String
}
schema {
query: Query
}
type Query {
getUser(id: Int!): User!
}
and a resolver:
Query: {
getUser: (root, args, context, info) => {
console.log(infoParser(info))
return db.Users.findOne({ id: args.id })
}
}
with a simple recursive infoParser function like this:
function infoParser (info) {
const fields = {}
info.fieldNodes.forEach(node => {
parseSelectionSet(node.selectionSet.selections, fields)
})
return fields
}
function parseSelectionSet (selections, fields) {
selections.forEach(selection => {
const name = selection.name.value
fields[name] = selection.selectionSet
? parseSelectionSet(selection.selectionSet.selections, {})
: true
})
return fields
}
The following query results in this log:
{
getUser(id: 1) {
id
username
roles {
name
}
}
}
=> { id: true, username: true, roles: { name: true } }
Things get pretty ugly pretty soon, for example when you use fragments in the query:
fragment UserInfo on User {
id
username
roles {
name
}
}
{
getUser(id: 1) {
...UserInfo
username
roles {
description
}
}
}
GraphQL engine correctly ignores duplicates, (deeply) merges etc. queried fields on execution, but it is not reflected in the info argument. When you add unions and inline fragments it just gets hairier.
Is there a way to construct a collection of all fields requested in a query, taking in account advanced querying capabilities of GraphQL?
Info about the info argument can be found on the Apollo docs site and in the graphql-js Github repo.
I know it has been a while but in case anyone ends up here, there is an npm package called graphql-list-fields by Jake Pusareti that does this. It handles fragments and skip and include directives.
you can also check the code here.

Resources