Using Error Union Pattern
1. Understanding Error Unions
| Aspect | Detail |
|---|---|
| Idea | Model expected errors as part of the schema, not exceptions |
| Benefit | Type-safe handling, exhaustive switch, better UX |
| When | User-recoverable errors (validation, conflicts) |
2. Creating Success and Error Types
Example: Result union
type RegisterSuccess { user: User!, token: String! }
type EmailAlreadyTaken implements UserError { message: String!, field: String! }
type WeakPassword implements UserError { message: String!, requirements: [String!]! }
union RegisterResult = RegisterSuccess | EmailAlreadyTaken | WeakPassword
3. Returning Typed Errors
Example: Resolver returning union
register: async (_, { input }, { db }) => {
if (await db.user.findUnique({ where: { email: input.email } })) {
return { __typename: "EmailAlreadyTaken", message: "Email taken", field: "email" };
}
if (input.password.length < 8) {
return { __typename: "WeakPassword", message: "Too short", requirements: ["8+ chars"] };
}
const user = await db.user.create({ data: input });
return { __typename: "RegisterSuccess", user, token: signJWT(user) };
}
4. Querying Error Unions
Example: Client query
mutation { register(input: { ... }) {
__typename
... on RegisterSuccess { user { id } token }
... on EmailAlreadyTaken { message field }
... on WeakPassword { message requirements }
} }
5. Handling User Errors
| Type | Strategy |
|---|---|
| Validation | Return typed error per field |
| Business rule | Specific error type (e.g., InsufficientFunds) |
| Conflict | VersionMismatch, DuplicateKey |
6. Handling System Errors
| Type | Strategy |
|---|---|
| DB down | Throw GraphQLError → top-level errors |
| Timeout | Throw → log, mask |
| Bug | Throw → log with stack, mask response |
7. Designing Error Hierarchies
Example: Common error interface
interface UserError {
message: String!
code: String!
}
interface FieldError implements UserError {
message: String!
code: String!
field: String!
}
8. Providing Error Details
| Field | Use |
|---|---|
| message | Human-readable |
| code | Machine-readable enum |
| field / path | Which input failed |
| retryable | Boolean hint |
| retryAfter | Seconds until retry safe |
9. Implementing Recoverable Errors
| Error Type | Recovery |
|---|---|
| RateLimitExceeded | Show retryAfter, retry automatically |
| VersionMismatch | Reload entity, retry |
| VerificationRequired | Prompt user for second factor |
10. Combining with Exceptions
| Strategy | Detail |
|---|---|
| Typed errors | Expected, recoverable, in payload |
| GraphQLError | Unexpected/system errors → top-level errors[] |
| Mixed payload | Both data and errors can coexist |