Error Types
Effect GraphQL provides built-in error types using Effect’s Data.TaggedError. These errors integrate with GraphQL’s error handling and provide structured information in responses.
Import
Section titled “Import”import { GraphQLError, ValidationError, AuthorizationError, NotFoundError} from "@effect-gql/core"Base Error
Section titled “Base Error”GraphQLError
Section titled “GraphQLError”The base error type for all GraphQL-related errors.
class GraphQLError extends Data.TaggedError("GraphQLError")<{ readonly message: string readonly extensions?: Record<string, unknown>}>Usage:
import { GraphQLError } from "@effect-gql/core"import { Effect } from "effect"
const resolver = () => Effect.fail( new GraphQLError({ message: "Something went wrong", extensions: { code: "INTERNAL_ERROR", timestamp: Date.now() } }))GraphQL Response:
{ "errors": [{ "message": "Something went wrong", "extensions": { "code": "INTERNAL_ERROR", "timestamp": 1705320600000 } }]}Specialized Errors
Section titled “Specialized Errors”ValidationError
Section titled “ValidationError”For input validation failures.
class ValidationError extends Data.TaggedError("ValidationError")<{ readonly message: string readonly field?: string readonly extensions?: Record<string, unknown>}>Usage:
import { ValidationError } from "@effect-gql/core"
mutation("createUser", { type: UserSchema, args: { email: S.String }, resolve: ({ email }) => Effect.gen(function* () { if (!email.includes("@")) { return yield* Effect.fail( new ValidationError({ message: "Invalid email format", field: "email" }) ) } // ... })})GraphQL Response:
{ "errors": [{ "message": "Invalid email format", "extensions": { "code": "VALIDATION_ERROR", "field": "email" } }]}AuthorizationError
Section titled “AuthorizationError”For access control failures.
class AuthorizationError extends Data.TaggedError("AuthorizationError")<{ readonly message: string readonly extensions?: Record<string, unknown>}>Usage:
import { AuthorizationError } from "@effect-gql/core"
query("adminStats", { type: StatsSchema, resolve: () => Effect.gen(function* () { const auth = yield* AuthContext if (auth.user?.role !== "ADMIN") { return yield* Effect.fail( new AuthorizationError({ message: "Admin access required" }) ) } // ... })})GraphQL Response:
{ "errors": [{ "message": "Admin access required", "extensions": { "code": "AUTHORIZATION_ERROR" } }]}NotFoundError
Section titled “NotFoundError”For missing resources.
class NotFoundError extends Data.TaggedError("NotFoundError")<{ readonly message: string readonly resourceType?: string readonly resourceId?: string readonly extensions?: Record<string, unknown>}>Usage:
import { NotFoundError } from "@effect-gql/core"
query("user", { type: UserSchema, args: { id: S.String }, resolve: ({ id }) => Effect.gen(function* () { const db = yield* Database const user = yield* db.findUser(id) if (!user) { return yield* Effect.fail( new NotFoundError({ message: `User not found: ${id}`, resourceType: "User", resourceId: id }) ) } return user })})GraphQL Response:
{ "errors": [{ "message": "User not found: 123", "extensions": { "code": "NOT_FOUND", "resourceType": "User", "resourceId": "123" } }]}Creating Custom Errors
Section titled “Creating Custom Errors”Extend the base error or create new tagged errors:
import { Data } from "effect"
// Custom error with additional fieldsclass RateLimitError extends Data.TaggedError("RateLimitError")<{ readonly message: string readonly retryAfter: number readonly limit: number}> {}
// Usageconst resolver = () => Effect.fail( new RateLimitError({ message: "Rate limit exceeded", retryAfter: 60, limit: 100 }))GraphQL Response:
{ "errors": [{ "message": "Rate limit exceeded", "extensions": { "code": "RATE_LIMIT_ERROR", "retryAfter": 60, "limit": 100 } }]}Error Handling Patterns
Section titled “Error Handling Patterns”Catching Specific Errors
Section titled “Catching Specific Errors”import { Effect } from "effect"import { NotFoundError } from "@effect-gql/core"
query("userOrDefault", { type: UserSchema, args: { id: S.String }, resolve: ({ id }) => getUserById(id).pipe( Effect.catchTag("NotFoundError", () => Effect.succeed(defaultUser) ) )})Transforming Errors
Section titled “Transforming Errors”import { Effect } from "effect"
const resolve = ({ id }) => db.findUser(id).pipe( Effect.catchAll((dbError) => Effect.fail( new GraphQLError({ message: "Database error", extensions: { code: "DATABASE_ERROR", originalError: dbError.message } }) ) ) )Multiple Error Types
Section titled “Multiple Error Types”type ResolverError = | ValidationError | AuthorizationError | NotFoundError
const resolver = (): Effect.Effect<User, ResolverError, Database> => Effect.gen(function* () { const auth = yield* AuthContext if (!auth.user) { return yield* Effect.fail( new AuthorizationError({ message: "Login required" }) ) } // ... })Error Serialization
Section titled “Error Serialization”Errors are automatically serialized to GraphQL error format:
| Error Property | GraphQL Location |
|---|---|
message | errors[].message |
_tag | errors[].extensions.code |
| Other fields | errors[].extensions.* |
Effect Schema Validation Errors
Section titled “Effect Schema Validation Errors”When Effect Schema validation fails on input arguments, a ValidationError is automatically generated:
mutation("createUser", { type: UserSchema, args: { age: S.Number.pipe(S.int(), S.positive()) }, resolve: ({ age }) => { ... }})
// Client sends: { age: -5 }// Response:{ "errors": [{ "message": "Expected positive number, got -5", "extensions": { "code": "VALIDATION_ERROR", "field": "age" } }]}Best Practices
Section titled “Best Practices”1. Use Specific Error Types
Section titled “1. Use Specific Error Types”// Good - Specific error typeEffect.fail(new NotFoundError({ message: "User not found", resourceType: "User", resourceId: id}))
// Avoid - Generic errorEffect.fail(new Error("User not found"))2. Include Helpful Extensions
Section titled “2. Include Helpful Extensions”new ValidationError({ message: "Invalid email format", field: "email", extensions: { expected: "valid email address", received: email, suggestion: "Check for missing @ symbol" }})3. Don’t Expose Internal Details
Section titled “3. Don’t Expose Internal Details”// Good - User-friendly messagenew GraphQLError({ message: "Unable to process request"})
// Avoid - Exposes internalsnew GraphQLError({ message: `PostgreSQL error: relation "users" does not exist`})4. Log Internal Errors
Section titled “4. Log Internal Errors”const resolver = () => internalOperation().pipe( Effect.catchAll((error) => Effect.gen(function* () { // Log internally yield* Logger.error("Internal error", { error })
// Return sanitized error return yield* Effect.fail( new GraphQLError({ message: "Internal error" }) ) }) ) )