Understanding React-Relay Connections in the Context of Type-GraphQL - graphql

The below code excerpt comes from the React-Relay docs on Rendering Connections. I am wondering if someone could provide me with an example of what the underlying schema definition (using `type-graphql for annotations/decorations) would look like.
const {graphql} = require('RelayModern');
const userFragment = graphql`
fragment UserFragment on User {
name
friends(after: $cursor, first: $count)
#connection(key: "UserFragment_friends") {
edges {
node {
...FriendComponent
}
}
}
}
`;
Would it look something like the following? With attention paid to the UserType type definition, and especial attention to the friends field. I am also hoping if anyone could turn my attention to a more elaborated upon example/boilerplate to help me understand what is compliant with the Relay specification. Some examples I am after:
How to type the return type of a Query if I intend one of the Query's resolved fields to be a Connection type? And what would this look when written as a fragment.
How to type the same scenario as above, except now the return type is an iterable of the original return type?
#ObjectType({ implements: [Node] })
export class UserType extends Node {
#Field()
name: string
#Field()
friends: UserConnection
}
const User = createUnionType({
name: 'User',
types: () => [UserType] as const,
resolveType: value => {
// if ('code' in value) {
// return Error
// }
return UserType
}
})
#ObjectType()
export class UserEdge extends EdgeType('report', User) {}
#ObjectType()
export class UserConnection extends ConnectionType<UserEdge>(
'user',
UserEdge
) {
}

Related

GraphQL combining two Resolvers

I currently have two resolvers, Authors and Books, that return data from two separate API's. In most scenarios I only need to call one or the other, however, in this scenario, I need to attach the books to the author.
It's not clear to me how I should do this.
Option 1 - Simple to do
I call the book API in the Author resolver and combine them here. This means I'd potentially make unnecessary calls to the Book API. It also means if the book API changes, I'd have to make updates to both the author and book resolvers instead of just updating the Book resolver.
Option 2 - Resolver
Is there a way to call the Book resolver from within the Author resolver?
Options 3 - Client
Is there a way to stitch the author and book together from within the client query?
I’m new to graphql and type-graphql so apologies if this is obvious.
Author
const author = {
name: 'James',
bookIds: [1, 2]
};
Book
const book = {
id: 1,
title: 'Book 1'
};
Desired outcome
const author = {
name: 'James',
books: [{
id: 1,
title: 'Book 1'
},
{
id: 2,
title: 'Book 2'
}]
}
Resolvers
#Service()
#Resolver(() => Author)
export class AuthorResolver {
constructor(private readonly authorService: authorService) { }
#Query(() => Author)
async author(
#Arg('authorId', () => ID, { nullable: false }) authorId: string,
#Ctx() { dataSources }: ResolverContext
): Promise<Author | undefined> {
const { authorService } = dataSources;
const author = await this.author.getAuthor(authorService, authorId);
return {
id: author.id,
name: author.name,
bookIds: author.bookIds
};
}
}
#Service()
#Resolver(() => Book)
export class BookResolver {
constructor(private readonly bookService: bookService) { }
#Query(() => Book)
async book(
#Arg('bookId', () => ID, { nullable: false }) bookId: string,
#Ctx() { dataSources }: ResolverContext
): Promise<Book | undefined> {
const { bookService } = dataSources;
const book = await this.book.getBook(bookService, bookId);
return {
id: book.id,
title: book.title
};
}
}
Client Side Query
query BookQuery($bookId: ID!) {
book(bookId: $bookId) {
id
title
}
}
query authorQuery($authorId: ID!) {
book(authorId: $authorId) {
id
name
books
}
}
You must implement a FieldResolver, called books, in the Author resolver.
If an API changes in the future, it will not affect the resolver since you use a service that talks to the API and acts as a middleware layer.
The service must be well implemented (abstraction) and the returned entity must be matched/mapped correctly to a GraphQL object type. i.e. there is no need to return {id: author.id, ...} inside a resolver since it's done automatically by the service and class mappings. 
Moreover, you inject a service instance inside the resolver, so there is no need to use #Ctx and obtain the same service instance: simply use this.[SERVICE_NAME].[METHOD].
Keep you context as simple as possible (e.g. authenticated user id obtained by a JWT).
The final Author resolver is much cleaner and more portable:
#Resolver(() => Author)
#Service()
export class AuthorResolver {
#Inject()
private readonly authorService!: AuthorService;
#Inject()
private readonly bookService!: BookService;
// 'nullable: false' is default behaviour
// No need for '#Ctx' here
// Returned value is inferred from service -> 'Promise<Author | undefined>'
#Query(() => Author)
async author(#Arg('id', () => ID) id: string) {
return this.authorService.findOne(id);
}
#FieldResolver(() => [Book])
async books(#Root() author: Author) {
return this.bookService.findManyByAuthorId(author.id);
// OR 'return this.bookService.findMany(author.bookIds);'
}
}
If you want an example project, see this.

Resolve field returns null for all fields except ID (extended type)

I have original GQL type from intermediaries microservice.
#ObjectType('Intermediary')
#Directive('#key(fields: "id")')
export class IntermediaryType implements Intermediary {
#Field(() => ID)
id: string
#Field()
beneficiaryName: string
// and other fields
}
And another GQL type that extends from external GQL type.
#ObjectType('Intermediary')
#Directive('#extends')
#Directive('#key(fields: "id")')
export class IntermediaryType {
#Field(() => ID)
#Directive('#external')
id: string
}
GQL type that nests external GQL type:
#ObjectType('FXPayment')
export class FXPaymentType {
#Field(() => ID)
id: string
#Field(() => [IntermediaryType])
intermediaries?: IntermediaryType[]
// and other fields
}
And I have a resolver for my mutation and intermediaries field.
#Resolver(() => FXPaymentType)
export class FXPaymentsResolver {
constructor(private _fxPaymentsService: FXPaymentsService) {}
#Mutation(() => FXPaymentType)
async createFXPayment(
#Args('input') input: CreateFXPaymentInput,
): Promise<FXPaymentType> {
const createdFXPayment = await this._fxPaymentsService.createFXPayment(input)
return createdFXPayment
}
#ResolveField()
intermediaries(#Parent() fxPayment: FXPaymentEntity): IntermediaryType[] {
const usedIntermediaries = [
fxPayment.nostroRequisites,
fxPayment.providerRequisites,
fxPayment.orderingRequisites,
]
return usedIntermediaries
}
}
The problem is that when mutation executed -- nested IntermediaryType contains only id field whereas other fields are nullable. I logged data in #ResolveField() and it showed me that all fields are not in null. I tried to remove #ResolveField and allow high-level resolver to resolve intermediaries field automatically (that is I just created intermediaries in my mutation) -- it doesn't work.
But still, I do really don't know what I'm doing wrong... Any attempts to forcely assign value to these fields failed also. Could you give any helpful comments on that? What should I try to do? Is everything okay with my code or not? Any ideas? Please, help.

NestJS GraphQL federation circular resolvers

I am working on an existing GraphQL service, that I have successfully broken down to smaller services, using apollo federation. I have some types being extended by other services and everything works just fine. However, as I followed this example: https://docs.nestjs.com/graphql/federation now I have a circular reference kind of problem.
So basically I have, for example two types:
#ObjectType()
#Directive('#key(fields: "id")')
export class Original {
#Field(type => ID)
id: string;
...
}
// extending it in the other service
#ObjectType()
#Directive('#extends')
#Directive('#key(fields: "id")')
export class Original {
#Field(type => ID)
#Directive('#external')
id: string;
#Field(type => [Other])
#Directive('#requires(fields: "id")')
others: Other[];
...
}
#ObjectType()
#Directive('#key(fields: "id")')
export class Other {
#Field(type => ID)
id: string;
...
#Field(type => Original, { nullable: true })
original?: Original;
}
And I have two resolvers, both in the service extending the original type:
#Resolver(of => Original)
export class OriginalResolver {
...
#ResolveField(returns => [Other])
async others(#Parent() original: Original) {
const { id} = original;
...
}
}
#Resolver(of => Other)
export class OtherResolver {
...
#ResolveField((of) => Original)
async original(#Parent() other: Other) {
return { __typename: 'Orignal', id: other.original.id };
}
}
As the resolvers suggest, I can have a query with something like this:
...,
original{
others{
original{
*and so on...*
}
}
}
I don't want this circular query to be possible and I am trying to remove it, but so far I had no luck. If I simply remove the "original" field resolver, where it should return the __typename, apollo just won't extend the original type anymore. I guess that line is what basically connects the two services to find the original type, but I am not that deep in apollo so far...
So my question is how could I remove that resolver all together OR if that just has to be there for apollo to work, is there any way to "hide it"?
Thanks in advance and feel free to ask for any more info you might need.
It's fully legal to have 'loops' in GraphQL (notice 'graph'). GraphQL 'by design' gives the ability to freely shape the query [and structure of the response] including 'loops' creation.
I wouldn't say it's 'circular reference kind of problem'. It can be an efficiency/performance problem ... **This is not an evil ... when not abused.
You can use some metrics to limit API usage, restrict 'max resolving depth level'/etc.
In this case, you can simply return null in original resolver when parent is others type. This way original can be queried only on query top/root level.

TypeGraphQL createUnionFunction with parameter

I'm trying to implement an extension of typegraphql's createUnionType() function to where I can pass in a class/ObjectType instead of hardcoding, and it will return a union type of both.
What I have so far doesn't work but I feel like it's possible. Could anyone provide any insight? Maybe it's not possible?
typedefs
import { ObjectType, Field, createUnionType, ClassType } from "type-graphql";
#ObjectType()
export default class MutationSuccess {
#Field()
success: boolean = true;
}
// Doesn't work
export const MutationResponse = (objectType: ClassType) => createUnionType({
name: 'MutationResponseType',
types: () => [MutationSuccess, objectType],
})
How I'm trying to use it in my resolver
#Resolver()
export default class RelationResolver {
#Mutation(() => MutationResponse(Relation), { description: 'follow user' })
async follow(
#Arg('relationInput') relationInput: RelationInput
): Promise<Relation | MutationSuccess> {
// do some stuff
}
}
error
UnhandledPromiseRejectionWarning: Error: Cannot determine GraphQL output type for follow
The Relation class need to be decorated with #ObjectType and the union type name has to be unique.

Defining JSON/Object type in graphql-tag

I'm kinda new to Apollo gql, just wondering if anyone know if its possible to define Object class in graphql-tag?
export const CREATE_STYLE = gql`
mutation styleCreate(
$formID: String!
$fontFamily: Object //how do you define object/JSON object?
) {
styleCreate(
formID: $formID
fontFamily: $fontFamily
) {
styleID
}
}
`;
First, if the input type is an object I would recommend to define that on the server as a input type.
In my setup I'm using:
export const createUser = gql`
mutation createUser($user: UserCreate) {
create(input: $user) {
name
email
}
}
where "UserCreate" is an interface that looks like:
export interface UserCreate {
// The user name.
name: string,
// The user email address.
email: string,
};
You can manually create the interface, but I would suggest to use apollo codegen that gives you all the input types you need.

Resources