Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement mutations with payload, input types and error as data handling #6

Merged
merged 26 commits into from
Jan 26, 2022

Conversation

LauraBeatris
Copy link
Owner

@LauraBeatris LauraBeatris commented Jan 26, 2022

Resources

Summary

Introduce mutations with conventions such as payload, input types, and error as handling.
The reasoning for following with those conventions can be found in the resource links above but here's a brief explanation:

Payload types

Schema implementation
@ObjectType()
export class CreateUserSuccess {
  @Field(() => User)
    user: User
}
@ObjectType()
export class EmailTakenError implements Partial<UserError> {
  @Field(() => ErrorCode)
    code: ErrorCode

  @Field()
    message: string

  @Field()
    emailWasTaken: boolean
}
@ObjectType()
export class UserNameTakenError implements Partial<UserError> {
  @Field(() => ErrorCode)
    code: ErrorCode

  @Field()
    message: string

  @Field()
    suggestedUsername: string
}
export const CreateUserPayload = createUnionType({
  name: 'CreateUserPayload',
  types: () => [CreateUserSuccess, EmailTakenError, UserNameTakenError] as const
})

Payload types are used with the purpose of being used as the result of a particular mutation.
It enables to evolve the mutation over time without having to directly change a single return type. When scaling an API, mutations often require to return more than just the thing that has been mutated.

Input types

Schema implementation
@InputType()
export class CreateUserInput implements Partial<User> {
  @Field()
    name: string

  @Field()
    email: string
}

Having a unique input type per mutation is a convention originally introduced by Relay.
The benefits are the following:
• The input is much more evolvable.
• The mutation can use a single variable on the client-side, which does make it a bit easier to use especially when you start having a lot of arguments.

Errors as data

For user-facing errors that are part of our business/domain rules, the current best practice is to look at designing these errors as part of our schema rather than treating them as exceptions/query level errors. Let’s take a look at some ways of achieving this.

The approach implemented in this PR uses union
types to represent possible problematic states to the client.
The benefits are the following:

  • The union type well describes what could happen during the execution of the mutation.
  • Each case is strongly typed using the GraphQL type system. This allows us to add custom fields to each error scenario.
export const CreateUserPayload = createUnionType({
  name: 'CreateUserPayload',
  types: () => [CreateUserSuccess, EmailTakenError, UserNameTakenError] as const
})

@LauraBeatris LauraBeatris merged commit d275165 into main Jan 26, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant