npm add nexus-result-field
Peer dependencies: nexus
, graphql
- Implementation of GraphQL result fields schema patterns
- Type safety powered by Nexus
- API mimicks Nexus' core
mutationField
/queryField
API making this easy to pickup. - Accept plain args or input objects.
- Return single or multi errors.
nexus-result-field
makes it easy to encode query and mutation operation errors in your schema with Nexus.
Here are some pre-requisite readings that will probably help you understand this library:
- Marc Andre's GraphQL Errors guide, a thorough but succinct introduction into the problem space.
- Nexus tutorial, a primer on what Nexus is all about if you don't already know.
But if you just want a quick elevator pitch here it is:
If you are at all familiar with functional programming then you might have heard of Either and Optional/Maybe types. The idea here is similar: when a caller (the GraphQL API client) executes an operation (sends queries or mutations), instead of either returning or throwing an error (the GraphQL API equivilant of throwing is returning ad-hoc untyped errors in the JSON envelope) you encode the error case into the type system, treating it as data.
This approach to errors benefits your API clients by letting them leverage the rich GraphQL type system for not just the happy path but also the unhappy path. This makes a lot of sense for many real-world applications these two things are true:
- You chose GraphQL in part because of its type system.
- Your API has many IO interactions with the outside world (databases, other APIs, ...).
- You do/want to handle errors seriously and gracefully.
The guide below will tour the main API features. For detailed reference, refer to the JSDoc.
The following guide works with resultMutationField
but not there is resultQueryField
which works the same way but for Query
type instead of Mutation
type.
import { makeSchema, objectType } from 'nexus'
import { printSchema } from 'graphql'
import { resultMutationField } from 'nexus-result-field'
printSchema(
makeSchema({
types: [
resultMutationField({
name: 'createFoo',
input(t) {
t.nonNull.string('handle')
},
errorTypes: ['HandleAlreadyTaken'],
successType: `Foo`,
resolve(_, args) {
// ...
return {
__typename: `Foo`,
id: 'abc',
handle: args.input.handle,
}
},
}),
objectType({
name: 'Foo',
definition(t) {
t.nonNull.id('id')
t.nonNull.string('handle')
},
}),
objectType({
name: 'HandleAlreadyTaken',
definition(t) {
t.string('message')
},
}),
],
features: {
abstractTypeStrategies: {
__typename: true,
},
},
})
)
type Foo {
id: ID!
handle: String!
}
type HandleAlreadyTaken {
message: String
}
union CreateFooResult = Foo | HandleAlreadyTaken
input CreateFooInput {
handle: String!
}
type Query {
ok: Boolean!
}
type Mutation {
createFoo(input: CreateFooInput!): CreateFooResult
}
import { makeSchema, objectType } from 'nexus'
import { printSchema } from 'grpahql'
import { resultMutationField } from 'nexus-result-field'
printSchema(
makeSchema({
types: [
resultMutationField({
name: 'createFoo',
args: {
handle: nonNull('String'),
},
errorTypes: ['HandleAlreadyTaken'],
successType: `Foo`,
resolve(_, args) {
// ...
},
}),
objectType({
name: 'Foo',
definition(t) {
t.nonNull.id('id')
t.nonNull.string('handle')
},
}),
objectType({
name: 'HandleAlreadyTaken',
definition(t) {
t.nonNull.string('message')
},
}),
],
features: {
abstractTypeStrategies: {
__typename: true,
},
},
})
)
union CreateFooResult = Foo | HandleAlreadyTaken
type Foo {
id: ID!
handle: String!
}
type HandleAlreadyTaken {
message: String!
}
type Query {
ok: Boolean!
}
type Mutation {
createFoo(handle: String!): CreateFooResult
}
import { makeSchema, objectType } from 'nexus'
import { printSchema } from 'grpahql'
import { resultMutationField } from 'nexus-result-field'
printSchema(
makeSchema({
types: [
resultMutationField({
name: 'createFoo',
input(t) {
t.nonNull.string('handle')
},
aggregateErrors: true, // <-- this
errorTypes: ['HandleAlreadyTaken'],
successType: `Foo`,
resolve(_, args) {
// ...
},
}),
objectType({
name: 'Foo',
definition(t) {
t.nonNull.id('id')
t.nonNull.string('handle')
},
}),
objectType({
name: 'HandleAlreadyTaken',
definition(t) {
t.nonNull.string('message')
},
}),
],
features: {
abstractTypeStrategies: {
__typename: true,
},
},
})
)
union CreateFooResult = Foo | CreateFooErrors
type CreateFooErrors {
errors: [CreateFooError!]!
}
union CreateFooError = HandleAlreadyTaken
input CreateFooInput {
handle: String!
}
type Foo {
id: ID!
handle: String!
}
type HandleAlreadyTaken {
message: String!
}
type Query {
ok: Boolean!
}
type Mutation {
createFoo(input: CreateFooInput!): CreateFooResult
}
import { makeSchema, objectType } from 'nexus'
import { printSchema } from 'grpahql'
import { resultMutationField } from 'nexus-result-field'
printSchema(
makeSchema({
types: [
resultMutationField({
name: 'createFoo',
input(t) {
t.nonNull.string('handle')
},
errorTypes: ['HandleAlreadyTaken'],
successType: `Foo`,
resolve(_, args) {
// ...
return {
__typename: `Foo`,
id: 'abc',
handle: args.input.handle,
}
},
}),
objectType({
name: 'Foo',
definition(t) {
t.nonNull.id('id')
t.nonNull.string('handle')
},
}),
objectType({
name: 'HandleAlreadyTaken',
definition(t) {
t.string('message')
},
}),
],
features: {
abstractTypeStrategies: {
__typename: true,
},
},
})
)
type Foo {
id: ID!
handle: String!
}
type HandleAlreadyTaken {
message: String
}
union CreateFooResult = Foo | HandleAlreadyTaken
input CreateFooInput {
handle: String!
}
type Query {
ok: Boolean!
}
type Mutation {
createFoo(input: CreateFooInput!): CreateFooResult
}