Skip to content

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 {
GraphQLError,
ValidationError,
AuthorizationError,
NotFoundError
} from "@effect-gql/core"

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
}
}]
}

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"
}
}]
}

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"
}
}]
}

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"
}
}]
}

Extend the base error or create new tagged errors:

import { Data } from "effect"
// Custom error with additional fields
class RateLimitError extends Data.TaggedError("RateLimitError")<{
readonly message: string
readonly retryAfter: number
readonly limit: number
}> {}
// Usage
const 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
}
}]
}
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)
)
)
})
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
}
})
)
)
)
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" })
)
}
// ...
})

Errors are automatically serialized to GraphQL error format:

Error PropertyGraphQL Location
messageerrors[].message
_tagerrors[].extensions.code
Other fieldserrors[].extensions.*

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"
}
}]
}
// Good - Specific error type
Effect.fail(new NotFoundError({
message: "User not found",
resourceType: "User",
resourceId: id
}))
// Avoid - Generic error
Effect.fail(new Error("User not found"))
new ValidationError({
message: "Invalid email format",
field: "email",
extensions: {
expected: "valid email address",
received: email,
suggestion: "Check for missing @ symbol"
}
})
// Good - User-friendly message
new GraphQLError({
message: "Unable to process request"
})
// Avoid - Exposes internals
new GraphQLError({
message: `PostgreSQL error: relation "users" does not exist`
})
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" })
)
})
)
)